From 2982852b2e1e4a67e513d361d3229fa36b07f4a1 Mon Sep 17 00:00:00 2001 From: artdeell Date: Sat, 18 May 2024 19:54:30 +0300 Subject: [PATCH] Feat[launcher]: check whether user is running out of addressable memory for Java --- .../net/kdt/pojavlaunch/Architecture.java | 16 +++++++++ .../main/java/net/kdt/pojavlaunch/Tools.java | 32 ++++++++++++++++- .../pojavlaunch/memory/MemoryHoleFinder.java | 22 ++++++++++++ .../pojavlaunch/memory/SelfMapsParser.java | 35 +++++++++++++++++++ .../src/main/res/values/strings.xml | 1 + 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java index 6189b2872b..8716525d47 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java @@ -12,6 +12,22 @@ public class Architecture { public static final int ARCH_X86 = 0x4; public static final int ARCH_X86_64 = 0x8; + /* On both 32-bit ARM and x86, the top 1GB is reserved for kernel use. */ + public static final long ADDRESS_SPACE_LIMIT_32_BIT = 0xbfffffffL; + /* + * Technically, this is supposed to be 48 bits on x86_64, but nobody's allocating + * 524288 terabytes of RAM on Pojav any time soon. + */ + public static final long ADDRESS_SPACE_LIMIT_64_BIT = 0x7fffffffffL; + + /** + * Get the highest byte accessible within the process's address space. + * @return the highest byte accessible within the process's address space. + */ + public static long getAddressSpaceLimit() { + return is64BitsDevice() ? ADDRESS_SPACE_LIMIT_64_BIT : ADDRESS_SPACE_LIMIT_32_BIT; + } + /** * Tell us if the device supports 64 bits architecture * @return If the device supports 64 bits architecture diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index 00be246e88..47da185a71 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -50,6 +50,8 @@ import net.kdt.pojavlaunch.lifecycle.ContextExecutor; import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask; import net.kdt.pojavlaunch.lifecycle.LifecycleAwareAlertDialog; +import net.kdt.pojavlaunch.memory.MemoryHoleFinder; +import net.kdt.pojavlaunch.memory.SelfMapsParser; import net.kdt.pojavlaunch.multirt.MultiRTUtils; import net.kdt.pojavlaunch.multirt.Runtime; import net.kdt.pojavlaunch.plugins.FFmpegPlugin; @@ -170,9 +172,20 @@ public static void initContextConstants(Context ctx){ public static void launchMinecraft(final AppCompatActivity activity, MinecraftAccount minecraftAccount, MinecraftProfile minecraftProfile, String versionId, int versionJavaRequirement) throws Throwable { int freeDeviceMemory = getFreeDeviceMemory(activity); + int freeAddressSpace = getMaxContinousAddressSpaceSize(); + int localeString; + Log.i("MemStat", "Free RAM: "+freeDeviceMemory+" Addressable: "+freeAddressSpace); + if(freeDeviceMemory > freeAddressSpace && freeAddressSpace != -1) { + freeDeviceMemory = freeAddressSpace; + localeString = R.string.address_memory_warning_msg; + } else { + localeString = R.string.memory_warning_msg; + } + if(LauncherPreferences.PREF_RAM_ALLOCATION > freeDeviceMemory) { + int finalDeviceMemory = freeDeviceMemory; LifecycleAwareAlertDialog.DialogCreator dialogCreator = (dialog, builder) -> - builder.setMessage(activity.getString(R.string.memory_warning_msg, freeDeviceMemory, LauncherPreferences.PREF_RAM_ALLOCATION)) + builder.setMessage(activity.getString(localeString, finalDeviceMemory, LauncherPreferences.PREF_RAM_ALLOCATION)) .setPositiveButton(android.R.string.ok, (d, w)->{}); if(LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator)) { @@ -923,6 +936,23 @@ public static int getFreeDeviceMemory(Context ctx){ return (int) (memInfo.availMem / 1048576L); } + private static int getMaxContinuousAddressSpaceSize0() throws Exception{ + MemoryHoleFinder memoryHoleFinder = new MemoryHoleFinder(); + new SelfMapsParser(memoryHoleFinder).run(); + long largestHole = memoryHoleFinder.getLargestHole(); + if(largestHole == -1) return -1; + else return (int)(largestHole / 1048576L); + } + + public static int getMaxContinousAddressSpaceSize() { + try { + return getMaxContinuousAddressSpaceSize0(); + }catch (Exception e){ + Log.w("Tools", "Failed to find the largest uninterrupted address space"); + return -1; + } + } + public static int getDisplayFriendlyRes(int displaySideRes, float scaling){ displaySideRes *= scaling; if(displaySideRes % 2 != 0) displaySideRes --; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java new file mode 100644 index 0000000000..283f6651ef --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java @@ -0,0 +1,22 @@ +package net.kdt.pojavlaunch.memory; + +import net.kdt.pojavlaunch.Architecture; + +public class MemoryHoleFinder implements SelfMapsParser.Callback { + private long mPreviousEnd = 0; + private long mLargestHole = -1; + private final long mAddressingLimit = Architecture.getAddressSpaceLimit(); + @Override + public boolean process(long begin, long end, String wholeLine) { + if(begin >= mAddressingLimit) begin = mAddressingLimit; + long holeSize = begin - mPreviousEnd; + if(mLargestHole < holeSize) mLargestHole = holeSize; + if(begin == mAddressingLimit) return false; + mPreviousEnd = end; + return true; + } + + public long getLargestHole() { + return mLargestHole; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java new file mode 100644 index 0000000000..6b8bab341b --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java @@ -0,0 +1,35 @@ +package net.kdt.pojavlaunch.memory; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Scanner; + +public class SelfMapsParser { + private final Callback mCallback; + public SelfMapsParser(Callback callback) { + mCallback = callback; + } + + public void run() throws IOException, NumberFormatException { + try (FileInputStream fileInputStream = new FileInputStream("/proc/self/maps")) { + Scanner scanner = new Scanner(fileInputStream); + while(scanner.hasNextLine()) { + if(!forEachLine(scanner.nextLine())) break; + } + } + } + + private boolean forEachLine(String line) throws NumberFormatException { + int firstSpaceIndex = line.indexOf(' '); + String addresses = line.substring(0, firstSpaceIndex); + String[] addressArray = addresses.split("-"); + if(addressArray.length < 2) return true; + long begin = Long.parseLong(addressArray[0], 16); + long end = Long.parseLong(addressArray[1], 16); + return mCallback.process(begin, end, line); + } + + public interface Callback { + boolean process(long startAddress, long endAddress, String wholeLine); + } +} diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 53d2714404..71073cc343 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -172,6 +172,7 @@ Forward lock Absolute finger tracking The current amount of free RAM (%d) is lower than allocated RAM (%d), which may lead to crashes. Change the allocation if the game crashes. + The current amount of free addressable RAM space (%d) is lower than the allocated RAM (%d), which will lead to crashes. Change the allocation if the game crashes. Memory allocation Controls how much memory is given to Minecraft. Corrupted Java Runtime