JVM Options Explorer (chriswhocodes.com)

by 0x54MUR41 120 comments 222 points
Read article View on HN

120 comments

[−] molticrystal 33d ago
Chrome has 1496 [0] known options as of today, maybe after a few more pushes they'll catch up to the 1843 of JVM.

An interface like above to sort things would probably be quite helpful as well.

[0] https://peter.sh/experiments/chromium-command-line-switches/

[−] rafaelgoncalves 31d ago
the link throws an error 500, "There has been a critical error on this website.":

archived on 2026-04-12: https://web.archive.org/web/20260412085953/https://peter.sh/...

[−] deepsun 33d ago
Why not compiling it to Java source code (not bytecode)? Users would use their own Java compiler then.

Same as, say, ANTLR generates code to parse various texts to AST.

[−] Hendrikto 33d ago
1843 options is too many. You could never even consider all of the possible combinations and interactions, let alone test them.

I have really come to appreciate modern opinionated tooling like gofmt, that does not come with hundreds to thousands of knobs.

[−] pron 33d ago
These are all the options that have ever existed, including options that are or were available only in debug builds used during development and diagnostic options. There are still a few hundred non-diagnostic "product" flags at any one time, but most are intentionally undocumented (the list is compiled from the source code [1]) and are similar in spirit to compiler/linker configuration flags (only in Java, compilation and linking are done at runtime) and they're mostly concerned with various resource constants. It is very rare for most of them to ever be set manually, but if there's some unusual environment or condition, they can be helpful.

[1]: https://github.com/openjdk/jdk/blob/master/src/hotspot/share...

[−] tomaytotomato 33d ago
It's a result of Java being required to run on many different OS environments (Oracle, Redhat, Windows, RISC/ARM/x86), along with user constraints and also business requirements.

In a way you can use this list of JVM options to illustrate how successful Java has become, that everyone needs an option to get it to work how they like it.

As a Java dev, I have maybe used about 10-15 of them in my career.

The weirdest/funnest one I used was for an old Sun Microsystems Solaris server which ran iPlanet, for a Java EE service.

Since this shared resources with some other back of office systems, it was prone to run out of memory.

Luckily there was a JVM option to handle this!

-XX:OnOutOfMemoryError=""

It wasn't too important so we just used to trigger it to restart the whole machine, and it would come back to life. Sometimes we used to mess about and get it to send funny IRC messages like "Immah eaten all your bytez I ded now, please reboot me"

[−] nkzd 33d ago
Which JVM options do you use the most?
[−] cogman10 33d ago
Heap size, GC algorithm.

I suggest most people never touch almost any other options. (Flight recording and heap dumps being the exception).

[−] marginalia_nu 33d ago
GC threads are generally often useful on multi-tenant systems or machines with many cores, as Java will default-size its thread pools according to the number of logical cores. If the server has 16 or more cores, that's very rarely something you want, especially if you run multiple JVMs on the same host.

Not JVM options, but these are often also good to tune:

    -Djdk.virtualThreadScheduler.parallelism
    -Djdk.virtualThreadScheduler.maxPoolSize
    -Djava.util.concurrent.ForkJoinPool.common.parallelism
In my experience this often both saves memory and improves performance.
[−] cyberpunk 33d ago
You can get into difficulty with kubernetes here, as your jvm will detect all cores on the node but you may have set a resources limit on the pod/whatever, so it’ll assume it can spend more time doing stuff than it actually can, so often times it’s quite necessary to tune some things to prevent excessive switching etc.
[−] dpratt 33d ago
Modern JVMs will detect orchestrator-set cgroup limits and size themselves accordingly. If you, for example, set a cpu limit for a pod to “1”, the JVM will size itself as if it was running on a single core machine.
[−] cyberpunk 30d ago
TIL, this is great. Thanks, saved me some yaml :}
[−] EdwardDiego 32d ago
Nah they fixed the JVM to be container aware some versions ago - I do remember dealing with this in early Java 8 days, think Java 10 is when it got fixed, and then it was backported to later releases of Java 8.
[−] vips7L 33d ago
Afaik this was fixed a long time ago.
[−] Hendrikto 33d ago

