diff --git a/PCL.Core/App/Config.cs b/PCL.Core/App/Config.cs index ae616ba86..8975607ab 100644 --- a/PCL.Core/App/Config.cs +++ b/PCL.Core/App/Config.cs @@ -483,6 +483,11 @@ partial class HideConfigGroup /// [ConfigItem("LaunchRamCustom", 15, ConfigSource.Local)] public partial int CustomMemorySize { get; set; } + /// + /// 自定义初始堆大小(-Xms)的滑块刻度值,0 表示不主动设置。仅在手动分配模式下生效,且实际值不会超过最大堆。 + /// + [ConfigItem("LaunchRamCustomInitial", 0, ConfigSource.Local)] public partial int CustomInitialMemorySize { get; set; } + /// /// 是否固定堆大小:启用后额外追加 -Xms 并使其等于 -Xmx,隐式禁用内存归还以降低延迟抖动、利于 ZGC。见 #3282。 /// @@ -622,6 +627,7 @@ partial class HideConfigGroup [ConfigItem("VersionUseDebugLog4j2Config", false)] public partial ArgConfig UseDebugLof4j2Config { get; } [ConfigItem("VersionRamType", 2)] public partial ArgConfig MemorySolution { get; } [ConfigItem("VersionRamCustom", 15)] public partial ArgConfig CustomMemorySize { get; } + [ConfigItem("VersionRamCustomInitial", 0)] public partial ArgConfig CustomInitialMemorySize { get; } [ConfigItem("VersionArgumentTitle", "")] public partial ArgConfig Title { get; } [ConfigItem("VersionArgumentTitleEmpty", false)] public partial ArgConfig UseGlobalTitle { get; } [ConfigItem("VersionArgumentInfo", "")] public partial ArgConfig TypeInfo { get; } diff --git a/PCL.Core/App/Localization/Languages/en-GB.xaml b/PCL.Core/App/Localization/Languages/en-GB.xaml index a7a7a39e4..50040ce65 100644 --- a/PCL.Core/App/Localization/Languages/en-GB.xaml +++ b/PCL.Core/App/Localization/Languages/en-GB.xaml @@ -2771,6 +2771,13 @@ Dynamically adjust the memory allocated to the game based on the installed mods and available system memory. Available: {0} GiB Game allocation + Maximum memory (-Xmx) + The maximum heap memory the game can use + Initial memory (-Xms) + The heap memory pre-allocated when the game starts, which can be lower than the maximum heap (-Xmx). Set to 0 to let the JVM decide. Only takes effect when memory allocation is set to Custom, and has no effect when Lock memory allocation is enabled. + initial {0} GiB, available {1} GiB + initial {0} GiB + available {0} GiB Game memory Used / installed memory 32-bit Java can only allocate up to 1 GiB of memory. It is strongly recommended to use 64-bit Java! diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index 43250bafb..77535faa6 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -2771,6 +2771,13 @@ Dynamically adjust the memory allocated to the game based on the installed mods and available system memory. Available: {0} GiB Game allocation + Maximum memory (-Xmx) + The maximum heap memory the game can use + Initial memory (-Xms) + The heap memory pre-allocated when the game starts, which can be lower than the maximum heap (-Xmx). Set to 0 to let the JVM decide. Only takes effect when memory allocation is set to Custom, and has no effect when Lock memory allocation is enabled. + initial {0} GiB, available {1} GiB + initial {0} GiB + available {0} GiB Game memory Used / installed memory 32-bit Java can only allocate up to 1 GiB of memory. It is strongly recommended to use 64-bit Java! diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index 4834027e9..56dcb32e7 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -2771,6 +2771,13 @@ 根据安装的模组量与电脑剩余内存,动态调整为游戏分配的内存 可用 {0} GiB 游戏分配 + 最大内存(-Xmx) + 游戏可使用的最大堆内存大小 + 初始内存(-Xms) + 游戏启动时预先分配的堆内存大小,可以低于最大堆(-Xmx)。设为 0 表示不主动设置,由 JVM 自行决定。仅在手动配置内存分配时生效,启用“锁定内存分配”时不生效。 + 初始 {0} GiB,可用 {1} GiB + 初始 {0} GiB + 可用 {0} GiB 游戏内存 已使用内存 / 已安装内存 32 位 Java 只能分配最多 1 GiB 的内存。强烈建议换用 64 位的 Java! diff --git a/PCL.Core/App/Localization/Languages/zh-TW.xaml b/PCL.Core/App/Localization/Languages/zh-TW.xaml index 136e5cbbf..d62dcb2cd 100644 --- a/PCL.Core/App/Localization/Languages/zh-TW.xaml +++ b/PCL.Core/App/Localization/Languages/zh-TW.xaml @@ -2771,6 +2771,13 @@ 根據安裝的模組量與電腦剩餘記憶體,動態調整為遊戲分配的記憶體 可用 {0} GiB 遊戲分配 + 最大記憶體(-Xmx) + 遊戲可使用的最大堆積記憶體大小 + 初始記憶體(-Xms) + 遊戲啟動時預先分配的堆積記憶體大小,可以低於最大堆(-Xmx)。設為 0 表示不主動設定,由 JVM 自行決定。僅在手動配置記憶體分配時生效,啟用「鎖定記憶體分配」時不生效。 + 初始 {0} GiB,可用 {1} GiB + 初始 {0} GiB + 可用 {0} GiB 遊戲記憶體 已使用記憶體 / 已安裝記憶體 32 位元 Java 只能分配最多 1 GiB 的記憶體。強烈建議換用 64 位元的 Java! diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs index d8a1260e1..eda3964c4 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs @@ -1135,10 +1135,22 @@ internal static void SecretLaunchJvmArgs(ref List dataList) var maxHeapArg = Math.Floor(totalRamMb).ToString(CultureInfo.InvariantCulture); dataList.Add("-Xmn" + Math.Floor(totalRamMb * 0.15).ToString(CultureInfo.InvariantCulture) + "m"); dataList.Add("-Xmx" + maxHeapArg + "m"); - // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 // 若 dataList 中已存在 -Xms(例如用户自定义参数已设)则跳过,避免重复/冲突。 - if (Config.Launch.LockMemory && !dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) - dataList.Add("-Xms" + maxHeapArg + "m"); + if (!dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) + { + if (Config.Launch.LockMemory) + { + // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 + dataList.Add("-Xms" + maxHeapArg + "m"); + } + else + { + // #3511: 支持自定义初始堆大小(-Xms),可低于最大堆。 + var initialRam = PageInstanceSetup.GetInitialRam(ModInstanceList.McMcInstanceSelected); + if (initialRam.HasValue) + dataList.Add("-Xms" + Math.Floor(initialRam.Value * 1024d).ToString(CultureInfo.InvariantCulture) + "m"); + } + } if (!dataList.Any(d => d.Contains("-Dlog4j2.formatMsgNoLookups=true"))) dataList.Add("-Dlog4j2.formatMsgNoLookups=true"); } @@ -1467,10 +1479,23 @@ private static string McLaunchArgumentsJvmOld(McInstance instance) var maxHeapArg = Math.Floor(PageInstanceSetup.GetRam(ModInstanceList.McMcInstanceSelected, !mcLaunchJavaSelected.Installation.Is64Bit) * 1024d); dataList.Add("-Xmx" + maxHeapArg + "m"); - // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 // 若 dataList 中已存在 -Xms(例如用户自定义参数已设)则跳过,避免重复/冲突。 - if (Config.Launch.LockMemory && !dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) - dataList.Add("-Xms" + maxHeapArg + "m"); + if (!dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) + { + if (Config.Launch.LockMemory) + { + // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 + dataList.Add("-Xms" + maxHeapArg + "m"); + } + else + { + // #3511: 支持自定义初始堆大小(-Xms),可低于最大堆。 + var initialRam = PageInstanceSetup.GetInitialRam(ModInstanceList.McMcInstanceSelected, + !mcLaunchJavaSelected.Installation.Is64Bit); + if (initialRam.HasValue) + dataList.Add("-Xms" + Math.Floor(initialRam.Value * 1024d).ToString(CultureInfo.InvariantCulture) + "m"); + } + } dataList.Add("\"-Djava.library.path=" + GetNativesFolder() + "\""); dataList.Add("-cp ${classpath}"); // 把支持库添加进启动参数表 diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml index 700b96a6f..0ccc38ec5 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml @@ -106,7 +106,8 @@ - + + @@ -124,19 +125,26 @@ Width="110" x:Name="RadioRamType0" Tag="VersionRamType/0" - Margin="0,0,20,0" + Margin="0,0,20,9" ToolTip="{DynamicResource Setup.Launch.Memory.Auto.ToolTip}" ToolTipService.Placement="Right" ToolTipService.HorizontalOffset="-10" ToolTipService.VerticalOffset="-3" /> + Margin="0,0,20,9" /> + + + Margin="0,0,12,9" /> + + + + diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.cs b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.cs index 8eca2e2f1..3272ae41e 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.cs @@ -34,6 +34,7 @@ public PageInstanceSetup() RadioRamType0.Check += RadioBoxChange; RadioRamType1.Check += RadioBoxChange; SliderRamCustom.Change += SliderChange; + SliderRamInitialCustom.Change += SliderChange; ComboServerLoginRequire.SelectionChanged += ComboServerLogin_Changed; TextServerAuthServer.TextChanged += TextBoxChange; @@ -107,6 +108,7 @@ public void Reload() var ramType = Config.Instance.MemorySolution[PageInstanceLeft.McInstance.PathInstance]; ((MyRadioBox)FindName("RadioRamType" + ramType)).Checked = true; SliderRamCustom.Value = Config.Instance.CustomMemorySize[PageInstanceLeft.McInstance.PathInstance]; + SliderRamInitialCustom.Value = Config.Instance.CustomInitialMemorySize[PageInstanceLeft.McInstance.PathInstance]; RamType(ramType); // 服务器 @@ -248,6 +250,8 @@ public void RamType(int type) if (SliderRamCustom is null) return; SliderRamCustom.IsEnabled = type == 1; + // 锁定内存为全局配置,启用时 -Xms 恒等于 -Xmx,初始内存设置不生效 + SliderRamInitialCustom.IsEnabled = type == 1 && !Config.Launch.LockMemory; } /// @@ -277,7 +281,17 @@ public void RefreshRam(bool showAnim) else SliderRamCustom.MaxValue = (int)Math.Round(Math.Floor((ramTotal - 16d) / 2d) + 33d); // 设置文本 - LabRamGame.Text = $"{Lang.Number(ramGame, "N1")} GiB{(ramGame != ramGameActual ? $" ({Lang.Text("Setup.Launch.Memory.AvailableSuffix", Lang.Number(ramGameActual, "N1"))})" : "")}"; + var ramInitial = GetInitialRam(PageInstanceLeft.McInstance); + string suffixText; + if (ramInitial.HasValue && ramGame != ramGameActual) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixBoth", Lang.Number(ramInitial.Value, "N1"), Lang.Number(ramGameActual, "N1")); + else if (ramInitial.HasValue) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixInitial", Lang.Number(ramInitial.Value, "N1")); + else if (ramGame != ramGameActual) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixAvailable", Lang.Number(ramGameActual, "N1")); + else + suffixText = ""; + LabRamGame.Text = $"{Lang.Number(ramGame, "N1")} GiB{(suffixText.Length > 0 ? $" ({suffixText})" : "")}"; LabRamUsed.Text = $"{Lang.Number(ramUsed, "N1")} GiB"; LabRamTotal.Text = $" / {Lang.Number(ramTotal, "N1")} GiB"; LabRamWarn.Visibility = @@ -524,14 +538,7 @@ public static double GetRam(McInstance version, bool? is32BitJava = default) { // 手动配置 var value = Config.Instance.CustomMemorySize[instancePath]; - if (value <= 12) - ramGive = value * 0.1d + 0.3d; - else if (value <= 25) - ramGive = (value - 12) * 0.5d + 1.5d; - else if (value <= 33) - ramGive = (value - 25) * 1 + 8; - else - ramGive = (value - 33) * 2 + 16; + ramGive = PageSetupLaunch.GetRamFromTick(value); } // 若使用 32 位 Java,则限制为 1G @@ -540,6 +547,32 @@ public static double GetRam(McInstance version, bool? is32BitJava = default) return ramGive; } + /// + /// 获取当前设置的初始堆大小(-Xms)。单位为 GB;未启用自定义初始大小时返回 null。 + /// + public static double? GetInitialRam(McInstance version, bool? is32BitJava = default) + { + // 锁定内存为全局配置,启用时 -Xms 恒等于 -Xmx,由锁定内存逻辑处理 + if (Config.Launch.LockMemory) + return null; + // 自定义 JVM 参数含 -Xms 时由其决定实际初始堆,不再显示滑块值 + if (PageSetupLaunch.HasCustomXms(version)) + return null; + var instancePath = version?.PathInstance; + switch (Config.Instance.MemorySolution[instancePath]) + { + case 2: + return PageSetupLaunch.GetInitialRam(version, true, is32BitJava); + case 1: + var initialTick = Config.Instance.CustomInitialMemorySize[instancePath]; + if (initialTick <= 0) + return null; + return Math.Min(PageSetupLaunch.GetRamFromTick(initialTick), GetRam(version, is32BitJava)); + default: + return null; + } + } + #endregion #region 服务器 diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index 12ad78264..a947f20df 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -177,18 +177,25 @@ - - + + - + + + Tag="LaunchRamCustom" MaxValue="49" Value="13" Change="SliderChange" Margin="0,0,12,9" /> + + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs index 56f4c7b06..f674fe55a 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs @@ -66,6 +66,7 @@ public void Reload() // 游戏内存 ((MyRadioBox)FindName("RadioRamType" + Config.Launch.MemoryAllocationMode)).Checked = true; SliderRamCustom.Value = Config.Launch.CustomMemorySize; + SliderRamInitialCustom.Value = Config.Launch.CustomInitialMemorySize; RamType(Config.Launch.MemoryAllocationMode); // 高级设置 @@ -179,6 +180,9 @@ private void CheckBoxChange(object senderRaw, bool user) var sender = (MyCheckBox)senderRaw; if (ModAnimation.AniControlEnabled == 0) SetByTag(sender.Tag?.ToString(), sender.Checked); + // 锁定内存状态变化时同步刷新初始内存滑块的可用状态 + if (ReferenceEquals(sender, CheckAdvanceLockMemory)) + RamType(Config.Launch.MemoryAllocationMode); } private static void SetByTag(string tag, object value) @@ -211,6 +215,8 @@ public void RamType(int type) if (SliderRamCustom is null) return; SliderRamCustom.IsEnabled = type == 1; + // 锁定内存时 -Xms 恒等于 -Xmx,初始内存设置不生效 + SliderRamInitialCustom.IsEnabled = type == 1 && !Config.Launch.LockMemory; } /// @@ -239,7 +245,17 @@ public void RefreshRam(bool showAnim) else SliderRamCustom.MaxValue = (int)Math.Round(Math.Floor((ramTotal - 16d) / 2d) + 33d); // 设置文本 - LabRamGame.Text = $"{Lang.Number(ramGame, "N1")} GiB{(ramGame != ramGameActual ? $" ({Lang.Text("Setup.Launch.Memory.AvailableSuffix", Lang.Number(ramGameActual, "N1"))})" : "")}"; + var ramInitial = GetInitialRam(ModInstanceList.McMcInstanceSelected, false); + string suffixText; + if (ramInitial.HasValue && ramGame != ramGameActual) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixBoth", Lang.Number(ramInitial.Value, "N1"), Lang.Number(ramGameActual, "N1")); + else if (ramInitial.HasValue) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixInitial", Lang.Number(ramInitial.Value, "N1")); + else if (ramGame != ramGameActual) + suffixText = Lang.Text("Setup.Launch.Memory.SuffixAvailable", Lang.Number(ramGameActual, "N1")); + else + suffixText = ""; + LabRamGame.Text = $"{Lang.Number(ramGame, "N1")} GiB{(suffixText.Length > 0 ? $" ({suffixText})" : "")}"; LabRamUsed.Text = $"{Lang.Number(ramUsed, "N1")} GiB"; LabRamTotal.Text = $" / {Lang.Number(ramTotal, "N1")} GiB"; LabRamWarn.Visibility = @@ -398,6 +414,53 @@ private void RefreshRamText() ramTextRight = right; } + /// + /// 将内存滑块刻度值转换为 GB。 + /// + public static double GetRamFromTick(int value) + { + return value switch + { + <= 12 => value * 0.1d + 0.3d, + <= 25 => (value - 12) * 0.5d + 1.5d, + <= 33 => (value - 25) * 1 + 8, + _ => (value - 33) * 2 + 16 + }; + } + + /// + /// 判断生效的自定义 JVM 参数中是否已包含 -Xms。 + /// 此时启动路径会跳过自动追加的初始堆,实际值以自定义参数为准。 + /// + public static bool HasCustomXms(McInstance version) + { + // 与启动路径一致:实例级参数非空时完全忽略全局参数,仅在为空时跟随全局 + var dataJvmCustom = version is null ? "" : Config.Instance.JvmArgs[version.PathInstance]; + var effectiveArgs = string.IsNullOrEmpty(dataJvmCustom) ? Config.Launch.JvmArgs : dataJvmCustom; + return !string.IsNullOrEmpty(effectiveArgs) && + effectiveArgs.Contains("-Xms", StringComparison.OrdinalIgnoreCase); + } + + /// + /// 获取全局设置的初始堆大小(-Xms)。单位为 GB;未启用自定义初始大小时返回 null。 + /// + public static double? GetInitialRam(McInstance version, bool useVersionJavaSetup, bool? is32BitJava = default) + { + // 锁定内存时 -Xms 恒等于 -Xmx,由锁定内存逻辑处理 + if (Config.Launch.LockMemory) + return null; + // 自定义 JVM 参数含 -Xms 时由其决定实际初始堆,不再显示滑块值 + if (HasCustomXms(version)) + return null; + // 仅手动分配模式下生效 + if (Config.Launch.MemoryAllocationMode != 1) + return null; + var initialTick = Config.Launch.CustomInitialMemorySize; + if (initialTick <= 0) + return null; + return Math.Min(GetRamFromTick(initialTick), GetRam(version, useVersionJavaSetup, is32BitJava)); + } + /// /// 获取当前设置的 RAM 值。单位为 GB。 /// @@ -468,14 +531,7 @@ public static double GetRam(McInstance version, bool useVersionJavaSetup, bool? else { // 手动配置 - var value = Config.Launch.CustomMemorySize; - ramGive = value switch - { - <= 12 => value * 0.1d + 0.3d, - <= 25 => (value - 12) * 0.5d + 1.5d, - <= 33 => (value - 25) * 1 + 8, - _ => (value - 33) * 2 + 16 - }; + ramGive = GetRamFromTick(Config.Launch.CustomMemorySize); } // 若使用 32 位 Java,则限制为 1G