Home › Java › Could Not Create the JVM: Fix -Xmx and Bad Options
Intermediate 6 min · September 23, 2026

Could Not Create the JVM: Fix -Xmx and Bad Options

Lower -Xmx to fit the machine, drop the removed flag, and unset JAVA_TOOL_OPTIONS.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
✓ Production
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 9 min
  • ✓A JDK installed so you can run java -version
  • ✓Access to the launch script or Dockerfile that sets JVM flags
  • ✓Basic shell skills: env, grep, and reading cgroup files
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Your heap flags ask for the impossible: a typo like -Xmx512mb, a space after -Xmx, or -Xms larger than -Xmx kills the JVM before main.
  • A JDK upgrade removed a flag your config still passes, usually CMS or PermSize — the error names the dead option directly.
  • A leaked JAVA_TOOL_OPTIONS or _JAVA_OPTIONS injects bad flags into every java launch, so even java -version fails.
  • Your -Xmx exceeds real memory: 32-bit ceilings near 4 GB or a Docker limit smaller than the heap — size from the cgroup, not the host.
✦ Definition~90s read
What is Java Could Not Create JVM Fix?

Could not create the Java Virtual Machine is the launcher's admission that the JVM itself couldn't be born — your main method never ran, your classes never loaded, and no application log exists. Before executing anything, the JVM must parse every flag, reserve the heap, pick a garbage collector, and claim native memory from the OS.

★
Think of the JVM as a restaurant that must reserve its tables before opening.

If any step is impossible, initialization aborts and this is the entire obituary, sometimes with a preceding line naming the specific grievance.

The preceding line is the real diagnosis. Invalid maximum heap size points at -Xmx syntax or an -Xms above -Xmx. Unrecognized VM option names a flag the running JDK doesn't know, almost always a casualty of an upgrade. Could not reserve enough space means the heap doesn't fit in available memory or the 32-bit address range.

No line at all, especially in Docker, usually means the cgroup limit vetoed the request silently. Read that first line before touching anything else.

Two injection channels make this error weirder than it looks. JAVA_TOOL_OPTIONS and _JAVA_OPTIONS prepend flags to every launch, so a poisoned variable breaks JVMs whose own configs are perfect — including build tools and the java -version you'd use to investigate.

Container runtimes add the second channel: cgroup ceilings the JVM's fixed -Xmx can't see. Both hide the cause outside the app, which is why code review never finds it.

The fix order follows the birth order: validate flag syntax first with java -version, then confirm each -XX option exists on your JDK, then strip the environment variables, then compare -Xmx against real and container memory. Work outside-in from the launcher toward the app, and you'll land on the cause in minutes instead of reviewing commits that were never involved.

Plain-English First

Think of the JVM as a restaurant that must reserve its tables before opening. You hand it a booking slip: how many tables (-Xmx), which menu (VM flags), and the room size (machine memory). If the slip asks for 500 tables in a 50-table room, names a dish removed from the menu years ago, or contains a coffee stain from someone else's note (a leaked env var), the manager cancels opening entirely. Nobody gets seated — that's this error. Fix the slip, not the recipes.

You change one flag, restart, and instead of your app you get a dead JVM and a single cryptic line. Nothing was deployed. No code changed. Yet the process won't even reach main. This error is the JVM telling you its own birth conditions were impossible — the heap you asked for can't exist, a flag you passed doesn't exist, or the box you're on can't honor the request.

It's disorienting because the failure sits below your application entirely. Your code is never loaded, your logs never open, and your usual debugging tools never get a chance. Engineers burn hours reviewing recent commits when the culprit is a -Xmx typo, a JAVA_TOOL_OPTIONS export they forgot about, or a JDK upgrade that deleted a flag their config still references.

Containers added a fresh trap. A heap sized generously for the host becomes a fantasy inside a Docker limit half its size, and the JVM dies at birth with no flag to blame. You'll swear the configuration is identical to staging while the cgroup ceiling quietly differs.

This guide covers all five causes: invalid -Xmx and -Xms pairs, VM options that died in a JDK upgrade, JAVA_TOOL_OPTIONS leaking across every launch, 32-bit heap ceilings, and container memory limits. You'll get a production incident where one global export broke every JVM on shared CI hosts, a debug guide with exact commands, and a table that maps each error line to its fix.

