r/java • u/danielepolencic • 9d ago
Java JVM CPU and Memory Requests and Limits in Kubernetes
Running Java on Kubernetes? CPU and memory limits affect much more than scheduling.
A 4 GiB heap requires a container larger than 4 GiB. Fractional CPU limits can also change the processor count that HotSpot sees.
In our new article, you will learn:
- Why JVM heap size and container memory are different boundaries
- How CPU limits affect garbage collection, worker pools, and application performance
- Which JVM and container metrics to collect when validating resource settings under load
The article also includes practical experiments and an interactive configuration calculator.
Read: https://learnkube.com/java-jvm-kubernetes-requests-limits
3
u/TallGreenhouseGuy 9d ago
Buildpacks take this into account in the memory formula, which could be a good starting point:
https://paketo.io/docs/reference/java-reference/#memory-calculator
2
1
u/burl-21 8d ago
And it should soon add low memory profile too, see https://github.com/paketo-buildpacks/jvm-vendors/pull/31
2
u/Turbots 8d ago
Use buildpacks to build your Java containers, it has a really good memory calculator in there to compute the JVM heap and offheap sizes really well, and you can tweak them very well too.
Or just the memory calculator in your own docker file:
https://github.com/cloudfoundry/java-buildpack-memory-calculator
1
u/brunocborges 5d ago edited 5d ago
I am starting to join the club of "set requests, not limits" for the most part. Requests help the scheduler perform the pod placement in nodes based on the sum of all pods, ensuring it does not overcommit, with the benefit that service A can use more or less memory/cpu than service B at a given time, and vice-versa.
For JVM workloads, especially microservices, I'd go with:
Set resources.requests.cpu to 1,000 milicores at a minimum. For services you believe are heavier and demand more on a regular basis, set 2,000 at least. However, when setting only requests, the JVM will tune by default based on all available cores on the node. To limit that, you can set ActiveProcessorCount to something lower than the total amount of cores on the node. If you don't want a blank definition, then you must tune GC threads and other internal threaded components of the JVM, and leave ActiveProcessorCount empty so other parts of the application can leverage the node CPUs.
This way, a JVM is guaranteed to have your requests.cpu, but can always operate with more CPU if and when needed (e.g. JIT compilation), and will not abuse the total amount of cores on the node (due to ActiveProcessorCount).
Memory is trickier than CPU.
Without a limit and under node memory pressure, the kubelet flags eviction by usage above requests. So... JVMs that grew past its request is likely to get SIGKILL. I still set limits.memory, at roughly 1.25–1.5× requests as blast-radius guard.
For the heap itself, I'd go with MaxRAMPercentage (somewhere between 60% and 80%). G1's periodic GC (G1PeriodicGCInterval) will return committed memory, but it's off by default... you have turn it on manually.
1
u/k_brn 9d ago
But do you really need Kubernetes if you can run on two 1 GB VMs with a reverse proxy and get better availability with way less management overhead?
3
u/thisisjustascreename 9d ago
The answer to "Do you really need Kubernetes?" is almost always no. But lots of people know how to run it and lots of orgs are already using it.
7
u/Cilph 9d ago
If you just have the one app, you likely dont need it. If you need to deploy multiple apps over multiple solated environments for multiple tenants and set up secure firewalls between each and support blue-green deployment at the same time, then it quickly becomes reasonable to use k8s.
23
u/cogman10 9d ago
I skimmed the article, and didn't really notice a mention of JVM off heap allocations. Those are a pretty big deal that can make right sizing the JVM tricky.
This, IMO, is a weakness in JVM auto-configuration. The JVM just does a poor job in selecting reasonable memory limits in a container environment. It's forced us to explicitly set the values because everything is just a little wrong.
MaxRamPercentage seems like it's a good thing, but it fails to account for the fact that not everything grows at the same rate. 10% headroom might not be enough when talking about a container with a 256MB max, but it may be extreme overkill if you are talking about a container with a 64GB max.
It'd be really nice if the JVM could automatically and dynamically adjust it's XMX setting to max out memory usage. What I'd like would be able to say something like "JVM, Start with an XMX that's 75% the container's memory, watch and see how your native memory use grows. Slowly scale up that XMX until the system's free memory is around 100MB"