Skip to content

Make -Xmx cap the whole heap under Node - #50

Merged
Project516 merged 3 commits into
masterfrom
p516/heap-cap
Nov 5, 2026
Merged

Project516 merged 3 commits into
masterfrom
p516/heap-cap

Conversation

@Project516

Copy link
Copy Markdown
Owner

-Xmx now caps the whole heap under Node. Before, it only capped a single array, so a program that kept many small objects grew until the RSS watchdog killed it with exit 137, where HotSpot throws OutOfMemoryError: Java heap space, which a program can catch.

How it works (src/runtime.ts):

  • With -Xmx set, allocations add an estimate of their size to a counter (arrays by their bytes, objects a fixed 32). Nothing else is tracked per object.
  • Each time the counter passes a step (1/16 of the cap, at least 4 MB), Node's heapUsed + arrayBuffers is compared with the cap, measured from a baseline taken after boot so the JDK's own footprint does not count.
  • When it is over, the VM collects first if gc is exposed (the java-web command exposes it) and measures again; only if still over does that allocation throw OutOfMemoryError("Java heap space"). Without gc, it checks only after the estimate itself passes the cap, to avoid false errors.
  • No -Xmx: no check, as before (the RSS watchdog stays the backstop). The browser keeps the per-array limit.

Cost: none measurable. pnpm test took 297 s against 293 s on master with one more test; javac under -Xmx512m took 11.7 s against 12.1 s.

Tests:

  • test/java/HeapCap.java (-Xmx32m) fills the heap with 256 KB arrays, catches the OutOfMemoryError, drops them and allocates again, matching HotSpot, with and without an exposed gc. Programs with -Xmx now run in their own java-web process in test/programs.test.ts, since the cap is per process.
  • Heap and Javac match HotSpot under -Xmx512m, so no false errors. QuadCurveOOMBug and UnboundValueAfterOOME still pass.
  • pnpm typecheck and pnpm test pass.

Noticed, not fixed:

  • The estimate and the sampled check mean the error can come somewhat after the heap passes the cap, not at the exact allocation HotSpot would fail on.

@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 784aa831-97e1-462e-827d-cdf5f2e58550
Commits

Reviewing files that changed from the base of the PR and between 327705b and ea564b2.

Files selected for processing: 6

  • docs/node.md
  • src/jvm.ts
  • src/node.ts
  • src/runtime.ts
  • test/java/HeapCap.java
  • test/programs.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@project516-review-bot project516-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR makes -Xmx cap the whole heap under Node by tracking estimated allocation sizes (32 bytes per object, byte length for arrays) and periodically checking actual V8 heap usage (heapUsed + arrayBuffers) against the cap, throwing OutOfMemoryError: Java heap space when live use stays over the cap after optional GC. The browser retains the per-array limit. Tests pass and performance impact is negligible.


review-bot, model nvidia/nemotron-3-ultra-550b-a55b:free, verdict approve

Comment thread src/runtime.ts Resolved
Comment thread src/runtime.ts Resolved
Comment thread src/runtime.ts Resolved
Comment thread src/runtime.ts Resolved
Comment thread src/node.ts Resolved

@project516-review-bot project516-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR makes -Xmx cap the total live heap under Node by adding a sampled allocation check that compares process.memoryUsage().heapUsed + arrayBuffers against the cap after a baseline is taken post-boot. A new test HeapCap.java verifies catching and recovering from OutOfMemoryError. However, the module-level explicitMaxHeap flag is set to true when any VM uses -Xmx and never reset, so subsequent VMs created in the same process (via runNode) incorrectly inherit the heap cap check with the last-set MAX_HEAP value.

Not approved: 5 earlier points are still open in the threads.


review-bot, model nvidia/nemotron-3-ultra-550b-a55b:free, verdict comment

Comment thread src/runtime.ts
// Reported by Runtime.maxMemory and the per-array limit. Node adds a sampled
// live-heap check when -Xmx is set; browser allocations keep the per-array cap.
export let MAX_HEAP = 1 << 30;
let explicitMaxHeap = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitMaxHeap is set to true in setMaxHeap (line 383) but never reset to false. If a process creates a VM with -Xmx (setting explicitMaxHeap = true) and later creates a VM without -Xmx, hasMaxHeap() still returns true and nodeVm enables the allocation check with the previous MAX_HEAP value. This violates the documented behavior that "No -Xmx: no check". Reset explicitMaxHeap when setMaxHeap is called without a cap, or add a clearMaxHeap function called when a VM starts without -Xmx.

Comment thread src/node.ts
const toVfs = (p: string) => path.resolve(p);
const classpath = o.jar ? [toVfs(o.jar)] : o.classpath.map(toVfs);
const natives = o.natives ?? {};
if (hasMaxHeap()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because hasMaxHeap() relies on the one-way explicitMaxHeap flag, a VM started without -Xmx after one with -Xmx incorrectly enters this branch and installs an allocation check using the stale MAX_HEAP. The else clearAllocationCheck() branch is never reached for subsequent VMs. This should be fixed together with the explicitMaxHeap reset.

@Project516
Project516 merged commit 468c0f3 into master Nov 5, 2026
16 checks passed
@Project516
Project516 deleted the p516/heap-cap branch November 5, 2026 18:22
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