Invalid -Xmx and -Xms: Typos and Backwards Heaps

Heap flags are validated before anything else, so a typo reads like a dead runtime. -Xmx512mb looks plausible but the JVM only accepts k, m, and g suffixes — mb is nonsense and the process aborts. A space between -Xmx and the number splits one flag into two broken arguments. A lowercase -xmx isn't a flag at all. Each failure prints a slightly different line, but all of them mean the same thing: the request couldn't be parsed, so no heap was ever built.

The subtler killer is an -Xms above -Xmx. Asking for 4 GB initial inside a 1 GB maximum is a contradiction, and the JVM refuses to start rather than guess which number you meant. This often arrives via layered configs: a base script sets -Xmx1g, someone's override adds -Xms4g, and the merged command line is impossible. Neither value is wrong alone — together they're fatal.

Always test flags without your app. java -Xmx512m -Xms256m -version either prints version info (flags valid) or dies (flags invalid), and that verdict takes a second with zero deployment. When it dies, bisect: drop -Xms first, then simplify -Xmx to a plain value like -Xmx1g. The moment version prints, you've isolated the offender.

Lock the working pair into one place — a shared launch script or a Dockerfile ENV — instead of scattering -Xms and -Xmx across profiles, unit files, and CI variables. One source of truth can't contradict itself. Add a CI step that runs java with the production flags; if a future edit breaks the pair, the pipeline catches it instead of your pager.

heap-flags-check.shBASH
1
2
3
4
5
6
7
8
9
10
# Reproduce WITHOUT your app: flags alone decide birth or death
java -Xmx512m -Xms256m -version

# These all die instantly — spot the defect in each:
# java -Xmx 512m -version     # space after -Xmx: invalid
# java -Xmx512mb -version     # bogus 'mb' suffix: use m or g
# java -Xms4g -Xmx1g -version # initial above maximum: swap them

# See what the JVM actually accepted
java -XshowSettings:vm -version 2>&1 | head -20
📊 Production Insight
Layered configs merge into impossible pairs silently — a base -Xmx1g plus an override -Xms4g dies with no single guilty file. CI should validate the merged flags, not each file.
🎯 Key Takeaway
Test heap flags with java -version alone, keep -Xms at or below -Xmx, and store the pair in one place.

Unrecognized VM Options After a JDK Upgrade

Every major JDK retires flags, and your old configs don't get the memo. The CMS collector (-XX:+UseConcMarkSweepGC) was removed in JDK 14, MaxPermSize died with PermGen back in JDK 8, and each removal turns a working launch line into an instant abort on the new runtime. The error names the dead flag plainly, which is generous — but only if you read it instead of blaming your code.

The pain is that the flag often comes from somewhere you didn't write. Build plugins inject collectors, base Docker images set -XX defaults, monitoring agents append their own options, and shared profiles export extras. Your app's own config can be pristine while the merged command line carries a corpse. That's why the same artifact starts on the old image and dies on the new one with zero code changes.

Triage by testing the named flag in isolation: java -XX:+UseConcMarkSweepGC -version. If that dies, you've convicted the flag, not the app. Then grep the carriers — /etc, /opt, dotfiles, Dockerfiles, agent configs — until you find who passes it. Replace CMS with G1GC (the default since JDK 9) or ZGC for latency-sensitive heaps, and validate the replacement the same isolated way.

Make flag audits part of every JDK upgrade runbook. List every -XX option from every layer, run each against the new runtime in CI, and fail the upgrade on the first unrecognized one. Upgrades that validate flags first are boring; upgrades that skip it page you at midnight.

jdk-upgrade-flag-audit.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Test the suspect flag alone on the NEW jdk
java -XX:+UseConcMarkSweepGC -version
# Unrecognized VM option 'UseConcMarkSweepGC' on JDK 14+

# modern replacement, validated the same way
java -XX:+UseG1GC -version

# Hunt every carrier of dead flags across configs and images
grep -rn "UseConcMarkSweepGC\|MaxPermSize\|UseParNewGC" \
  /etc/ /opt/ ~/.bashrc ~/.profile Dockerfile* 2>/dev/null

# Confirm what the running JVM actually is
java -version 2>&1 | head -5
📊 Production Insight
The dead flag usually arrives from a layer you didn't write — base images, agents, plugins. Audit the merged command line on the new runtime before rollout, not after the page.
🎯 Key Takeaway
Test each -XX flag alone on the new JDK, grep every config layer for carriers, and replace CMS with G1GC.