> As a Java dev, I have maybe used about 10-15 of them in my career.

So do we really need multiple thousand? Having all of them also makes finding the few you actually need much more difficult.

[−] KronisLV 33d ago

> So do we really need multiple thousand?

Assuming that you don't need 99.9% of them (they should have sane defaults that you never have to change or even learn that they exist or what they are) until that super rare case when one will save your hide, I'd lean towards yes.

In other words, they might as well be an escape hatch of sorts, that goes untouched most of the time, but is there for a reason.

> Having all of them also makes finding the few you actually need much more difficult.

This is a good point! I'd expect the most commonly changed ones (e.g. memory allocation and thread pools) to be decently well documented, on the web and in LLM training data sets. Reading the raw docs will read like noise, however.

[−] nradov 33d ago
Did you RTFM? It's not difficult to find the few you actually need.
[−] elric 33d ago
In what way is gofmt remotely comparable to a JVM?

In reality the number of options is significantly smaller than the 1843 you mentioned. The list contains boatloads of duplicates because they exist for multiple architectures. E.g. BackgroundCompilation is present on 8 lines on the OpenJDK 25 page: aarch64, arm, ppc, riscv, s390, x86 and twice more without an architecture.

[−] avianlyric 33d ago
gofmt isn’t really comparable to the JVM, but it is a really strong expression of the opinionated tooling GoLang has.

While gofmt is “just” a formatting tool. The interesting part is that go code that doesn’t follow the go formatting standard is rejected by the go compiler. So not only does gofmt not have knobs, you can’t even fork it to add knobs, because the rest of the go ecosystem will outright reject code formatted in any other way.

It’s a rather extreme approach to opinionated tooling. But you can’t argue with the results, nobody writing go on any project ever worries about code formatting.

[−] parsd 33d ago
I don’t believe the Go compiler would reject unformatted code. The compiler has its own set of rules for what it views as syntactically correct code, but these rules have nothing to do with gofmt’s formatting rules.

For example, it’s the compiler and not gofmt that dictates that you must write a curly brace only on the same line of an “if” statement. If you put it on the next line, you don’t have unformatted code - you have a syntax error.

However, the compiler doesn’t care if you have too much whitespace between tokens or if you write your slice like []int{1, 2,3,4}, but gofmt does.

We could say the rules of the compiler and gofmt don’t even overlap.

[−] kfuse 33d ago
They do worry, they just can't do anything about it. Like the fact that error handling code takes at least three lines no matter how trivial it is. I'm sure error handling would not be critisized nearly as much if it didn't consume so much vertical space and could fit in one line, which go compiler does allow.
[−] elric 33d ago
That's all well and good, but entirely irrelevant to the number of options a JVM should reasonably have.
[−] bram98 33d ago
.
[−] pjmlp 32d ago
We also don't in other ecosystems, Go advocates pretend we do.

C has had indent since 1976, then it is only a matter of having a pre-commit hook, regardless of the programming language.

We don't need some monk telling us how to format code from the gospels, a company wide rulling suffices.

[−] layer8 33d ago
The comparison with gofmt makes no sense. If Go had myriads of compiler implementations (the analogy being target environments for the JVM) that all had different performance characteristics and other behavioral differences depending on how the source code is formatted, you bet that gofmt would have a lot of options as well.

The JVM is like an operating system. A better comparison would be Linux kernel parameters: https://www.kernel.org/doc/html/latest/admin-guide/kernel-pa...

[−] eru 33d ago

> You could never even consider all of the possible combinations and interactions, let alone test them.

Nobody has ever tested all possible inputs to 64 bit multiplication either. You can sample from the space.

