Skip to content

Cap the auto-sized JVM heap against total device RAM - #65

Open
jfrolund wants to merge 1 commit into
QuestCraftPlusPlus:QuestCraft-6.0.1from
jfrolund:fix/heap-ceiling-601
Open

Cap the auto-sized JVM heap against total device RAM#65
jfrolund wants to merge 1 commit into
QuestCraftPlusPlus:QuestCraft-6.0.1from
jfrolund:fix/heap-ceiling-601

Conversation

@jfrolund

Copy link
Copy Markdown

Problem

The auto path sizes the JVM heap purely from free memory:

long availMem = (ami.availMem-ami.threshold)/(1024*1024);
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. On
this branch the *= 0.8 discount 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 lmkd kills the game.

Evidence

Measured on a 6GB Quest 2 (Horizon OS, MC 1.21.5 + Vivecraft):

Free RAM at launch Heap granted Process PSS Result
~322 MB 1536 MB 3.1 GB survived
~3.5 GB 2048 MB 4.24 GB repeated reason=3 (LOW_MEMORY) kills

Having 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

  • Clamp -Xmx to a quarter of total RAM. This scales with the device rather than
    hardcoding a number, and makes the heap stable across launches instead of varying
    with whatever else happened to be running.
  • Lower the startup commitment so the heap grows only as the game needs it, rather than
    committing 1024 MB in every session.
  • Stop pinning -Xms to -Xmx on the custom path, where it forced the user's entire
    chosen 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:+UseZGC carries real native overhead and
is 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 from
patching 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 exact
code 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

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant