Remix.run Logo
cogman10 a day ago

My main advice, ignore all advice online about JVM tuning minecraft. It's dated and often just wrong.

The best thing you can do is run on the latest JVM, set your max memory as high as you can tolerate, and use ZGC. That's it.

The JVM tuning guides for minecraft will have you switching and toggling every JVM flag under the sun with extremely dubious "evidence" of the payout for the flags they switch. In some cases, they will set contradictory flags. For example, setting eden size and target pause time. The fine tuning flags disable the heuristic flags like target pause time.

KISS. ZGC has very low latency and the larger the heap you give it the less it'll slow things down (though, do keep it under 32gb. Object header compression works on heaps less than 32gb). And using the latest JVM reduces the memory needed and increases the performance in general of the JVM. Win-win.

Night_Thastus a day ago | parent | next [-]

It did used to be required. Older Java versions performed pretty terrible. However, Java 17+ completely fixed that. Even the heaviest modded setups run buttery smooth, even on older versions which are not well optimized.

cogman10 a day ago | parent | next [-]

It really wasn't and was always dubious.

A lot of the advice, for example, suggested using CMS. Which has been a terrible garbage collector almost since it was added to the JVM. When G1GC landed in 8, that was the correct GC to select for minecraft. Even the parallel collector would have been a better option than CMS in a lot of cases (sub 4gb heaps).

It was placebo. People wanted there to be SOMETHING that made things better so they reached for the billion different flags on the JVM (rather than fixing the underlying code). I've seen the same sort of mentality professionally and it's basically always been wrong.

nananana9 21 hours ago | parent | next [-]

> rather than fixing the underlying code

There's not much you can do to fix the underlying code if the language doesn't allow you to declare a struct Vector3 { float x, y, z; } without heap-allocating it.

Quekid5 a day ago | parent | prev [-]

Yep, once people discover the multitude of JVM flags the cargo culting begins...

It really applies to anything with a huge number of options, I remember similar things from back when I used Gentoo and the wild suggestions I'd get for what exact CC/C++ compiler options to use...

pjmlp a day ago | parent | prev [-]

It was more like Minecraft code was horrible, than anything else.

Creating objects like crazy, range for in hot paths, not caring about data representation, chosen algorithms,....

JIT and GC were already doing heroic efforts.

Yet, it settled Notch for life, which is something to take into account in the usual question regarding which technical stack for games.

hwyadenlaw a day ago | parent | next [-]

For sure. The tech only needs to be good enough to support the game design. For some games that means very high optimisation is needed, and for others it's enough that the screen can refresh before the user gets tired of waiting.

Night_Thastus a day ago | parent | prev | next [-]

Minecraft 1.7.10, covered in mods, can run fantastic (300+ FPS) if Java 25 is used. That used to be impossible.

vintermann 20 hours ago | parent [-]

Thanks to the Gregtech New Horizon folks.

0x3444ac53 a day ago | parent | prev [-]

Yeah at the time it was bad. Now, it's a pretty nice codebase

Velocifyer a day ago | parent | prev | next [-]

Do not set the heap size to more than 8GiB, even if using heavy modpacks. Anything above 8GiB actually makes the game run worse, unless you have tons of players, because of GC lag.

cogman10 a day ago | parent | next [-]

It depends on the GC, with ZGC no. Particularly if you enable generational mode.

For other GCs like G1 and the parallel collector, when new gen is filled up the GC pauses the application in order to run a minor collection. For G1, major collections are ran in the background until the heap fully fills up. When that happens, the GC triggers stop the world GC.

A smaller heap size would cause those pauses to be more frequent but because the heap is smaller it means that ultimately the pauses will be limited in length.

ZGC is different. Without generational mode, ZGC is collecting the entire heap every time with it, ZGC will do the young old collections.

When a collection is triggered, ZGC flips a field to indicate a collection in progress, that flip is the only pause for the application under zgc (except the case that the heap fully fills) it typically finishes in microseconds. Once collection starts, the ZGC collector threads and the in progress application are running collections. The size of the heap really doesn't change much other than the background collection could last longer (slowing down the server, but not totally stopping it from being responsive).