[−] pixl97 33d ago
Eh that sounds a bit different to me, multiplication should be roughly the same operator on each test, these are wildly different functions.
[−] deepsun 33d ago
You forgot about NaNs (all of them), infinities and positive/negative zeros. Tests warranted.
[−] MaxBarraclough 33d ago
Don't forget the Intel floating-point division bug from the 90s.

https://en.wikipedia.org/wiki/Pentium_FDIV_bug

[−] eru 32d ago
I was being a bit hyperbolic.

However, sampling really is the way to go when you face a combinatorial explosion. (If you can't prove it correct, that is.)

[−] Geezus_42 33d ago
As a sysadmin, not developer, I hate Java almost as much as Windows. The error messages Java apps produce are like coded messages that you have to decipher.

I.E. Instead of " TLS Handshake failed" it will be something like "ERROR: PKIX failed". So now I have to figure out that PKIX is referring to PKI and it would make too much sense to provide the domain that failed. Instead I have to play the guessing game.

[−] gf000 32d ago
Did you mistype Go?

Java has proper error messages with a full stack trace that tells the whole story.

Of course individual developers may lazy out on writing useful error messages, but that's hardly a Java issue.

Meanwhile in Go you can be happy to have any error message at all. And then you can hope it's a unique string you can grep in a codebase. Where you only may find a line of code you could have arrived from many different places.

[−] Geezus_42 31d ago
See my other comments about how stack traces are un-ergonomic for sysadmins. I'm sure they are great for developers.
[−] deepsun 31d ago
Btw, Go does have runtime exceptions, they just call them Panics and don't have any nice tooling around it. :)
[−] deepsun 33d ago
I hate when tools only produce generic "TLS Handshake failed" instead of saying why exactly it failed, where is the problem.
[−] thunky 33d ago
Sounds like you'd both be happy if the tool produced both.
[−] deepsun 31d ago
And Java typically does produce both (see Exception "cause" field). So when an exception stack trace is printed it's actually list of stacktraces, for each "cause". You can skip stacktraces and just concatenate causes' messages (like people often do in Go).

So the full message would be like "Cannot add item X to cart Y: Error connecting to warehouse Z: Error establishing TLS connection to example.com 127.0.1.1: PKIX failed".

[−] Geezus_42 33d ago
Sounds to me that deepsun and I are in agreement that an error message should tell you what the actual error was.

I.E. ERROR: TLS handshake failed: certificate chain unverified

[−] iamcalledrob 32d ago
People give Go's error handing a lot of flak, but I personally love the errors that come out of a quality codebase.

Just like your example: single line, to the point and loggable. e.g.

  writing foo.zip: performing http request (bar.com): tls: handshake: expired certificate (1970-01-01)
Exceptions with stack traces are so much more work for the reader. The effort of distilling what's going on is pushed to me at "runtime". Whereas in Go, this effort happens at compile time. The programmer curates the relevant context.
[−] gf000 32d ago
What?

What you write makes zero sense, see my comment here: https://news.ycombinator.com/item?id=47750450

And come on, skipping 5 lines and only reading the two relevant entries is not "much work". It's a feature that even when developers eventually lazied out, you can still find the error, meanwhile you are at the mercy of a dev in go (and due to the repeating noisy error handling, many of the issues will fail to be properly handled - auto bubbling up is the correct default, not swallowing)

[−] iamcalledrob 32d ago
Different strokes for different folks.

The Go errors that I encounter in quality codebases tend to be very well decorated and contain the info I need. Much better than the wall of text I get from a stack trace 24 levels deep.

[−] gf000 32d ago
Apples to oranges.

Quality java code bases also have proper error messages. The difference is that a) you get additional info on how you got to a given point which is an obviously huge win, b) even if it's not a quality code base, which let's be honest, the majority, you still have a good deal of information which may be enough to reconstruct the erroneous code path. Unlike "error", or even worse, swallowing an error case.

[−] Geezus_42 32d ago

> reconstruct the erroneous code path