JAVA_TOOL_OPTIONS and _JAVA_OPTIONS Leaking Into Every Launch

JAVA_TOOL_OPTIONS is the JVM's auto-include: its contents are prepended to every java launch on the machine, printed to stderr as Picked up JAVA_TOOL_OPTIONS for honesty. That's handy for one debugging session and catastrophic as a permanent export, because every future JVM — your app, Maven, Gradle, Jenkins agents, even java -version — inherits whatever you left there. One stale flag poisons the whole box identically, which looks like a broken JDK rather than a broken variable.

Its shadowy sibling _JAVA_OPTIONS behaves the same way but prints no Picked up notice, making it harder to spot. Both override with silent confidence: command-line flags still win, but anything the command line doesn't mention comes from the variable. Teams that set these in /etc/profile.d or shared .bashrc files create a trap that springs months later when a JDK upgrade invalidates the injected flag.

Diagnosis is quick. env | grep -i java reveals the carriers; running env -u JAVA_TOOL_OPTIONS -u _JAVA_OPTIONS java -version shows whether the stripped JVM lives. If stripping revives it, don't just unset and move on — find the export's home (grep -rn JAVA_TOOL_OPTIONS /etc/profile.d/ ~/.bashrc) and delete it there, or the next login resurrects the failure.

The durable rule: never export JVM flags machine-wide. Put service flags in the service's own definition — a systemd Environment line, a Dockerfile ENV, a per-app env file. Scoped flags break one service at worst; global flags break everything at once, including the tools you'd use to investigate.

tool-options-leak-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
# What's lurking in the environment?
env | grep -i java
echo "JAVA_TOOL_OPTIONS=$JAVA_TOOL_OPTIONS"
echo "_JAVA_OPTIONS=$_JAVA_OPTIONS"

# Prove the variable is the killer: strip and retry
env -u JAVA_TOOL_OPTIONS -u _JAVA_OPTIONS java -version

# Scope flags to ONE service instead of the whole box
# /etc/systemd/system/shop.service:
# Environment="JAVA_OPTS=-Xmx2g -XX:+UseG1GC"
📊 Production Insight
Global JVM exports turn one bad flag into a fleet-wide outage of every Java tool, including the debugger. Per-service env files contain the blast radius to the service that owns the flags.
🎯 Key Takeaway
Strip the variables to prove guilt, delete the global export at its source, and scope flags per service.

32-Bit Heap Limits: When 4 GB Isn't Available

A 32-bit JVM can only address about 2 to 4 GB total, and the heap must also fit in one contiguous address block — which is smaller than the theoretical max once libraries and native mappings take their slices. Asking for -Xmx4g on a 32-bit runtime fails even on a host with 64 GB free, because the limit is address width, not physical memory. The error reads like starvation on a full box, but the box is nearly empty.

This still bites in production through legacy dependencies: old native libraries, 32-bit-only vendor agents, or base images nobody rebuilt since 2016. The app team sizes -Xmx for the host's RAM while the runtime underneath caps out at a fraction of it. Upgrading the host or adding memory changes nothing — you can't buy address space with RAM.

Confirm quickly with java -version (it says 32-Bit or omits 64-Bit) plus uname -m for the kernel. If the JVM is 32-bit, drop -Xmx under the ceiling — 1536m is the pragmatic safe value — to restore service immediately. Then schedule the actual fix: move to a 64-bit JDK, which raises the ceiling past any heap you'll plausibly configure.

Watch for the hybrid case too: a 64-bit kernel running a 32-bit java binary, common when JAVA_HOME points at an ancient install. The kernel reports x86_64 while the JVM reports 32-Bit, and only the JVM's answer matters. Pin JAVA_HOME to the 64-bit JDK in every launch path so the wrong binary can't sneak back.

bitness-heap-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Bitness decides the ceiling long before RAM matters
java -version 2>&1 | head -5
uname -m
free -m

# A 32-bit JVM rejects heaps like this no matter the host:
# java -Xmx4g -version  # Invalid maximum heap size on 32-bit

# Get running now with a heap under the 32-bit ceiling
java -Xmx1536m -version

