Java’s twenty years old now — next year Duke can have Irish Coffee! Seriously, though, in that time there’s been a lot of changes to the Java Virtual Machine (JVM). What once was true may no longer be the case. In digging into an issue I discovered a nice feature of the JVM which enables being able to definitively state the effects of a particular switch as well as decisions made by JVM heuristics on a JVM/Host pair — just because something might be true on a particular host doesn’t mean it’s true everywhere!
There is a helpful switch added post 1.6.0_14 (I believe it was added at 1.6.0_18, but am not certain): -XX:+PrintFlagsFinal
which outputs the values of all of the jvm configuration parameters and/or values. A snippet of the output may be seen below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Global flags] uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} uintx AdaptiveSizePolicyOutputInterval = 0 {product} uintx AdaptiveSizePolicyWeight = 10 {product} uintx AdaptiveSizeThroughPutPolicy = 0 {product} uintx AdaptiveTimeWeight = 25 {product} |
This is very helpful in terms of determining the effect (or lack thereof) of an argument on the JVM. Additionally, you can determine the values computed by the heuristics on a particular host/JVM combination. It can be invoked as follows:
1 2 |
java -XX:+PrintFlagsFinal -version 2>&1 |tee OUTFILE |
That invocation lets you “watschen der blinkinlights” and also save the results to a file. If you were interested in seeing how a particular set of arguments impacted the startup time of the JVM, you could prepend the command with time
. However, should you do so, run it more than once and take the average.
Here are some of the things you can discover (there are hundreds):
Parameter | Description |
---|---|
InitialHeapSize |
The minimum size of the heap in KB. |
MaxHeapSize |
The maximum size of the heap in KB. On most machines today this will be equivalent to 1/4 the ram. |
NewSize |
Size of the Young Generation (or Eden Space) |
OldSize |
Size of the Old Generation |
ParallelGCThreads |
Number of threads used for garbage collection. Up to 8 cores this corresponds to the number of cores available |
SelfDestructTimer |
The number of minutes before the JVM will terminate. (0 means it won’t). I just discovered it; this could be fun on April Fools Day. |
UseConcMarkSweepGC UseDynamicNumberOfGCThreads UseG1GC UseParallelGC UseSerialGC |
This will show the type of garbage collection being used. Typically (as of 2015) it will be the Parallel Garbage Collector |
Examples
The following examples are on a quad core linux host with 12GB of RAM using JDK 1.8.0_60.
Baseline
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ java -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 188743680 {product} uintx MaxHeapSize := 2988441600 {product} uintx ParallelGCThreads = 4 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC := true {product} bool UseParallelOldGC = true {product} bool UseSerialGC = false {product} |
From a couple of tests I’ve performed, the stock InitialHeapSize
is 1/16 the size of the MaxHeapSize
. However, “you don’t have to take my word for it“.
Setting a Maximum Heap Size or Minimum, But Not Both
Here the MaxHeapSize
is set to 1G. Note that the InitialHeapSize
has remained constant:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ java -Xmx1G -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 188743680 {product} uintx MaxHeapSize := 1073741824 {product} uintx ParallelGCThreads = 4 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC := true {product} bool UseParallelOldGC = true {product} bool UseSerialGC = false {product} |
Setting the InitialHeapSize
but not the MaxHeapSize
— the max goes back to the default:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ java -Xms1G -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 1073741824 {product} uintx MaxHeapSize := 2988441600 {product} uintx ParallelGCThreads = 4 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC := true {product} bool UseParallelOldGC = true {product} bool UseSerialGC = false {product} |
Changing the Garbage Collector
JDK 1.8 provides several collectors. At present (1.8.0_60), the default garbage collector on this machine is the Parallel garbage collector. It is believed that this will change to the G1 garbage collector in 1.9.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ java -XX:+UseG1GC -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 188743680 {product} uintx MaxHeapSize := 2988441600 {product} uintx ParallelGCThreads = 4 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC := true {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC = false {product} bool UseParallelOldGC = false {product} bool UseSerialGC = false {product} $ java -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 188743680 {product} uintx MaxHeapSize := 2988441600 {product} uintx ParallelGCThreads = 4 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC := true {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC := true {product} bool UseParallelGC = false {product} bool UseParallelOldGC = false {product} bool UseSerialGC = false {product} $ java -XX:+UseSerialGC -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 188743680 {product} uintx MaxHeapSize := 2988441600 {product} uintx ParallelGCThreads = 0 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC = false {product} bool UseParallelOldGC = false {product} bool UseSerialGC := true {product} $ |
flickr photo shared by JD Hancock under a Creative Commons ( BY ) license
One of these things is not like the others. Notice that the Serial garbage collector is single threaded.
Also take note which collectors have (or do not have) UseParallelOldGC
or UseParNewGC
set to true; it’s pretty interesting.
Not All JVM are Created Equal
To illustrate, here are the defaults from another Linux host; this one has 8 cores and 32G of ram:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ java -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 518637312 {product} uintx MaxHeapSize := 8298430464 {product} uintx ParallelGCThreads = 8 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC := true {product} bool UseParallelOldGC = true {product} bool UseSerialGC = false {product} |
Here are the defaults from a JVM on a Raspberry Pi B+ (1 Core, 512M of ram):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ java -XX:+PrintFlagsFinal -version 2>&1 |egrep 'InitialHeapSize|MaxHeapSize|Use.*GC |ParallelGCThreads' uintx InitialHeapSize := 8388608 {product} uintx MaxHeapSize := 115343360 {product} uintx ParallelGCThreads = 0 {product} bool UseAdaptiveSizePolicyWithSystemGC = false {product} bool UseConcMarkSweepGC = false {product} bool UseG1GC = false {product} bool UseMaximumCompactionOnSystemGC = true {product} bool UseParNewGC = false {product} bool UseParallelGC = false {product} bool UseParallelOldGC = false {product} bool UseSerialGC = false {product} |
It’s just a little different….
This started as a blog post to help me remember a nifty switch I’d discovered today; it’s grown a little bit and I’ve learned some new things along the way!
One last thought, however. Don’t assume that you know how the JVM is going to behave in a particular version on a particular host.
1 ping
Docker, Cgroups, Memory Constraints, and Java: A Cautionary Tale, or Here be Reapers (sometimes) » Ramblings
March 17, 2016 at 8:00 am (UTC -5) Link to this comment
[…] year I wrote in Looking Inside a JVM: -XX:+PrintFlagsFinal about finding the values configured in the JVM at runtime. By not specifying a heap size, I get the […]