This is only useful to the developers who should be fixing the bug. Us sysadmins need to know the immediate issue to remediate while the client is breathing down our neck. Collect all the stack traces, heap dumps, whatever you want for later review. Just please stop writing them to the main log where we are just trying to identify the immediate issue and have no idea what all the packages those paths point to do. It just creates more text for us to sift through.

[−] appplication 33d ago
This is why stack traces exist. But I agree Java seems to not really have a culture of “make the error message helpful”, but instead preferring “make the error message minimal and factual”.

For what it’s worth, the rise of helpful error messages seems to be a relatively new phenomenon the last few years.

[−] Geezus_42 32d ago
A stack trace that is >10 pages in less is not what I would call minimal.
[−] deepsun 31d ago
And that's why you should have multiple appenders. So in code you write "log.error("...", exception)" once, but logging writes it in parallel to:

   1. STDOUT for quick and easy look, short format.
   2. File as JSON for node-local collectors.
   3. Proper logging storage like VictoriaLogs/Traces for distributed logging.
Each appender has its own format, some print only short messages, others full stacktraces for all causes (and with extra context information like trace id, customer id etc). I really think STDOUT-only logging is trying to squeeze different purposes into one unformatted stream. (And Go writing everything to STDERR was a really strange choice).

https://www.baeldung.com/logback#bd-appenders

[−] Geezus_42 29d ago
Cool. Convince your fellow Java developers to do that and I'll quit complaining about the awful errors every Java app produces.
[−] kitd 33d ago
This is the kind of scenario that is served better by Go/C-style error values than exceptions. Error values facilitate and encourage you to log what you were doing at the precise point when an error occurs. Doing the same with exceptions idiomatically often requires an exception hierarchy or copious amounts of separate try/catches.

The difference really becomes apparent when trying to debug a customer's problem at 3am (IME).

[−] gf000 32d ago
This couldn't be further from the truth.

There is no ecosystem I would choose over Java when it comes to observability and it's not even close.

Good luck finding your segfault, oom, race condition or just simply lazy logging culture bug with a C/go codebase at 3am.

I will happily see my proper stack trace, heap dump, or connect directly to prod with a debugger with basically no performance penalty.

[−] well_ackshually 33d ago
So your issue isn't with Java, just with shit error messages and devs clearing the exception stack.
[−] asteroidburger 32d ago
Tell your developers to start logging the exception, not just a hard-coded error message.
[−] TacticalCoder 33d ago

> 1843 options is too many. You could never even consider all of the possible combinations and interactions, let alone test them.

You can search for those that may concern you. Good old search or AI "search".

For example I recently did test the AOT compilation of Clojure (on top of the JVM) code using "Leyden". I used an abandoned Github project as a base but all the JVM parameters related to Leyden had changed names (!) and the procedure had to be adapted. I did it all (as a Dockerfile) in less than an hour with Sonnet 4.6 (complete with downloading/verifying the Leyden JVM, testing, taking notes about the project, testing on different machines, etc.).

These are not trivial calls to the "java" command: it involves a specific JVM and several JVM params that have to work fine together.

The goal was to load 80 000 Clojure/java classes (not my idea: the original project did that part) and see the results: 1.5 seconds to launch with the Leyden JVM (and correct params) vs 6 seconds for a regular launch (so a 75% gain). GraalVM is even faster but much more complicated/annoying to get right.

It can look overwhelming but I'd say all these parameters are there for a reason and you only need a few of them. But when you need them, you need them.

P.S: unrelated to TFA and as a bonus for the "Java is slow crowd":

    time java -jar hello/hello.jar
    Hello, World!

    real    0m0.040s