The payment you make with ZGC is that you do burn more CPU cycles doing collections especially while the app is running. But for a game server, that hardly matters. What matters is that the server remains responsive in a timely fashion, which ZGC enables.

acpdev a day ago | parent [-]

I don’t believe this is exactly how g1 works.

- It is divided into by default 2048 regions so it will be setting region size to 4mb means with an 8gb heap, and that you’ll get an inefficient collection once you have a 2mb+ object to deal with. So there is a value to setting a bigger heap size to get a bigger region size. The max is 32mb region size then after that iirc the region count bumps. https://docs.oracle.com/en/java/javase/17/gctuning/garbage-f...

- The young gen size also something that resizes dynamically and so having a bigger max heap will let that get larger and avoid pressure and promotion of objects that would otherwise just never get promoted in the first place (stop the world to move from new to old gen). I’ve never seen this stated anywhere I can think of but believe that I’ve observed it many thousands times.

- The reason that a Full GC occurs is because the application allocates too many objects that can't be reclaimed quickly enough, this isn’t just something that is always happening in the background , I would call full gcs a tuning problem, not everyone agrees in a given org fwiw, but I’ve been able to tune them out for nearly all workloads. https://docs.oracle.com/en/java/javase/17/gctuning/garbage-f...

TLDR , as long as I have enough ram for the working set (page cache etc) I’ve had good luck cranking g1gc very high in prod and avoiding long collection times all together. You see other large deployments do similar (was big in the Cassandra space back in the day, Instagram off the top of my head was running very large heaps with g1gc before anyone else I knew of).

cogman10 a day ago | parent [-]

> I don’t believe this is exactly how g1 works.

Everything you've posted is accurate but I also don't believe it contradicts what I said. I certainly simplified and I'm not suggesting G1 is a bad GC, it's one that we often use. I just think ZGC is better for the job of a game server.

G1 is an excellent GC if your application can tolerate periodic 50ms latency spikes (most applications can). My argument is that specifically for a game server, ZGC is better. That's because game servers typically have more than enough CPU horse power and added latency is detrimental to the experience. So trading off more CPU usage due to GC is a worthy tradeoff vs the added latency spikes.

preg_match a day ago | parent | prev | next [-]

8 GB is simply not enough for most modpacks, at least the 200+ mods one. I know, I've tried, although it was years ago. You run at consistently 95% GC usage and get constant, very heavy, collections because of it. Like every second you get lag spikes due to GC pressure. And because of the usage these look to be full collections, which are stop-the-world last I checked.

This was ~5 years ago, so I'm sure it's gotten better. But not that much better 8gb would cut it, either client or server side.

int0x29 a day ago | parent | prev [-]

I would generally not call modpack that runs well on 8gb heavy

int0x29 a day ago | parent | prev [-]

Set JVM heap to half available memory. Mincraft is very much the kind of appication that benifits from filesystem cache to help with loading chunks.

Also if for some reason you are setting the heap size high avoid going past 32 gb unless you tune object alignment. Allocating between 32 and 47 gb will result in an effectively smaller heap. Allocating more than 32 gb can result in performance penalties.

inigyou a day ago | parent [-]

Wow. When I played Minecraft, 1GB was enough and 4GB was a lot. I know that since 1.8 Microsoft rewrote large parts of the engine to make them less efficient and more ivory-tower, but I didn't know it was that bad.

atomicnumber3 a day ago | parent | next [-]

We're talking for servers, which almost always means modded (even if only server side mods like the bukkit family). Pretty common for servers with more than a dozen people to be running heaps in the 8-16GB range. And one reason for the "large" size is that they can often run with a lot less but when memory isn't that constricted you can tune for smaller GC pause times

int0x29 a day ago | parent | prev | next [-]

This was more a hasty response with modding in mind to the parent comment of "set it as high as you are comfortable with." There are limits to how high you should go. If you are running vanilla you may not need to set the heap to 50%. Anything past 50% or 32 gb isn't a great idea generally.

mudkipdev a day ago | parent | prev [-]

FWIW I still run a server for 5 people with 2GB ram and the performance is fine.