# The real fix: install and switch to a 64-bit JDK
# sudo apt install openjdk-17-jdk:amd64 && java -version
📊 Production Insight
Legacy agents pin teams to 32-bit runtimes while host RAM keeps growing, so -Xmx sizing drifts past the ceiling. Assert JVM bitness in CI and cap heaps accordingly until the migration lands.
🎯 Key Takeaway
32-bit JVMs cap heaps near 2-4 GB regardless of host RAM — drop to -Xmx1536m now, move to 64-bit next.

Container Memory Limits That Kill the JVM at Birth

Containers lie about memory by omission. free -m inside Docker often shows the host's RAM, so a -Xmx4g that fits the host looks safe while the container's cgroup limit sits at 1 GB. The JVM tries to reserve its heap, the kernel refuses, and the process dies at birth with no flag to blame — just the creation failure. Staging passes because its limits are looser; production dies because they're tighter.

-Xmx is blind to cgroups by design: it's a fixed byte count that never adapts. That rigidity breaks the moment your pods move between node types or someone tightens a limit to save cost. Every reschedule becomes a gamble between the hardcoded heap and the new ceiling, and the loser gets paged.

-XX:MaxRAMPercentage ends the gamble. It sizes the heap as a fraction of the container limit the JVM detects — 75.0 means three-quarters of the cgroup max — so the heap tracks reschedules automatically. Older JDKs need -XX:+UnlockExperimentalVMOptions plus MaxRAMFraction, but anything from JDK 10 up reads cgroups natively and just works.

Verify from inside the container: cat the cgroup max, compare it against your -Xmx, and read the JVM's own accounting via -XshowSettings:system. If Xmx exceeds the max, either raise the limit or switch to the percentage flag. Never size container heaps from host metrics — the host's gigabytes aren't yours.

container-memory-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# The container ceiling, not the host, is your budget
cat /sys/fs/cgroup/memory.max 2>/dev/null || \
  cat /sys/fs/cgroup/memory/memory.limit_in_bytes
free -m

# See the JVM's own view of the constraint
java -XshowSettings:system -version 2>&1 | head -20

# Heap that tracks the container instead of fighting it
java -XX:MaxRAMPercentage=75.0 -version

# Example: 1 GB container gets ~768 MB heap automatically
📊 Production Insight
Host-based heap sizing is the top container JVM killer: limits tighten silently while -Xmx stays fixed. Derive heaps from cgroup max in the Dockerfile and alert when Xmx exceeds 80 percent of the limit.
🎯 Key Takeaway
Size from the cgroup max with MaxRAMPercentage so the heap tracks the container instead of fighting it.

Reading the Error Line: Which Cause Is Yours

The error line is a tiny decision tree if you read it literally. Invalid maximum heap size or initial heap means flag syntax — test the pair with java -version and fix the suffix or the Xms/Xmx order. Unrecognized VM option plus a flag name means a dead option — test it alone and grep the configs for its carrier. Could not reserve enough space means arithmetic — compare -Xmx against free memory, bitness, and the cgroup max. No detail line at all inside Docker means the container limit — read the cgroup and switch to MaxRAMPercentage.

Resist the urge to tune the application when the JVM won't even form. No GC log, heap dump, or profiler output can exist yet, because there's no heap to profile. The only evidence that matters is the launch: the full java command, the environment, the JDK version, and the machine or container limits. Capture those four before changing anything, and most cases resolve from that snapshot alone.

Reproduce with the smallest possible command. Strip your app away (java -version with the same flags), then strip the environment (env -u the JAVA_* variables), then strip the container (run on the host). Each layer you remove either clears the failure — convicting that layer — or preserves it, narrowing the search. Three bisections beat three hours of config reading.

When it starts again, lock in the lesson: validated flags in one script, scoped env files per service, heaps derived from cgroup limits. Startup failures are the cheapest outages to prevent permanently, because the entire check runs in CI in under a second with java -version.

🔥Birth Failures Aren't App Failures
When the JVM won't form, debugging the app is theater. Validate flags, environment, and memory first — your code can't be guilty before it loads.
📊 Production Insight
Teams that capture the full launch (command, env, JDK, limits) at startup resolve these incidents from the snapshot alone. Log all four on every boot and the next page answers itself.
🎯 Key Takeaway
Read the first error line literally, bisect launch layers with java -version, and lock validated flags into CI.
● Production incidentPOST-MORTEMseverity: high