And that's without any Leyden/GraalVM trick. For Clojure the "slow" startup times are due to each Clojure function being transformed into one Java .class each and there are many Clojure functions. Hence the test with 80 000 Clojure functions from the project I reused: https://github.com/jarppe/clojure-app-startup-time-test (but it's not maintained, won't work as if with the latest Leyden JVM)
[−] fHr 33d ago
Thank god you have no say in where modern tooling is heading, at the creator of the site, absolute right choice to leave it up to the user to chose all options.
[−] pjmlp 33d ago
Have you ever seen how many GCC has for plain old C?
[−] deepsun 33d ago
Just because you have more features and ways to use them. Say I like to use a different garbage collector for a tool.
[−] mzi 33d ago
One of my nerd-quizzes I hade at interviews before was "what letters in what case are NOT flags to GNU ls".
[−] RadiozRadioz 33d ago
I don't think modernity is a noteworthy factor as to whether tooling is opinionated.
[−] quotemstr 33d ago
In the age of LLMs coupled with open source software, option count is unlimited. I fork FOSS projects and modify them for my own use all the time. Sometimes, with an agent, doing so is even easier than finding the "right" knob.
[−] tezza 33d ago
How is this different to system tuning parameters in Linux /proc, FreeBsd, Windows Registry, Firefox about:config, sockopt, ioctl, postgres?

Zillions of options. Some important, some not

[−] jmyeet 33d ago
Wasn't it Joel Spolsky who said every option is a cop out? Or maybe Steve Yegge? I forget. It's something I agree with. I often have this thought when going through the options of something conceptually fairly simple: "who is this for? who actually uses this option?"

I kinda feel the same way with C/C++ warnings. Different code bases decide if different warnings are errors. That was a mistake (IMHO).

The other thought I have scanning these options is how many are related to GC. I kinda think GC is a bit of a false economy. It's just hiding the complexity. I wonder if it would've been better to push GC to be pluggable rather than relying on a host of options, a bit like TCP congestion management. I mean there are /proc parameters for that in Linux, for example, but it's also segregated (eg using BRR).

At the end of the day, none of this really matters. As in, the JVM is mature and I think generally respected.

[−] ncruces 32d ago
I appreciate the philosophy behind Go as much as anyone, but this comment sounds tone deaf, and indicative of a certain immaturity.

Many of these flags are the equivalent of GODEBUG. GODEBUG does not have 1843 options, but do you know how many it has? Have you ever used GODEBUG? (I'm guessing no, hence, immaturity) And if not, do you think it should just be dropped because you never needed it?

[−] exabrial 33d ago
His other project "Byte Me", along with judicious javap usage, has been super useful for me learning JVM bytecode so I could make a machine learning model compiler for the JVM (basically compile your ML models as native code; ONNX, tree ensembles, regressors, classifiers, etc as native JVM classes with no massive runtime needed)

still in the works, but its here for those interested: Petrify: https://github.com/exabrial/petrify

[−] coolius 33d ago
This is going to come very handy for development of CodeBrew, my Java IDE for iPhone/iPad. It runs a full OpenJ9 JVM under the hood, and I had to do a bunch off massaging with the options to get it to run properly. I wish I had known this page sooner!

For anyone intered, here's the app:

https://apps.apple.com/app/apple-store/id6475267297?pt=11914...

[−] motoboi 33d ago
People say we don’t build cathedrals anymore.

But here it is: JVM is a modern cathedral.

[−] guusbosman 33d ago
There is a 2nd edition now of the Optimizing Java book you are referring to on your site.
[−] zkmon 33d ago
Those button at the top link to different domains altogether, but present the same page. So it is one page with multiple domains, instead of one domain with multiple pages.
[−] m3047 32d ago
Includes numerous JVM implementations, including Azul Systems'.
[−] scrame 33d ago
OK, now make them all run at once!

(I know many conflict and there is not a shell buffer long enough to handle all that)

Kidding aside, I actually said "ugh, seriously" when I saw that there were literally thousands of options. Is there a public program with more options?

[−] rvz 33d ago
All of that configuration and it will always be less efficient than Rust, or even Golang.

This is why lots of engineers waste time fiddling with options to tune the JVM and still require hundreds of replicated micro-services to "scale" their backends and losing money on AWS and when they will never admit the issue is the technology they have chosen (Java) and why AWS loves their customers using inefficient and expensive technologies.

Even after that, both Go and Rust continue to run rings around the JVM no matter the combination of options.