From 90ee87062f48fac76b4359dc98f5b76584b3088e Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 28 Aug 2021 23:34:03 +0800 Subject: [PATCH] feat: new memory settings --- .../hmcl/game/HMCLGameRepository.java | 10 +- .../hmcl/setting/VersionSetting.java | 18 +++ .../hmcl/ui/versions/VersionSettingsPage.java | 152 +++++++++++++++--- HMCL/src/main/resources/assets/css/root.css | 33 ++-- .../assets/lang/I18N_zh_CN.properties | 13 +- .../hmcl/util/platform/OperatingSystem.java | 75 +++++++-- 6 files changed, 246 insertions(+), 55 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 2abddaf12..1364bd70d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -296,7 +296,11 @@ public class HMCLGameRepository extends DefaultGameRepository { .setProfileName(Metadata.TITLE) .setGameArguments(StringUtils.tokenize(vs.getMinecraftArgs())) .setJavaArguments(StringUtils.tokenize(vs.getJavaArgs())) - .setMaxMemory(vs.getMaxMemory()) + .setMaxMemory((int)(getAllocatedMemory( + vs.getMaxMemory(), + OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID).getAvailable(), + vs.isAutoMemory() + ) / 1024 / 1024)) .setMinMemory(vs.getMinMemory()) .setMetaspace(Lang.toIntOrNull(vs.getPermSize())) .setWidth(vs.getWidth()) @@ -400,4 +404,8 @@ public class HMCLGameRepository extends DefaultGameRepository { return versions.containsKey(id); } } + + public static long getAllocatedMemory(long minimum, long available, boolean auto) { + return auto ? Math.max(minimum, (long) (available * 0.8)) : minimum; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index c10e32da5..a47e4e0b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -237,6 +237,20 @@ public final class VersionSetting implements Cloneable { minMemoryProperty.set(minMemory); } + private final BooleanProperty autoMemory = new SimpleBooleanProperty(this, "autoMemory", true); + + public boolean isAutoMemory() { + return autoMemory.get(); + } + + public BooleanProperty autoMemoryProperty() { + return autoMemory; + } + + public void setAutoMemory(boolean autoMemory) { + this.autoMemory.set(autoMemory); + } + private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, "precalledCommand", ""); public StringProperty preLaunchCommandProperty() { @@ -558,6 +572,7 @@ public final class VersionSetting implements Cloneable { permSizeProperty.addListener(listener); maxMemoryProperty.addListener(listener); minMemoryProperty.addListener(listener); + autoMemory.addListener(listener); preLaunchCommandProperty.addListener(listener); javaArgsProperty.addListener(listener); minecraftArgsProperty.addListener(listener); @@ -589,6 +604,7 @@ public final class VersionSetting implements Cloneable { versionSetting.setPermSize(getPermSize()); versionSetting.setMaxMemory(getMaxMemory()); versionSetting.setMinMemory(getMinMemory()); + versionSetting.setAutoMemory(isAutoMemory()); versionSetting.setPreLaunchCommand(getPreLaunchCommand()); versionSetting.setJavaArgs(getJavaArgs()); versionSetting.setMinecraftArgs(getMinecraftArgs()); @@ -619,6 +635,7 @@ public final class VersionSetting implements Cloneable { obj.addProperty("minecraftArgs", src.getMinecraftArgs()); obj.addProperty("maxMemory", src.getMaxMemory() <= 0 ? OperatingSystem.SUGGESTED_MEMORY : src.getMaxMemory()); obj.addProperty("minMemory", src.getMinMemory()); + obj.addProperty("autoMemory", src.isAutoMemory()); obj.addProperty("permSize", src.getPermSize()); obj.addProperty("width", src.getWidth()); obj.addProperty("height", src.getHeight()); @@ -659,6 +676,7 @@ public final class VersionSetting implements Cloneable { vs.setMinecraftArgs(Optional.ofNullable(obj.get("minecraftArgs")).map(JsonElement::getAsString).orElse("")); vs.setMaxMemory(maxMemoryN); vs.setMinMemory(Optional.ofNullable(obj.get("minMemory")).map(JsonElement::getAsInt).orElse(null)); + vs.setAutoMemory(Optional.ofNullable(obj.get("autoMemory")).map(JsonElement::getAsBoolean).orElse(true)); vs.setPermSize(Optional.ofNullable(obj.get("permSize")).map(JsonElement::getAsString).orElse("")); vs.setWidth(Optional.ofNullable(obj.get("width")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0)); vs.setHeight(Optional.ofNullable(obj.get("height")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index b44beaa80..c456cfabd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -21,20 +21,17 @@ import com.jfoenix.controls.*; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.*; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.image.Image; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.GameDirectoryType; +import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.NativesDirectoryType; import org.jackhuang.hmcl.game.ProcessPriority; import org.jackhuang.hmcl.setting.LauncherVisibility; @@ -47,8 +44,10 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -58,6 +57,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.stream.Collectors; @@ -76,7 +76,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final VBox rootPane; private final JFXTextField txtWidth; private final JFXTextField txtHeight; - private final JFXTextField txtMaxMemory; private final JFXTextField txtJVMArgs; private final JFXTextField txtGameArgs; private final JFXTextField txtMetaspace; @@ -87,8 +86,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final ComponentList componentList; private final ComponentList iconPickerItemWrapper; private final JFXComboBox cboLauncherVisibility; + private final JFXCheckBox chkAutoAllocate; private final JFXCheckBox chkFullscreen; - private final Label lblPhysicalMemory; private final JFXToggleButton chkNoJVMArgs; private final JFXToggleButton chkNoGameCheck; private final JFXToggleButton chkNoJVMCheck; @@ -105,6 +104,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final InvalidationListener javaListener = any -> initJavaSubtitle(); + private boolean uiVisible = false; + private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(); + private final ObjectProperty memoryStatusProperty = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID); + public VersionSettingsPage() { ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); @@ -163,21 +166,120 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag gameDirItem.setCustomText(i18n("settings.custom")); gameDirItem.setDirectory(true); - BorderPane maxMemoryPane = new BorderPane(); + VBox maxMemoryPane = new VBox(8); { - VBox vbox = new VBox(); - maxMemoryPane.setLeft(vbox); - Label maxMemoryLabel = new Label(i18n("settings.max_memory")); - lblPhysicalMemory = new Label(); - lblPhysicalMemory.getStyleClass().add("subtitle-label"); - vbox.getChildren().setAll(maxMemoryLabel, lblPhysicalMemory); + Label title = new Label(i18n("settings.memory")); + VBox.setMargin(title, new Insets(0, 0, 8, 0)); - txtMaxMemory = new JFXTextField(); - maxMemoryPane.setRight(txtMaxMemory); - BorderPane.setAlignment(txtMaxMemory, Pos.CENTER_RIGHT); - FXUtils.setValidateWhileTextChanged(txtMaxMemory, true); - FXUtils.setLimitWidth(txtMaxMemory, 300); - txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false)); + chkAutoAllocate = new JFXCheckBox(i18n("settings.memory.auto_allocate")); + VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5)); + + HBox lowerBoundPane = new HBox(); + lowerBoundPane.setAlignment(Pos.CENTER); + VBox.setMargin(lowerBoundPane, new Insets(8, 0, 0, 16)); + { + Label label = new Label(); + label.textProperty().bind(Bindings.createStringBinding(() -> { + if (chkAutoAllocate.isSelected()) { + return i18n("settings.memory.lower_bound"); + } else { + return i18n("settings.memory"); + } + }, chkAutoAllocate.selectedProperty())); + + JFXSlider slider = new JFXSlider(0, 1, 0); + HBox.setMargin(slider, new Insets(0, 16, 0, 16)); + HBox.setHgrow(slider, Priority.ALWAYS); + slider.setValueFactory(self -> Bindings.createStringBinding(() -> (int)(self.getValue() * 100) + "%", self.valueProperty())); + AtomicBoolean changedByTextField = new AtomicBoolean(false); + FXUtils.onChangeAndOperate(maxMemoryProperty, maxMemory -> { + changedByTextField.set(true); + slider.setValue(maxMemory.intValue() * 1.0 / OperatingSystem.TOTAL_MEMORY); + changedByTextField.set(false); + }); + slider.valueProperty().addListener((value, oldVal, newVal) -> { + if (changedByTextField.get()) return; + maxMemoryProperty.set((int)(value.getValue().doubleValue() * OperatingSystem.TOTAL_MEMORY)); + }); + + JFXTextField txtMaxMemory = new JFXTextField(); + FXUtils.setLimitWidth(txtMaxMemory, 60); + FXUtils.setValidateWhileTextChanged(txtMaxMemory, true); + txtMaxMemory.textProperty().bindBidirectional(maxMemoryProperty, SafeStringConverter.fromInteger()); + txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false)); + + lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MB")); + } + + BorderPane titlePane = new BorderPane(); + VBox.setMargin(titlePane, new Insets(0, 0, 0, 16)); + { + Label left = new Label(i18n("settings.memory.used_per_total")); + left.getStyleClass().add("subtitle-label"); + titlePane.setLeft(left); + + Label right = new Label(); + right.textProperty().bind(Bindings.createStringBinding(() -> { + if (chkAutoAllocate.isSelected()) { + return i18n("settings.memory.allocate.auto"); + } else { + return i18n("settings.memory.allocate.manual"); + } + }, chkAutoAllocate.selectedProperty())); + right.getStyleClass().add("subtitle-label"); + titlePane.setRight(right); + } + + StackPane progressBarPane = new StackPane(); + progressBarPane.setAlignment(Pos.CENTER_LEFT); + VBox.setMargin(progressBarPane, new Insets(0, 0, 0, 16)); + { + progressBarPane.setMinHeight(4); + progressBarPane.getStyleClass().add("memory-total"); + + StackPane usedMemory = new StackPane(); + usedMemory.getStyleClass().add("memory-used"); + usedMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> + progressBarPane.getWidth() * + (memoryStatusProperty.get().getUsed() * 1.0 / memoryStatusProperty.get().getTotal()), progressBarPane.widthProperty(), + memoryStatusProperty)); + StackPane allocateMemory = new StackPane(); + allocateMemory.getStyleClass().add("memory-allocate"); + allocateMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> + progressBarPane.getWidth() * + Math.min(1.0, + (double)(HMCLGameRepository.getAllocatedMemory(maxMemoryProperty.get() * 1024L * 1024L, memoryStatusProperty.get().getAvailable(), chkAutoAllocate.isSelected()) + + memoryStatusProperty.get().getUsed()) / memoryStatusProperty.get().getTotal()), progressBarPane.widthProperty(), + maxMemoryProperty, memoryStatusProperty, chkAutoAllocate.selectedProperty())); + + progressBarPane.getChildren().setAll(allocateMemory, usedMemory); + } + + BorderPane digitalPane = new BorderPane(); + VBox.setMargin(digitalPane, new Insets(0, 0, 0, 16)); + { + Label lblPhysicalMemory = new Label(); + lblPhysicalMemory.getStyleClass().add("memory-label"); + digitalPane.setLeft(lblPhysicalMemory); + lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() -> { + return i18n("settings.memory.used_per_total.format", memoryStatusProperty.get().getUsedGB(), memoryStatusProperty.get().getTotalGB()); + }, memoryStatusProperty)); + + Label lblAllocateMemory = new Label(); + lblAllocateMemory.textProperty().bind(Bindings.createStringBinding(() -> { + long maxMemory = Lang.parseInt(maxMemoryProperty.get(), 0) * 1024L * 1024L; + return i18n(memoryStatusProperty.get().hasAvailable() && maxMemory > memoryStatusProperty.get().getAvailable() + ? (chkAutoAllocate.isSelected() ? "settings.memory.allocate.format.auto.exceeded" : "settings.memory.allocate.format.manual.exceeded") + : (chkAutoAllocate.isSelected() ? "settings.memory.allocate.format.auto" : "settings.memory.allocate.format.manual"), + OperatingSystem.PhysicalMemoryStatus.toGigaBytes(maxMemory), + OperatingSystem.PhysicalMemoryStatus.toGigaBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatusProperty.get().getAvailable(), chkAutoAllocate.isSelected())), + OperatingSystem.PhysicalMemoryStatus.toGigaBytes(memoryStatusProperty.get().getAvailable())); + }, memoryStatusProperty, maxMemoryProperty, chkAutoAllocate.selectedProperty())); + lblAllocateMemory.getStyleClass().add("memory-label"); + digitalPane.setRight(lblAllocateMemory); + } + + maxMemoryPane.getChildren().setAll(title, chkAutoAllocate, lowerBoundPane, titlePane, progressBarPane, digitalPane); } BorderPane launcherVisibilityPane = new BorderPane(); @@ -368,7 +470,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag } private void initialize() { - lblPhysicalMemory.setText(i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB"); + memoryStatusProperty.set(OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID)); Task.supplyAsync(JavaVersion::getJavas).thenAcceptAsync(Schedulers.javafx(), list -> { javaItem.loadChildren(list.stream() @@ -434,7 +536,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag if (lastVersionSetting != null) { FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty()); FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty()); - FXUtils.unbindInt(txtMaxMemory, lastVersionSetting.maxMemoryProperty()); + maxMemoryProperty.unbindBidirectional(lastVersionSetting.maxMemoryProperty()); FXUtils.unbindString(javaItem.getTxtCustom(), lastVersionSetting.javaDirProperty()); FXUtils.unbindString(gameDirItem.getTxtCustom(), lastVersionSetting.gameDirProperty()); FXUtils.unbindString(nativesDirItem.getTxtCustom(), lastVersionSetting.nativesDirProperty()); @@ -444,6 +546,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag FXUtils.unbindString(txtWrapper, lastVersionSetting.wrapperProperty()); FXUtils.unbindString(txtPrecallingCommand, lastVersionSetting.preLaunchCommandProperty()); FXUtils.unbindString(txtServerIP, lastVersionSetting.serverIpProperty()); + FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty()); FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty()); FXUtils.unbindBoolean(chkNoGameCheck, lastVersionSetting.notCheckGameProperty()); FXUtils.unbindBoolean(chkNoJVMCheck, lastVersionSetting.notCheckJVMProperty()); @@ -469,7 +572,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag // bind new data fields FXUtils.bindInt(txtWidth, versionSetting.widthProperty()); FXUtils.bindInt(txtHeight, versionSetting.heightProperty()); - FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty()); + maxMemoryProperty.bindBidirectional(versionSetting.maxMemoryProperty()); FXUtils.bindString(javaItem.getTxtCustom(), versionSetting.javaDirProperty()); FXUtils.bindString(gameDirItem.getTxtCustom(), versionSetting.gameDirProperty()); FXUtils.bindString(nativesDirItem.getTxtCustom(), versionSetting.nativesDirProperty()); @@ -479,6 +582,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty()); FXUtils.bindString(txtPrecallingCommand, versionSetting.preLaunchCommandProperty()); FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty()); + FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty()); FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty()); FXUtils.bindBoolean(chkNoGameCheck, versionSetting.notCheckGameProperty()); FXUtils.bindBoolean(chkNoJVMCheck, versionSetting.notCheckJVMProperty()); diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c42898f69..21d842d82 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -42,6 +42,22 @@ -fx-text-fill: rgba(0, 0, 0, 0.5); } +.memory-label { + -fx-font-size: 14px; +} + +.memory-used { + -fx-background-color: -fx-base-darker-color; +} + +.memory-allocate { + -fx-background-color: -fx-base-check-color; +} + +.memory-total { + -fx-background-color: -fx-base-rippler-color; +} + .update-label { -fx-text-fill: red; } @@ -530,23 +546,12 @@ * * *******************************************************/ -.jfx-slider-style { +.jfx-slider { -jfx-indicator-position: right; } -.jfx-slider-style > .thumb { - -fx-background-color: #03a9f4; -} - -.jfx-slider-style .track { - -fx-background-color: #ff5252; - -fx-pref-height: 5px; - -fx-pref-width: 5px; -} - -.jfx-slider-style .sliderValue { - -fx-stroke: WHITE; - -fx-font-size: 10.0; +.jfx-slider .thumb { + -fx-background-color: -fx-base-color; } /******************************************************************************* diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 55c8ec27f..a4a2a2bb1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -520,8 +520,17 @@ settings.launcher.proxy.socks=Socks settings.launcher.proxy.username=账户 settings.launcher.theme=主题 -settings.min_memory=最小内存(MB) -settings.max_memory=最大内存(MB) +settings.memory=游戏内存 +settings.memory.allocate.auto=最低分配 / 实际分配 +settings.memory.allocate.manual=游戏分配 +settings.memory.allocate.format.auto=%1$.1f GB / %2$.1f GB +settings.memory.allocate.format.auto.exceeded=%1$.1f GB / %2$.1f GB (可用 %3$.1f GB) +settings.memory.allocate.format.manual=%1$.1f GB +settings.memory.allocate.format.manual.exceeded=%1$.1f GB (可用 %3$.1f GB) +settings.memory.auto_allocate=自动分配 +settings.memory.lower_bound=最低分配 +settings.memory.used_per_total=已使用 / 总内存 +settings.memory.used_per_total.format=%1$.1f GB / %2$.1f GB settings.physical_memory=物理内存大小 settings.show_log=查看日志 settings.tabs.installers=自动安装 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java index 5182dbcb4..9b9673933 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java @@ -17,12 +17,7 @@ */ package org.jackhuang.hmcl.util.platform; -import javax.management.JMException; -import javax.management.MBeanServer; -import javax.management.ObjectName; - import java.io.File; -import java.lang.management.ManagementFactory; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; @@ -108,7 +103,8 @@ public enum OperatingSystem { else CURRENT_OS = UNKNOWN; - TOTAL_MEMORY = getTotalPhysicalMemorySize() + TOTAL_MEMORY = getPhysicalMemoryStatus() + .map(PhysicalMemoryStatus::getTotal) .map(bytes -> (int) (bytes / 1024 / 1024)) .orElse(1024); @@ -133,15 +129,22 @@ public enum OperatingSystem { } } - private static Optional getTotalPhysicalMemorySize() { - MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); - try { - Object attribute = mBeanServer.getAttribute(new ObjectName("java.lang", "type", "OperatingSystem"), "TotalPhysicalMemorySize"); - if (attribute instanceof Long) { - return Optional.of((Long) attribute); + public static Optional getPhysicalMemoryStatus() { + 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(); + + if (CURRENT_OS == LINUX) { + // On Linux, real amount of memory that is free for using is "available" size of memory, + // which also includes size of memory for caching that can be make use of. + // But available size of memory cannot be obtained by OperatingSystemMXBean interface. + // So we simply disable reporting free physical memory size on Linux. + return Optional.of(new PhysicalMemoryStatus(sunBean.getTotalPhysicalMemorySize(), -1)); } - } catch (JMException e) { - return Optional.empty(); + + return Optional.of(new PhysicalMemoryStatus(sunBean.getTotalPhysicalMemorySize(), sunBean.getFreePhysicalMemorySize())); } return Optional.empty(); } @@ -203,4 +206,48 @@ 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); + } }