Phrases such as â€śunsustainable allocation rateâ€? and â€śyou need to keep your allocation rates lowâ€? seem to belong to the vocabulary of Java Champions alone. Complex, scary and surrounded by magic aura.
As it often is, the magic disappears with a puff of smoke when you look at the concepts more closely. This post is trying to remove the magic from the terms.
Definition and practical value
Allocation rate is measured in the amount of memory allocated per time unit. Often it is expressed in MB/sec, but you can use PB/per light year if you feel like it. So that is all there is â€“ no magic, just the amount of memory you allocate in your Java code measured over a period of time.
Knowing this fact alone is not too beneficial though. If you can bear with me I can walk you through the practical use of the concept.
Facing high allocation rate can mean trouble for your application. From the practical standpoint, the impact is surfaced via Garbage Collection becoming the bottleneck. From the hardware standpoint, even commodity hardware can sustain several GB/sec of allocations per core, so in case your rates do not start exceeding 1 GB/sec/core, you can be rather comfortable that your hardware will not actually be the bottleneck.
So, when focusing on the GC, we can start with the parallel also true in the real world â€“ if you create a lot of stuff, you tend to face a lot of cleaning afterwards. Knowing that JVM is built with a garbage collecting mechanism, need to look into how allocation rate changes the frequency or duration of the GC pauses.
Measuring allocation rate
Let us start with the measurement of the allocation rate. For this letâ€™s turn on the GC logging by specifying -XX:+PrintGCDetails -XX:+PrintGCTimeStamps flags for the JVM. The JVM now starts logging the GC pauses.
0.291: [GC (Allocation Failure) [PSYoungGen: 33280K->5088K(38400K)] 33280K->24360K(125952K), 0.0365286 secs] [Times: user=0.11 sys=0.02, real=0.04 secs]
0.446: [GC (Allocation Failure) [PSYoungGen: 38368K->5120K(71680K)] 57640K->46240K(159232K), 0.0456796 secs] [Times: user=0.15 sys=0.02, real=0.04 secs]
0.829: [GC (Allocation Failure) [PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(159232K), 0.0861795 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
From the GC log above, we can calculate the allocation rate as the difference between the Young Generation size after the last collection completed and before the next one started.Using the example above, we can for example extract the following information:
- At 0.291ms after the JVM was launched 33280K of objects were created. First minor GC event cleaned the Young generation, after which there was 5088K of objects in the Young generation left.
- At 0.446ms after launch, the Young gen occupancy had grown to 38368K triggering next GCwhich managed to reduce the Young Gen occupancy to 5120K.
- At 0.829ms after the launch, Young gen size was 71680K and the GC reduced it again to5120K.
Having this information allows us to say that this particular software experienced has the allocation rate of 161 MB/sec during the period of measurement.
Analyzing the impact
Now, being equipped with this information we start understanding how the changes in allocation rate affect the application throughput by increasing or reducing the frequency of GC pauses. First and foremost, you should notice that only Minor GC pauses cleaning the Young Generation are affected. The frequency nor duration of the GC pauses cleaning the Old Generation is not directly impacted by allocation rate, but instead of a promotion rate, a term we will cover in our next post.
Knowing that we can focus only to Minor GC pauses, we should next look into the different memory pools inside the Young Generation. As the allocation takes place in Eden, we can immediately look into how sizing the Eden can impact the allocation rate. So we can have a hypothesis that increasing the size of Eden will reduce the frequency of minor GC pauses and thus allowing the application to sustain faster allocation rates.
And indeed, when running the same example with different Eden sizes using -XX:NewSize -XX:MaxNewSize &-XX:SurvivorRatio parameters, we can see two-fold difference in allocation rates
- Running the example with 100M of Eden, reduces the allocation rate to below 100MB/sec
- Increasing the Eden size to 1GB, increases the allocation rate to just below 200MB/sec.
If you are still wondering why this can be true â€“ if you stop your application threads for GC less frequently you can do more useful work. More useful work also happens to create more objects, thus supporting the increased allocation rate.
Now, before you jump to conclusion that â€śbigger Eden is goodâ€?, you should notice that allocation rate might and probably does not directly correlate to the actual throughput of your application. It is a technical measurement, contributing towards throughput. Allocation rate can and will have impact on how frequently your Minor GC pauses are stopping the application threads, but to see the overall impact, you need to take into account the Major GC pauses as well and measure the throughput not in MB/sec but in business operations your application is providing.
The post was originally published in Plumbr performance tuning blog.