One Global Export Broke Every JVM on Shared CI Hosts

Symptom
After a routine JDK 11 to 17 rollout on CI hosts, every Java process failed with Unrecognized VM option followed by Could not create the Java Virtual Machine. Builds, test runners, and agents all died identically. Rolling back the JDK masked it, but the next upgrade attempt exploded the same way.
Assumption
Build engineers blamed the new JDK rollout, app teams blamed the new container base image, and both pointed at recent code. Nobody suspected the environment because the flag in the error belonged to no repository anyone could find.
Root cause
A shared /etc/profile.d script exported JAVA_TOOL_OPTIONS=-XX:+UseConcMarkSweepGC -Xmx2g for a legacy monitoring agent. JDK 17 removed the CMS collector, so the flag became unrecognized. Because JAVA_TOOL_OPTIONS prepends to every java launch, all JVMs on the hosts inherited the dead flag and aborted during initialization — before any application code loaded.
Fix
The global export was removed from the shared profile and the monitoring agent's unit file. CMS was replaced with G1GC in the agent's own config, scoped to that service only. A CI check was added that fails the build if JAVA_TOOL_OPTIONS appears in any shared profile or base image.
Key lesson
  • Machine-wide JVM flag exports are a single point of failure for every Java process — scope flags to the service that needs them.
  • When the error names a flag nobody set, search the environment before the codebase: env and profiles first, repositories second.
  • JDK upgrades must include a flag audit: grep every config and image for -XX options and validate each one on the new runtime.
Production debug guideFive birth conditions the JVM checks before main — test each with these commands.5 entries
Symptom · 01
Error mentions heap size, initial heap, or minimum heap
→
Fix
Rerun the exact flags against nothing: java -Xmx512m -Xms256m -version. If that fails, your app is innocent — the flags are guilty. Watch for a space after -Xmx, a lowercase x, or a bogus suffix like mb. Then print the live settings with java -XshowSettings:vm -version 2>&1 | head -20 to see what the JVM actually accepted.
Symptom · 02
Error names an Unrecognized VM option after a JDK upgrade
→
Fix
Copy the named flag and test it alone: java -XX:+UseConcMarkSweepGC -version. If that fails on the new JDK but passed on the old one, the flag was removed. Find every carrier with grep -rn "UseConcMarkSweepGC\|MaxPermSize" /etc/ /opt/ ~/.bashrc ~/.profile Dockerfile* 2>/dev/null, then replace CMS with -XX:+UseG1GC.
Symptom · 03
Every java command fails, including java -version and Maven
→
Fix
Inspect the environment first: env | grep -i java and echo "JAVA_TOOL_OPTIONS=$JAVA_TOOL_OPTIONS". Then prove it: env -u JAVA_TOOL_OPTIONS -u _JAVA_OPTIONS java -version. If the stripped launch works, the variable carried the poison flag — unset it globally and move the needed options into the service's own unit file.
Symptom · 04
Large -Xmx rejected on a host with plenty of RAM
→
Fix
Check the bitness and the room: java -version 2>&1 | head -5, uname -m, and free -m. A 32-bit JVM rejects heaps near or above 2-4 GB no matter the host. Drop to -Xmx1536m to get running, then plan the move to a 64-bit JDK — no flag raises a 32-bit ceiling.
Symptom · 05
Starts on your laptop, dies at birth inside Docker
→
Fix
Read the container ceiling, not the host: cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory/memory.limit_in_bytes. Compare against your -Xmx, and view the JVM's own math with java -XshowSettings:system -version 2>&1 | head -20. If Xmx exceeds the cgroup max, switch to -XX:MaxRAMPercentage=75.0 so the heap tracks the limit.
Could Not Create the JVM Causes Compared
Root CauseHow to ConfirmFixPrevention
Invalid heap syntax or Xms above Xmxjava -version with the same flags fails the same wayFix the suffix and keep -Xms at or below -XmxSmoke-test flags with java -version in CI
VM flag removed by a JDK upgradeThe error names the flag; old JDK starts, new one doesn'tDrop or replace the flag, e.g. CMS with G1GCGrep configs for -XX on every JDK bump
Leaked JAVA_TOOL_OPTIONS or _JAVA_OPTIONSenv shows the variable; even java -version failsUnset it globally, scope flags per serviceNever export JVM flags machine-wide
Heap bigger than the machine or containerNo flag is named; free or cgroup max is below -XmxLower -Xmx or use -XX:MaxRAMPercentage=75.0Size heaps from cgroup limits, not host RAM
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
heap-flags-check.shjava -Xmx512m -Xms256m -versionInvalid -Xmx and -Xms
jdk-upgrade-flag-audit.shjava -XX:+UseConcMarkSweepGC -versionUnrecognized VM Options After a JDK Upgrade
tool-options-leak-check.shenv | grep -i javaJAVA_TOOL_OPTIONS and _JAVA_OPTIONS Leaking Into Every Launc
bitness-heap-check.shjava -version 2>&1 | head -532-Bit Heap Limits
container-memory-check.shcat /sys/fs/cgroup/memory.max 2>/dev/null || \Container Memory Limits That Kill the JVM at Birth

