Cap the auto-sized JVM heap against total device RAM - #65
Open
jfrolund wants to merge 1 commit into
Open
Conversation
The auto path sizes the heap purely from free memory:
long allocatedRam = Math.max(availMem, 1536);
That is a floor with no ceiling, so the more RAM is free at launch the
larger a heap the game claims for the rest of the session. 874e85b
("Fix ram setter") introduced this when it replaced the previous
bounded calculation, which did cap the result.
The relationship is backwards on a headset. The heap is only part of the
process -- the GL driver, XR swapchains, LWJGL and the JRE need roughly
as much again outside it -- and the VR compositor and system services
have to keep running or lmkd kills the game. Measured on a 6GB Quest 2:
launching with ~3.5GB free produced a 2048MB heap and a ~3.9GB process
that was killed with reason=3 (LOW_MEMORY) repeatedly, while the same
device launching with only ~322MB free took the 1536MB floor and
survived. Having more memory free made the game less stable, not more.
Clamp the result to a quarter of total RAM. That scales with the device
rather than hardcoding a number, and it makes the heap stable across
launches instead of varying with whatever else happened to be running.
Also lower the startup commitment so the heap grows only as the game
needs it, and stop pinning -Xms to -Xmx on the custom path, where it
forced the user's entire chosen value to be committed immediately.
Quest 2 goes from unbounded (2048MB+ observed) to 1458MB; Quest 3 to
1950MB; a 12GB Quest Pro to 2925MB.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The auto path sizes the JVM heap purely from free memory:
That is a floor with no ceiling, so the more RAM is free at launch, the larger a heap
the game claims for the rest of the session.
874e85b("Fix ram setter") introducedthis when it replaced the previous bounded calculation, which did cap the result. On
this branch the
*= 0.8discount is gone as well, so nothing tempers the reading at all.The relationship is backwards on a headset. The heap is only part of the process — the
GL driver, XR swapchains, LWJGL and the JRE need roughly as much again outside it — and
the VR compositor and system services have to keep running or
lmkdkills the game.Evidence
Measured on a 6GB Quest 2 (Horizon OS, MC 1.21.5 + Vivecraft):
reason=3 (LOW_MEMORY)killsHaving more memory free made the game less stable, not more. With the heap held to
1456 MB instead, the same device measured 3.12 GB PSS and 754 MB free, with no
LOW_MEMORY kills across ~21 hours and several sessions.
On this branch the same 3.5 GB-free launch would request roughly a 3300 MB heap.
Change
-Xmxto a quarter of total RAM. This scales with the device rather thanhardcoding a number, and makes the heap stable across launches instead of varying
with whatever else happened to be running.
committing 1024 MB in every session.
-Xmsto-Xmxon the custom path, where it forced the user's entirechosen value to be committed immediately.
Resulting caps: Quest 2 → 1458 MB, Quest 3 → ~1950 MB, 12GB Quest Pro → ~2925 MB.
The GC flags are deliberately untouched.
-XX:+UseZGCcarries real native overhead andis a tempting target, but it was presumably chosen for frame-time consistency, which
matters more here than in a desktop game — that is a separate argument needing separate
evidence.
Testing caveat
Compiles against
QuestCraft-6.0.1(JDK 17). The on-device numbers above come frompatching the heap constants in the shipped QuestCraft 6.0.0 APK, whose Pojlib predates
874e85b— so they validate the effect of bounding the heap rather than this exactcode path. I do not have a Unity build environment to produce a full APK from this
branch; happy to adjust if you can run it through the normal pipeline.
🤖 Generated with Claude Code