# Research: JVM, launcher, OS & driver levers — Onion MC client (NeoForge 1.21.1, Java 21, ~212 mods, Distant Horizons mandatory) ## Summary For a DH-mandatory 200+ mod NeoForge 1.21.1 client, the single biggest platform lever is getting off stock G1 GC (Distant Horizons itself warns about it) onto either Generational ZGC (`-XX:+UseZGC -XX:+ZGenerational` on JDK 21) or Shenandoah, with heap sized to real headroom (5–6 GB on the 16 GB Deck and the 8 GB-free Linux box, 6–8 GB on the 32 GB Windows box) — oversizing costs more than it buys. Most OS/driver levers are small but cheap: `mesa_glthread=true` + `gamemoderun` on the Steam Deck, explicit `__GL_THREADED_OPTIMIZATIONS=0` on Linux NVIDIA (Sodium already sets it in source), and NVCP "Prefer maximum performance" on Windows. GraalVM is a benchmark-first experiment only: GraalVM for JDK 21 does not support Generational ZGC and real-world NeoForge 1.21.1 miscompiles exist. --- ## Findings ### 1. The GC baseline: G1 + why DH complains about it - **NeoForge 1.21.1 requires Java 21**, so GC options are: G1 (default), Parallel (default for server-class? no—G1 default since JDK 9), Generational ZGC, Shenandoah. [NeoForge docs](https://docs.neoforged.net/) (DOCUMENTED). - Vanilla ships its own G1 tuning in the client JSON: `-XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M`. Widely quoted from Mojang's version JSON. [Minecraft Forum](https://www.minecraftforum.net/forums/support/java-edition-support/3082919-jvm-arguments-using-g1gc-for-minecraft) (COMMUNITY, corroborated across 2 threads). - **Distant Horizons logs at startup**: `[Render thread/WARN]: Distant Horizons: G1 Garbage collector detected. This can cause FPS stuttering. It's recommended to use a concurrent garbage collector like ZGC (Java 21+) or Shenandoah (Java 8 through 17) for a smoother experience.` DH is closed-source; exact wording verified from two independent log excerpts. [Minecraft Forum log](https://www.minecraftforum.net/forums/support/java-edition-support/3228579-modpack-randomly-losing-fps-and-sometimes-crashing), [New Eden modpack page quoting the warning](https://modrinth.com/project/7jwudrLZ) (DOCUMENTED — it is DH's own runtime warning). Newer DH writes it to the log only instead of an on-screen alert [Reddit r/ModdedMinecraft](https://www.reddit.com/r/ModdedMinecraft/comments/1pnk5uc/getting_this_message_every_time_i_launch_my_game/) (COMMUNITY). Mechanism is consistent with DH's large LOD allocations → G1 old-gen/mixed pauses = visible stutters (INFERENCE from the warning text + DH's memory behavior). - Practical note: on Java 21, `-XX:+UseZGC` **alone** selects the *legacy non-generational* ZGC. Generational ZGC (what you want, and what "Java 21+" implies) needs `-XX:+UseZGC -XX:+ZGenerational`. Generational mode is only the *default* on JDK 23+ (JEP 474); non-gen was removed in JDK 24 (JEP 490). [OpenJDK ZGC wiki](https://wiki.openjdk.org/display/zgc/Main), [JEP 474](https://openjdk.org/jeps/474) (VERIFIED). ### 2. G1 tuned (Aikar-style) vs client - Aikar's flags were tuned for Paper *servers* with large heaps (`MaxGCPauseMillis=200`, `G1NewSizePercent=30`…). [aikar.co via brucethemoose Benchmark.py](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks/blob/main/Benchmark.py) (DOCUMENTED origin, COMMUNITY applicability). - brucethemoose (benchmarked repo, most credible MC flags source): Aikar's high `G1NewSizePercent` effectively clamps `MaxGCPauseMillis`, producing long stutters on some *clients*; clients also fill old gen far slower than a busy server. He ships a client-flavored G1 set: `-XX:+UseG1GC -XX:MaxGCPauseMillis=37 -XX:G1HeapRegionSize=16M -XX:G1NewSizePercent=23 -XX:G1ReservePercent=20 -XX:G1MixedGCCountTarget=3 -XX:G1HeapWastePercent=20 -XX:InitiatingHeapOccupancyPercent=10 -XX:G1RSetUpdatingPauseTimePercent=0 -XX:MaxTenuringThreshold=1 -XX:GCTimeRatio=99 …` [README](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks) (DOCUMENTED, benchmarked; **Java-17-era repo — revalidate on 21**). - Expectation: tuned G1 remains the best default on **4C/8T CPUs with tight RAM** (P0, P2). It has no concurrent-cycle CPU tax like ZGC/Shenandoah. (INFERENCE from brucethemoose's guidance "G1GC on servers/clients with less RAM and fewer cores".) ### 3. Generational ZGC on JDK 21 - Flags for this pack on JDK 21: `-XX:+UseZGC -XX:+ZGenerational -XmxG` (+ optionally `-XX:SoftMaxHeapSize`, `-XX:ConcGCThreads`). [OpenJDK ZGC wiki](https://wiki.openjdk.org/display/zgc/Main) (VERIFIED). - Sizing rule from the ZGC docs: heap must hold live-set **plus headroom for allocations while a cycle runs**; "in general, the more memory you give to ZGC the better", balanced against waste; `SoftMaxHeapSize` lets you cap heuristic target below Xmx. This makes ZGC a poor fit for P2 (only ~8 GB free RAM total) and marginal on the Deck's shared 16 GB. (VERIFIED docs + INFERENCE for fleet.) - ZGC pauses are <1 ms regardless of heap, but it costs throughput: brucethemoose measured "a significant client FPS hit" from ZGC on an 8C/16T laptop, and notes ZGC wants *more* RAM and a raised `ConcGCThreads` cap (default = logical cores/4; he suggests 2 on 4-thread CPUs). [README](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks) (DOCUMENTED/COMMUNITY benchmark). - DH's warning explicitly blesses ZGC as the primary fix; the DH community answer is always `-XX:+UseZGC` on Java 21 [r/MinecraftHelp NeoForge 1.21.1 thread](https://www.reddit.com/r/MinecraftHelp/comments/1svzilf/java_neoforge_1211_distant_horizons_g1_garbage/) (COMMUNITY). - **GraalVM for JDK 21 does not support Generational ZGC** ("The following JDK 21 features are not available: JEP 439 Generational ZGC"). So GenZGC ⇒ a plain OpenJDK distro (Temurin et al.), not GraalVM 21. [GraalVM JDK 21 release notes](https://www.graalvm.org/release-notes/JDK_21/) (VERIFIED). ### 4. Shenandoah - Not in Oracle JDK; available in Temurin/OpenJDK-based builds "from version 11 onward". [Red Hat Temurin docs](https://docs.redhat.com/en/documentation/red_hat_build_of_openjdk/21/html-single/getting_started_with_eclipse_temurin/index); not every build ships it — verify with `java -XX:+UseShenandoahGC -version` [Stack Overflow](https://stackoverflow.com/questions/73344814/openjdk-17-cant-use-shenandoahgc) (DOCUMENTED + caveat). - brucethemoose's benchmarked verdict: **"I recommend Shenandoah on clients"**; flags `-XX:+UseShenandoahGC -XX:ShenandoahGCMode=iu -XX:ShenandoahGuaranteedGCInterval=1000000 -XX:AllocatePrefetchStyle=1`; it dislikes `AllocatePrefetchStyle=3`; mode/heuristic knobs changed little in his tests. [README](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks) (DOCUMENTED). - Like ZGC it is concurrent, so it eats background CPU on 4C/8T — cheap stutter fix, small FPS tax. (INFERENCE.) ### 5. Heap sizing for a 200+ mod pack, and the cost of oversizing - Prism's own wiki: "MORE RAM ALLOCATED DOESN'T MEAN BETTER PERFORMANCE! … a huge heap makes each garbage collection pause longer, which shows up in game as a stutter." [Prism Java settings wiki](https://prismlauncher.org/wiki/help-pages/java-settings/) (DOCUMENTED). - brucethemoose: "Allocating too much memory can break gc or just slow Minecraft down, even if you have plenty to spare… allocate only as much as it needs (usually less than 8G)"; set `-Xms` = `-Xmx` **except** on low-RAM systems where Xms should stay below Xmx. [README](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks) (DOCUMENTED). - For this pack: DH's LOD cache, Alex's Caves/Mowzie/Geckolib models and 212 mods plausibly put live-set near 3–5 GB with LODs loaded (INFERENCE — must be measured with Spark/F3). Recommended: **P0 5–6 GB, P1 6–8 GB, P2 5 GB max** (P2 has ~8 GB free for the whole OS+game). Oversizing harms: longer/less frequent G1 pauses, more RSS stolen from OS/page cache (fatal on 8 GB), ZGC cycles sweeping more memory. (INFERENCE, consistent with both sources above.) ### 6. `-XX:+AlwaysPreTouch` and Transparent Huge Pages (Linux) - ZGC docs: if low latency is the goal, run Xms=Xmx and use `-XX:+AlwaysPreTouch` to page memory in **before** the app starts, avoiding commit/uncommit latency; uncommit can hurt latency. (VERIFIED: [ZGC wiki](https://wiki.openjdk.org/display/zgc/Main).) - THP: `-XX:+UseLargePages -XX:+UseTransparentHugePages` issues `madvise(MADV_HUGEPAGE)`; requires kernel THP set to `madvise` in `/sys/kernel/mm/transparent_hugepage/enabled` (and, **for ZGC only**, `shmem_enabled=advise`). Some distros force `always`/`never` — check sysfs before benchmarking. (VERIFIED: [ZGC wiki](https://wiki.openjdk.org/display/zgc/Main).) - brucethemoose: large pages "improve performance of Minecraft servers and clients"; on Windows they silently fail without admin/SeLockMemoryPrivilege — on Linux prefer `UseTransparentHugePages`. (DOCUMENTED: same README.) - Cost: slower startup (pre-touch of full heap) and, with THP, potential memory bloat — risky on the 8 GB P2. Use on P1/P0, skip or test carefully on P2. (INFERENCE.) ### 7. GraalVM CE/EE (Oracle GraalVM) vs Temurin - Since the "GraalVM for JDK 21" rebrand there is GraalVM Community Edition (free) and Oracle GraalVM; CE for JDK 21 exists as a full distribution. [GraalVM JDK 21 notes](https://www.graalvm.org/release-notes/JDK_21/) (VERIFIED). Note brucethemoose's README is older (recommends "EE 22.x", Java 17) — treat his EE claims as dated but directionally useful. - Claims: client FPS gains are modest; chunk-gen/server workloads up to +20% on GraalVM EE. (DOCUMENTED-era 17: [README](https://github.com/brucethemoose/Minecraft-Performance-Flags-Benchmarks).) - Constraints: **must use G1 with his Graal flags; GraalVM (of his era) doesn't work with ZGC/Shenandoah** — and GraalVM 21 verifiably lacks GenZGC (see §3). (DOCUMENTED both.) - Compatibility risk is real on 1.21.1: a dedicated fix repo exists because "Integrated Scripting 1.0.22" crashes on GraalVM JDK 21 + NeoForge 1.21.1 [jan-guenter/integratedscripting-graalfix](https://github.com/jan-guenter/integratedscripting-graalfix); players report packs failing on GraalVM Java 21 [r/feedthebeast](https://www.reddit.com/r/feedthebeast/comments/18hta1r/how_i_about_doubled_my_minecraft_performance/) (COMMUNITY). With Flywheel 1.0.6 + Veil/Lodestone + Sable/Aeronautics JIT-heavy stacks in this pack, GraalVM is a **trial**, never the fleet baseline. (INFERENCE.) - Obydux's "Minecraft-GraalVM-Flags" is **server startup** flags for GraalVM EE Java 17 — not applicable to a 1.21.1 client; dropped. [Obydux repo](https://github.com/Obydux/Minecraft-GraalVM-Flags). ### 8. Prism Launcher settings (applies to P0 flatpak and P2) - **Memory**: per-instance Settings → Java → memory box (Xms/Xmx). Prism warns against over-allocation (see §5). [Java settings wiki](https://prismlauncher.org/wiki/help-pages/java-settings/) (DOCUMENTED). - **Java path / JVM args**: same Java page — set the Temurin/GraalVM `bin/java` path and paste JVM arguments. (DOCUMENTED: same page.) - **Wrapper command**: "Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)" — this is where `gamemoderun` goes (Settings → Custom Commands). [Custom commands wiki](https://prismlauncher.org/wiki/help-pages/custom-commands/) (DOCUMENTED). - **Environment variables**: Prism passes env vars to the java runtime (instance-level). [Env vars wiki](https://prismlauncher.org/wiki/help-pages/environment-variables/) (DOCUMENTED). Use for `mesa_glthread=true`, `__GL_THREADED_OPTIMIZATIONS=0`, `vblank_mode=0` etc. - Flatpak Prism on the Deck: official install is via their own flatpak repo [Prism Steam Deck download](https://prismlauncher.org/download/steam-deck/); gamemode from inside a flatpak goes through the xdg-desktop-portal GameMode interface (proxies to Feral's daemon with PID mapping/permission checks) [portal docs](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html); some flatpaks need `flatpak override --talk-name=com.feralinteractive.GameMode` for direct D-Bus access [example](https://github.com/an-anime-team/flatpak-builds) (DOCUMENTED + COMMUNITY). ### 9. Linux driver/OS levers - **mesa_glthread**: moves GL dispatch to a second thread inside Mesa. radeonsi (which is what Minecraft's OpenGL actually uses on the Deck's Van Gogh APU — RADV is Vulkan and irrelevant here) enables glthread **by default since Mesa 22.3**; Mesa has also shipped a driconf default for the *official Minecraft launcher* app since 22.0 — a Prism-launched `java` process should not be assumed to match those overrides, so set `mesa_glthread=true` explicitly. [Phoronix](https://www.phoronix.com/news/Mesa-22.3-RadeonSI-glthread-On), [PCGamingWiki](https://www.pcgamingwiki.com/wiki/Mesa), [MangoHud issue #676](https://github.com/flightlessmango/MangoHud/issues/676) (VERIFIED driver behavior; explicit-set recommendation INFERENCE). Glthread is "a big help to CPU-bound games" — i.e., exactly the Deck's 4-core Zen2 profile. (Phoronix, VERIFIED quote.) - **NVIDIA proprietary**: Sodium 0.8.x/0.6.x **sets `__GL_THREADED_OPTIMIZATIONS=0` itself** on Linux right before GLFW window creation (`Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0")` in `NvidiaWorkarounds`), because threaded optimization causes stalls with Sodium. [NvidiaWorkarounds.java (1.21.1/stable)](https://github.com/CaffeineMC/sodium/blob/1.21.1/stable/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java) (VERIFIED). Setting it also in Prism env vars is harmless belt-and-braces. - **gamemode**: `gamemoderun` as Prism wrapper; requests CPU governor + GPU perf level. [Feral GameMode](https://github.com/FeralInteractive/gamemode) / Prism wiki (DOCUMENTED, see §8). - Optional: run java at raised priority (`renice` post-launch; negative nice needs root) — brucethemoose. (DOCUMENTED: README.) ### 10. Windows (P1) NVIDIA control panel - **Threaded optimization is effectively not user-controllable for Minecraft**: Sodium's author (jellysquid3): the NVIDIA driver "forcefully enables Threaded Optimizations regardless of the control panel settings" because it hardcodes Minecraft detection for all `javaw.exe` processes. [sodium#2061](https://github.com/CaffeineMC/sodium/issues/2061) (VERIFIED, maintainer statement). - What Sodium does on Windows instead: spoofs the process command line to `net.caffeinemc.sodium / net.minecraft.client.main.Main /` (keeps the driver's "good" Minecraft heuristics), then enables `GL_DEBUG_OUTPUT_SYNCHRONOUS` (KHR_debug) at context creation, which forces the driver to disable threaded command submission. It also detects the broken driver range x.y.15.2647–3622 (526.47 → before 536.23). [NvidiaWorkarounds.java](https://github.com/CaffeineMC/sodium/blob/1.21.1/stable/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java), [sodium#1486 referenced in source](https://github.com/CaffeineMC/sodium/issues/1486) (VERIFIED). - NVCP **Power management mode → Prefer maximum performance** remains a legitimate per-app setting for stabilizing clocks; community standard advice for MC. [Minecraft Forum thread on threaded optimization/NVCP](https://www.minecraftforum.net/forums/minecraft-java-edition/discussion/3144703-nvidia-threaded-optimization), [r/feedthebeast NVCP report](https://www.reddit.com/r/feedthebeast/comments/vzkoc0/great_performance_improvement_using_nvidia/) (COMMUNITY). Note P1 is VRAM-bound (1060 6GB): platform levers help pacing, not the VRAM wall (INFERENCE). ### 11. Steam Deck (P0) specifics - Install Prism via its official flatpak repo; add Prism (or `flatpak run org.prismlauncher.PrismLauncher --launch ` desktop entry) as a non-Steam-game so it launches in Game Mode under gamescope (SteamOS's compositor — no extra gamescope config needed at 1280×800 native). [Prism Steam Deck page](https://prismlauncher.org/download/steam-deck/), [RetroDECK wiki example with `--launch`](https://retrodeck.readthedocs.io/en/latest/wiki_experiments/desktop-launch/desktop-launch-prism/), [GamingOnLinux guide](https://www.gamingonlinux.com/2022/10/minecraft-on-steam-deck-guide-with-prism-launcher-and-gamepad-support/) (DOCUMENTED + COMMUNITY). - **UMA frame buffer (VRAM) is 1 GB reserved by default**; BIOS allows 4 GB. With DH + shaderpacks + 212 mods on an 8CU iGPU, raising it to 4 GB is a common stability/smoothness tweak (2560×1600 shared-memory reads dominate on Deck). [Windows Central how-to](https://www.windowscentral.com/gaming/pc-gaming/how-to-increase-vram-on-steam-deck), [PiMyLifeUp](https://pimylifeup.com/steam-deck-increase-vram/) (COMMUNITY). - TDP cap (e.g., 8–10 W) stabilizes clocks → steadier frame pacing on battery; standard Deck practice. (COMMUNITY.) - GC choice on 4C/8T Zen2 with 16 GB *shared* RAM: tuned G1 or Shenandoah; avoid ZGC (concurrent-cycle CPU tax + heap headroom taken from the iGPU's shared pool). (INFERENCE from §2–§5.) ### 12. Sodium's "NO_ERROR_CONTEXT" workaround — what it actually means - Sodium's options include **"Use No-Error Context"** (`sodium.options.use_no_error_context`, tooltip: "Creates the OpenGL context without error checking") — i.e., Sodium requests a `KHR_no_error` GL context by default to shave per-call driver error-checking overhead. (VERIFIED from Sodium's own lang strings: [sodium#3407](https://github.com/CaffeineMC/sodium/issues/3407).) - When the installed driver can't provide that, Sodium logs `Sodium has applied one or more workarounds …: [NVIDIA_THREADED_OPTIMIZATIONS_BROKEN, NO_ERROR_CONTEXT_UNSUPPORTED]` — `NO_ERROR_CONTEXT_UNSUPPORTED` simply means the no-error-context optimization was **disabled** and a normal error-checking context is used; "not necessarily an issue, but it may result in certain features or optimizations being disabled." [sodium#2961 log excerpt](https://github.com/CaffeineMC/sodium/issues/2961) (VERIFIED). - User-facing takeaway: don't try to "fix" this warning; it's informational. Performance impact on supported drivers is small. (INFERENCE.) --- ## Lever table (recommended value per fleet machine) | Lever | Recommended value | P0 Deck (Zen2/16GB shared) | P1 Win i7-7700K/1060 6GB | P2 Linux i7-6700K/1080 Ti, 8GB free | Expected impact | Risk | Confidence | |---|---|---|---|---|---|---|---| | GC engine | P0/P2: Shenandoah (`-XX:+UseShenandoahGC -XX:ShenandoahGCMode=iu -XX:ShenandoahGuaranteedGCInterval=1000000`) or tuned G1; P1: Generational ZGC (`-XX:+UseZGC -XX:+ZGenerational`) | tuned G1 or Shenandoah | GenZGC (32 GB RAM makes headroom free) | tuned G1 or Shenandoah (not ZGC: RAM+CPU poor fit) | **High** (removes DH-flagged G1 stutters) | Low (config-only) | Med-High | | G1 tuning (if G1) | brucethemoose client set: `MaxGCPauseMillis=37, G1NewSizePercent=23, G1HeapRegionSize=16M, G1MixedGCCountTarget=3, G1HeapWastePercent=20, IHOP=10, MaxTenuringThreshold=1` | yes | fallback if GenZGC regresses | yes | **Med** (1% lows) | Low | Med | | Heap size | P0: 5–6 GB · P1: 6–8 GB · P2: 5 GB (Xmx=Xms except P2 low-RAM) | 5–6 GB | 6–8 GB | 5 GB, Xms