Key takeaways

1
The JVM validates heap and flags before main, so bad values kill the process with no app logs.
2
Keep -Xms at or below -Xmx and test every pair with java -version before deploying.
3
JDK upgrades remove flags
validate each -XX option on the new runtime, don't copy configs blindly.
4
Never export JAVA_TOOL_OPTIONS globally; scope JVM flags to each service.
5
32-bit JVMs cap heaps near 2-4 GB no matter how much RAM the host has.
6
In containers, size from the cgroup limit with MaxRAMPercentage instead of a fixed -Xmx.

Common mistakes to avoid

5 patterns
×

Writing -Xmx with a space or a bogus suffix like mb

Symptom
The JVM dies instantly complaining about the heap size value, even though the number itself would fit fine.
Fix
Remove the space and use a valid suffix: -Xmx512m. Test every flag pair with java -Xmx512m -version before it reaches a start script.
×

Setting -Xms larger than -Xmx

Symptom
The JVM refuses to start with an initial-heap error on a machine with plenty of free memory.
Fix
Keep initial below maximum (-Xms1g -Xmx4g) or drop -Xms and let the JVM size the young heap itself. Encode the pair once in a shared script.
×

Copying -XX flags across a JDK upgrade

Symptom
Everything worked on JDK 11 and nothing starts on JDK 17, with the error naming a flag your app never set directly.
Fix
Audit every -XX flag against the JDK you actually run: java -XX:+UseG1GC -version. Replace removed flags (CMS) with supported ones instead of copying configs forward.
×

Exporting JAVA_TOOL_OPTIONS globally on shared boxes

Symptom
Every java process on the machine fails — app, Maven, even java -version — because one leaked variable poisons them all.
Fix
Unset the global export and set JVM flags per service in its systemd unit or Dockerfile. Confirm with env | grep -i java on every host.
×

Giving the container less memory than -Xmx promises

Symptom
The JVM starts on your laptop but dies at birth inside Docker with no flag complaints at all.
Fix
Size from the container limit, not the host: -XX:MaxRAMPercentage=75.0 lets the JVM read the cgroup. Check cat /sys/fs/cgroup/memory.max first.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why does a bad -Xmx kill the JVM before main runs?
Q02SENIOR
Every java binary on a box fails, including java -version. What do you c...
Q03SENIOR
Nothing starts after a JDK 11 to 17 upgrade. How do you triage it?
Q04SENIOR
A 32-bit JVM rejects -Xmx4g on a 64 GB host. Why?
Q05SENIOR
It starts on your laptop but dies in Docker with identical flags. Explai...
Q01 of 05JUNIOR

Why does a bad -Xmx kill the JVM before main runs?

ANSWER
The JVM validates heap flags before loading anything, so a bad value aborts the process during initialization. Test with java -Xmx512m -version, fix the syntax, and keep -Xms at or below -Xmx. No application code is involved at this stage.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Can I change -Xmx without restarting the JVM?
02
What does Unrecognized option or Invalid maximum heap size mean?
03
Is MaxRAMPercentage better than -Xmx in Docker?
04
How do I prove JAVA_TOOL_OPTIONS is the culprit?
05
Which old flags break startup after a JDK upgrade?
06
The JVM won't start — is my app leaking memory?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
✓ Verified
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
🔥

That's JVM. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Java Could Not Find Main Class Fix
1 / 1 · JVM
Next
Spring Port 8080 Already in Use Fix
→