在 SystemInfo 中采集内存信息 (#3903)

This commit is contained in:
Glavo
2025-05-10 12:06:11 +08:00
committed by GitHub
parent 1da98d6d67
commit 48ca2d6ee6
24 changed files with 483 additions and 200 deletions

View File

@@ -212,7 +212,7 @@ public class DefaultLauncher extends Launcher {
if (javaVersion <= 8) {
res.addUnstableDefault("MaxInlineLevel", "15");
}
if (is64bit && OperatingSystem.TOTAL_MEMORY > 4 * 1024) {
if (is64bit && SystemInfo.getTotalMemorySize() > 4L * 1024 * 1024 * 1024) {
res.addUnstableDefault("DontCompileHugeMethods", false);
res.addUnstableDefault("MaxNodeLimit", "240000");
res.addUnstableDefault("NodeLimitFudgeFactor", "8000");

View File

@@ -0,0 +1,66 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import java.text.DecimalFormat;
/**
* @author Glavo
*/
public enum DataSizeUnit {
BYTES,
KILOBYTES,
MEGABYTES,
GIGABYTES,
TERABYTES;
private static final DataSizeUnit[] VALUES = values();
private static final DecimalFormat FORMAT = new DecimalFormat("#.##");
public static String format(long bytes) {
for (int i = VALUES.length - 1; i > 0; i--) {
DataSizeUnit unit = VALUES[i];
if (bytes >= unit.bytes) {
return unit.formatBytes(bytes);
}
}
return bytes == 1 ? "1 byte" : bytes + " bytes";
}
private final long bytes = 1L << (ordinal() * 10);
private final char abbreviationChar = name().charAt(0);
private final String abbreviation = abbreviationChar == 'B' ? "B" : abbreviationChar + "iB";
public double convertFromBytes(long bytes) {
return (double) bytes / this.bytes;
}
public long convertToBytes(double amount) {
return (long) (amount * this.bytes);
}
private String format(double amount) {
return FORMAT.format(amount) + " " + this.abbreviation;
}
public String formatBytes(long bytes) {
return format(convertFromBytes(bytes));
}
}

View File

@@ -85,16 +85,6 @@ public enum OperatingSystem {
*/
public static final OperatingSystem CURRENT_OS = parseOSName(System.getProperty("os.name"));
/**
* The total memory/MB this computer have.
*/
public static final int TOTAL_MEMORY;
/**
* The suggested memory size/MB for Minecraft to allocate.
*/
public static final int SUGGESTED_MEMORY;
public static final String PATH_SEPARATOR = File.pathSeparator;
public static final String FILE_SEPARATOR = File.separator;
public static final String LINE_SEPARATOR = System.lineSeparator();
@@ -129,8 +119,6 @@ public enum OperatingSystem {
private static final String[] INVALID_RESOURCE_BASENAMES;
private static final String[] INVALID_RESOURCE_FULLNAMES;
private static final Pattern MEMINFO_PATTERN = Pattern.compile("^(?<key>.*?):\\s+(?<value>\\d+) kB?$");
static {
String nativeEncoding = System.getProperty("native.encoding");
String hmclNativeEncoding = System.getProperty("hmcl.native.encoding");
@@ -246,13 +234,6 @@ public enum OperatingSystem {
OS_RELEASE_NAME = osRelease.get("NAME");
OS_RELEASE_PRETTY_NAME = osRelease.get("PRETTY_NAME");
PhysicalMemoryStatus physicalMemoryStatus = getPhysicalMemoryStatus();
TOTAL_MEMORY = physicalMemoryStatus != PhysicalMemoryStatus.INVALID
? (int) (physicalMemoryStatus.getTotal() / 1024 / 1024)
: 1024;
SUGGESTED_MEMORY = TOTAL_MEMORY >= 32768 ? 8192 : (int) (Math.round(1.0 * TOTAL_MEMORY / 4.0 / 128.0) * 128);
// setup the invalid names
if (CURRENT_OS == WINDOWS) {
// valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
@@ -315,48 +296,6 @@ public enum OperatingSystem {
return major >= 6 && !SYSTEM_VERSION.startsWith("6.0");
}
@SuppressWarnings("deprecation")
public static PhysicalMemoryStatus getPhysicalMemoryStatus() {
if (CURRENT_OS == LINUX) {
try {
long free = 0, available = 0, total = 0;
for (String line : Files.readAllLines(Paths.get("/proc/meminfo"))) {
Matcher matcher = MEMINFO_PATTERN.matcher(line);
if (matcher.find()) {
String key = matcher.group("key");
String value = matcher.group("value");
if ("MemAvailable".equals(key)) {
available = Long.parseLong(value) * 1024;
}
if ("MemFree".equals(key)) {
free = Long.parseLong(value) * 1024;
}
if ("MemTotal".equals(key)) {
total = Long.parseLong(value) * 1024;
}
}
}
if (total > 0) {
return new PhysicalMemoryStatus(total, available > 0 ? available : free);
}
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
try {
java.lang.management.OperatingSystemMXBean bean = java.lang.management.ManagementFactory.getOperatingSystemMXBean();
if (bean instanceof com.sun.management.OperatingSystemMXBean) {
com.sun.management.OperatingSystemMXBean sunBean =
(com.sun.management.OperatingSystemMXBean)
java.lang.management.ManagementFactory.getOperatingSystemMXBean();
return new PhysicalMemoryStatus(sunBean.getTotalPhysicalMemorySize(), sunBean.getFreePhysicalMemorySize());
}
} catch (NoClassDefFoundError ignored) {
}
return PhysicalMemoryStatus.INVALID;
}
@SuppressWarnings("removal")
public static void forceGC() {
System.gc();
@@ -420,48 +359,4 @@ public enum OperatingSystem {
return true;
}
public static class PhysicalMemoryStatus {
private final long total;
private final long available;
public PhysicalMemoryStatus(long total, long available) {
this.total = total;
this.available = available;
}
public long getTotal() {
return total;
}
public double getTotalGB() {
return toGigaBytes(total);
}
public long getUsed() {
return hasAvailable() ? total - available : 0;
}
public double getUsedGB() {
return toGigaBytes(getUsed());
}
public long getAvailable() {
return available;
}
public double getAvailableGB() {
return toGigaBytes(available);
}
public boolean hasAvailable() {
return available >= 0;
}
public static double toGigaBytes(long bytes) {
return bytes / 1024. / 1024. / 1024.;
}
public static final PhysicalMemoryStatus INVALID = new PhysicalMemoryStatus(0, -1);
}
}

View File

@@ -17,8 +17,10 @@
*/
package org.jackhuang.hmcl.util.platform;
import org.jackhuang.hmcl.util.DataSizeUnit;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;
import org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector;
import org.jackhuang.hmcl.util.platform.windows.WindowsHardwareDetector;
@@ -44,13 +46,15 @@ public final class SystemInfo {
DETECTOR = new HardwareDetector();
}
public static final long TOTAL_MEMORY = DETECTOR.getTotalMemorySize();
public static final @Nullable List<GraphicsCard> GRAPHICS_CARDS = DETECTOR.detectGraphicsCards();
}
public static void initialize() {
StringBuilder builder = new StringBuilder("System Info:");
List<GraphicsCard> graphicsCards = getGraphicsCards();
// Graphics Card
List<GraphicsCard> graphicsCards = getGraphicsCards();
if (graphicsCards != null) {
if (graphicsCards.isEmpty())
builder.append("\n - GPU: Not Found");
@@ -64,18 +68,46 @@ public final class SystemInfo {
}
}
OperatingSystem.PhysicalMemoryStatus memoryStatus = OperatingSystem.getPhysicalMemoryStatus();
if (memoryStatus.getTotal() > 0 && memoryStatus.getAvailable() > 0) {
builder.append("\n - Memory: ")
.append(String.format("%.2f GiB / %.2f GiB (%d%%)",
memoryStatus.getUsedGB(), memoryStatus.getTotalGB(),
(int) (((double) memoryStatus.getUsed() / memoryStatus.getTotal()) * 100)
));
}
// Memory
long totalMemorySize = getTotalMemorySize();
long usedMemorySize = getUsedMemorySize();
builder.append("\n - Memory: ")
.append(DataSizeUnit.format(usedMemorySize))
.append(" / ")
.append(DataSizeUnit.format(totalMemorySize));
if (totalMemorySize > 0 && usedMemorySize > 0)
builder.append(" (").append((int) (((double) usedMemorySize / totalMemorySize) * 100)).append("%)");
LOG.info(builder.toString());
}
public static PhysicalMemoryStatus getPhysicalMemoryStatus() {
long totalMemorySize = getTotalMemorySize();
long freeMemorySize = getFreeMemorySize();
return totalMemorySize > 0 && freeMemorySize >= 0
? new PhysicalMemoryStatus(totalMemorySize, freeMemorySize)
: PhysicalMemoryStatus.INVALID;
}
public static long getTotalMemorySize() {
return Holder.TOTAL_MEMORY;
}
public static long getFreeMemorySize() {
return Holder.DETECTOR.getFreeMemorySize();
}
public static long getUsedMemorySize() {
long totalMemorySize = getTotalMemorySize();
if (totalMemorySize <= 0)
return 0;
return Long.max(0, totalMemorySize - getFreeMemorySize());
}
public static @Nullable List<GraphicsCard> getGraphicsCards() {
return Holder.GRAPHICS_CARDS;
}

View File

@@ -19,13 +19,40 @@ package org.jackhuang.hmcl.util.platform.hardware;
import org.jetbrains.annotations.Nullable;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.util.List;
/**
* @author Glavo
*/
@SuppressWarnings("ALL")
public class HardwareDetector {
public @Nullable List<GraphicsCard> detectGraphicsCards() {
return null;
}
public long getTotalMemorySize() {
try {
OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();
if (bean instanceof com.sun.management.OperatingSystemMXBean) {
return ((com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalPhysicalMemorySize();
}
} catch (NoClassDefFoundError ignored) {
}
return 0L;
}
public long getFreeMemorySize() {
try {
OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();
if (bean instanceof com.sun.management.OperatingSystemMXBean) {
return ((com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getFreePhysicalMemorySize();
}
} catch (NoClassDefFoundError ignored) {
}
return 0L;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.platform.hardware;
public final class PhysicalMemoryStatus {
private final long total;
private final long available;
public PhysicalMemoryStatus(long total, long available) {
this.total = total;
this.available = available;
}
public long getTotal() {
return total;
}
public long getUsed() {
return hasAvailable() ? total - available : 0;
}
public long getAvailable() {
return available;
}
public boolean hasAvailable() {
return available >= 0;
}
public static final PhysicalMemoryStatus INVALID = new PhysicalMemoryStatus(0, -1);
}

View File

@@ -21,7 +21,16 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
@@ -34,4 +43,62 @@ public final class LinuxHardwareDetector extends HardwareDetector {
return null;
return LinuxGPUDetector.detectAll();
}
private static final Path MEMINFO = Paths.get("/proc/meminfo");
private static final Pattern MEMINFO_PATTERN = Pattern.compile("^.+:\\s*(?<value>\\d+)\\s*kB?$");
private static long parseMemoryInfoLine(final String line) throws IOException {
Matcher matcher = MEMINFO_PATTERN.matcher(line);
if (!matcher.matches())
throw new IOException("Unable to parse line in /proc/meminfo: " + line);
return Long.parseLong(matcher.group("value")) * 1024;
}
@Override
public long getTotalMemorySize() {
try (BufferedReader reader = Files.newBufferedReader(MEMINFO)) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("MemTotal:")) {
long total = parseMemoryInfoLine(line);
if (total <= 0)
throw new IOException("Invalid total memory size: " + line + " kB");
return total;
}
}
} catch (Throwable e) {
LOG.warning("Failed to parse /proc/meminfo", e);
}
return super.getTotalMemorySize();
}
@Override
public long getFreeMemorySize() {
try (BufferedReader reader = Files.newBufferedReader(MEMINFO)) {
long free = -1L;
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("MemAvailable:")) {
long available = parseMemoryInfoLine(line);
if (available < 0)
throw new IOException("Invalid available memory size: " + line + " kB");
return available;
}
if (line.startsWith("MemFree:"))
free = parseMemoryInfoLine(line);
}
if (free >= 0)
return free;
} catch (Throwable e) {
LOG.warning("Failed to parse /proc/meminfo", e);
}
return super.getFreeMemorySize();
}
}

View File

@@ -48,4 +48,9 @@ public interface Kernel32 extends StdCallLibrary {
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getusergeoid">GetUserGeoID function</a>
*/
int GetUserGeoID(int geoClass);
/**
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex">GlobalMemoryStatusEx function</a>
*/
boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer);
}

View File

@@ -60,4 +60,33 @@ public interface WinTypes {
);
}
}
/**
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex">MEMORYSTATUSEX structure</a>
*/
final class MEMORYSTATUSEX extends Structure {
public int dwLength;
public int dwMemoryLoad;
public long ullTotalPhys;
public long ullAvailPhys;
public long ullTotalPageFile;
public long ullAvailPageFile;
public long ullTotalVirtual;
public long ullAvailVirtual;
public long ullAvailExtendedVirtual;
public MEMORYSTATUSEX() {
dwLength = size();
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(
"dwLength", "dwMemoryLoad",
"ullTotalPhys", "ullAvailPhys", "ullTotalPageFile", "ullAvailPageFile",
"ullTotalVirtual", "ullAvailVirtual", "ullAvailExtendedVirtual");
}
}
;
}

View File

@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
@@ -133,4 +134,36 @@ public final class WindowsHardwareDetector extends HardwareDetector {
return Collections.emptyList();
}
}
@Override
public long getTotalMemorySize() {
if (NativeUtils.USE_JNA) {
Kernel32 kernel32 = Kernel32.INSTANCE;
if (kernel32 != null) {
WinTypes.MEMORYSTATUSEX status = new WinTypes.MEMORYSTATUSEX();
if (kernel32.GlobalMemoryStatusEx(status))
return status.ullTotalPhys;
else
LOG.warning("Failed to get memory status: " + kernel32.GetLastError());
}
}
return super.getTotalMemorySize();
}
@Override
public long getFreeMemorySize() {
if (NativeUtils.USE_JNA) {
Kernel32 kernel32 = Kernel32.INSTANCE;
if (kernel32 != null) {
WinTypes.MEMORYSTATUSEX status = new WinTypes.MEMORYSTATUSEX();
if (kernel32.GlobalMemoryStatusEx(status))
return status.ullAvailPhys;
else
LOG.warning("Failed to get memory status: " + kernel32.GetLastError());
}
}
return super.getFreeMemorySize();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public final class DataSizeUnitTest {
@Test
public void testToString() {
assertEquals("0 bytes", DataSizeUnit.format(0));
assertEquals("1 byte", DataSizeUnit.format(1));
assertEquals("2 bytes", DataSizeUnit.format(2));
assertEquals("100 bytes", DataSizeUnit.format(100));
assertEquals("1023 bytes", DataSizeUnit.format(1023));
assertEquals("1 KiB", DataSizeUnit.format(1024));
assertEquals("1 KiB", DataSizeUnit.format(1025));
assertEquals("1.07 KiB", DataSizeUnit.format(1100));
assertEquals("4 KiB", DataSizeUnit.format(4096));
assertEquals("1 MiB", DataSizeUnit.format(1024 * 1024));
assertEquals("1.5 MiB", DataSizeUnit.format((long) (1.5 * 1024 * 1024)));
assertEquals("1 GiB", DataSizeUnit.format(1024 * 1024 * 1024));
assertEquals("1.5 GiB", DataSizeUnit.format((long) (1.5 * 1024 * 1024 * 1024)));
assertEquals("1 TiB", DataSizeUnit.format(1024L * 1024 * 1024 * 1024));
assertEquals("1.5 TiB", DataSizeUnit.format((long) (1.5 * 1024 * 1024 * 1024 * 1024)));
}
@Test
public void testConvertFromBytes() {
assertEquals(1, DataSizeUnit.KILOBYTES.convertFromBytes(1024L));
assertEquals(1.5, DataSizeUnit.KILOBYTES.convertFromBytes((long) (1024. * 1.5)));
assertEquals(1, DataSizeUnit.MEGABYTES.convertFromBytes(1024L * 1024));
assertEquals(1.5, DataSizeUnit.MEGABYTES.convertFromBytes((long) (1024 * 1024 * 1.5)));
}
@Test
public void testConvertToBytes() {
assertEquals(10., DataSizeUnit.BYTES.convertToBytes(10));
assertEquals(10. * 1024, DataSizeUnit.KILOBYTES.convertToBytes(10));
assertEquals(10. * 1024 * 1024, DataSizeUnit.MEGABYTES.convertToBytes(10));
assertEquals(10. * 1024 * 1024 * 1024, DataSizeUnit.GIGABYTES.convertToBytes(10));
assertEquals(10. * 1024 * 1024 * 1024 * 1024, DataSizeUnit.TERABYTES.convertToBytes(10));
}
private DataSizeUnitTest() {
}
}