JVM Tuning for Lavalink: Heap Size and Garbage Collectors
Tune the Java runtime for Lavalink — choosing heap size, picking G1 or generational ZGC, container-aware flags, GC logging and spotting pauses that cause stutter.
On this page
Lavalink runs on the Java Virtual Machine, and the JVM’s memory and garbage collection settings directly affect how music sounds. A heap that’s too small causes frequent collections and out-of-memory crashes; the wrong garbage collector causes pauses long enough to be heard as stutters. This article covers the handful of settings that matter.
Java version: 17 or 21
Lavalink v4 requires Java 17 or newer. Both 17 and 21 are long-term-support releases.
- Java 17 is widely available and well tested with Lavalink. Kerit Cloud’s self-managed Lavalink plans come with Java 17 pre-installed.
- Java 21 adds generational ZGC, a very low-pause collector that’s excellent for audio workloads with larger heaps, and general runtime improvements.
If you’re comfortable installing it, Java 21 is a good choice for busy nodes. For small nodes, 17 is perfectly fine.
Heap size
The heap is where Java objects live. You set its maximum with -Xmx:
java -Xmx750M -jar Lavalink.jar
Don’t use all the RAM
The JVM needs memory outside the heap: metaspace (class data), thread stacks, the JIT compiler’s code cache, and native buffers used for audio and networking. If -Xmx equals your server’s RAM, total usage exceeds the limit and the process gets killed — often with no Java error at all.
Give the heap roughly 70–75% of available memory:
| RAM | -Xmx |
|---|---|
| 512 MB | 350–400M |
| 1 GB | 700–750M |
| 2 GB | 1400–1500M |
| 4 GB | 3000M |
| 6 GB | 4500M |
Container-aware sizing
In Docker and other containers, let the JVM read the container’s limit:
java -XX:MaxRAMPercentage=75 -jar Lavalink.jar
This sizes the maximum heap at 75% of the container’s memory, so the same command works if you change the container size. Modern JVMs detect container limits automatically.
Should you set -Xms?
-Xms sets the initial heap. Setting it equal to -Xmx avoids the heap growing and shrinking, which some operators prefer for predictable behaviour on a dedicated node. On a small server shared with other processes, leaving -Xms unset lets the JVM start smaller.
Bigger isn’t always better
A much larger heap than you need doesn’t help — it can make each collection handle more memory. Size for your peak usage plus headroom, and measure with /v4/stats (see how much RAM Lavalink needs).
Garbage collectors
The garbage collector reclaims unused memory. Every collector pauses the application at times; for Lavalink, short, predictable pauses matter more than raw throughput, because a long pause interrupts the steady flow of audio frames.
G1 (the default)
G1 is the default collector on modern Java and a solid choice for most nodes. It aims for pause times around a target you can set:
java -Xmx1400M -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -jar Lavalink.jar
A lower pause target makes G1 work harder to keep pauses short. 50 ms is a reasonable target for audio; the default is 200 ms.
Generational ZGC (Java 21+)
ZGC does almost all its work concurrently, with pauses typically well under a millisecond regardless of heap size. On Java 21, enable the generational mode:
java -Xmx3000M -XX:+UseZGC -XX:+ZGenerational -jar Lavalink.jar
(Newer Java versions make generational mode the default for ZGC.) It uses somewhat more memory and CPU than G1 for the same workload, so it shines on nodes with a couple of gigabytes of heap or more and enough CPU. On a tiny 512 MB node, G1 is usually the better fit.
Shenandoah
Another concurrent, low-pause collector available in many OpenJDK builds (-XX:+UseShenandoahGC). A reasonable alternative to ZGC if your JDK includes it.
Avoid the Parallel collector
The Parallel GC maximises throughput with long stop-the-world pauses — the opposite of what audio needs.
Watch for GC problems
Lavalink’s own warnings
Keep gc-warnings: true in application.yml. Lavalink logs a warning when collection pauses are long enough to affect playback. Occasional warnings under load are tolerable; frequent ones mean the heap is too small or the collector isn’t suited to the workload.
GC logging
For a detailed picture, enable unified GC logging with rotation:
java -Xmx1400M -Xlog:gc*:file=/opt/lavalink/logs/gc.log:time,uptime:filecount=5,filesize=10M -jar Lavalink.jar
The log shows every collection with its pause time. Look for pauses above ~50 ms and for collections happening very frequently — both suggest the heap is too tight.
Frame deficits
The ultimate test is audio. In /v4/stats, rising frameStats.deficit alongside GC warnings points straight at garbage collection. If deficits rise while GC is quiet, look at CPU instead.
A few other flags
-XX:+UseStringDeduplication(G1) can save memory when many identical strings exist, such as track metadata. Modest benefit; harmless to enable.-XX:+AlwaysPreTouchtouches all heap memory at startup so the OS allocates it up front. Useful on a dedicated node with-Xmsequal to-Xmx; skip it on small shared servers.-XX:+ExitOnOutOfMemoryErrormakes the JVM exit on an out-of-memory error instead of limping along, so systemd or Docker restarts a clean process.
Recommended starting points
| Node | Flags |
|---|---|
| 512 MB, Java 17 | -Xmx380M -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:+ExitOnOutOfMemoryError |
| 1–2 GB, Java 17 | -Xmx750M / -Xmx1450M with the same G1 flags |
| 2 GB+, Java 21 | -Xmx1450M (or higher) -XX:+UseZGC -XX:+ZGenerational -XX:+ExitOnOutOfMemoryError |
| Docker, any size | -XX:MaxRAMPercentage=75 -XX:+UseG1GC -XX:MaxGCPauseMillis=50 |
Put the flags in your systemd ExecStart line or Docker’s _JAVA_OPTIONS / JAVA_OPTS environment variable — see setting up Lavalink v4 and running Lavalink in Docker.
On Kerit Cloud’s managed Lavalink plans, JVM configuration is handled for you. On self-managed plans, you have full JVM access — heap size, GC choice and tuning flags on Java 11, 17 or 21.
Summary
Run Lavalink on Java 17 or 21, give the heap about 70–75% of available memory (or use MaxRAMPercentage=75 in containers), and choose a low-pause collector: G1 with a 50 ms pause target for most nodes, generational ZGC on Java 21 for larger heaps. Keep Lavalink’s GC warnings on, log GC with rotation when investigating, and correlate pauses with frame deficits to confirm that tuning actually improves audio.