RFR: 8368015: Shenandoah: fix error in computation of average allocation rate

Kelvin Nilsen kdnilsen at openjdk.org
Sat Sep 20 00:11:27 UTC 2025


On Fri, 19 Sep 2025 20:36:51 GMT, Kelvin Nilsen <kdnilsen at openjdk.org> wrote:

> We use bytes_allocated_since_gc_start() to compute allocation rates.  This leaves a blind spot, as our current implementation ignores allocations and the time period between the moment we begin GC and the first time we update the allocation rate following the start of GC.  When this happens, we typically find that the sampled number of allocations is smaller than the allocations that had accumulated by the time we triggered the start of the current GC cycle.
> 
> This PR adds tracking for that accounting gap.

As currently implemented, bytes_allocated_since_gc_start() is reliable (as in mainline, or as in the PR that unifies all accounting within the ShenandoahFreeSet).

The problem is that the average allocation rate is not reliable.  The reason average allocation rate is not reliable is because:
1. In the most common case, all of the allocations that happen between the moment we last sampled before the start of GC and we next sample at the end of final mark are ignored.  This might represent several seconds of time.  In the case that allocation rates are increasing, we will be excluding the most recent several seconds of allocation behavior from our next allocation average.
2. In the less common case, our "average" allocation rate will be grossly under-reported.  Consider a case where allocation rate is gradually increasing.  We've been sampling every 100 ms, and our average rate at the time we trigger GC is based on recent allocation rates of: ..., 1GB/s, 1.1GB/s, 1.3GB/s, 1.4GB/s and then we trigger.  Suppose allocation rates continue to increase at this same rate, but we don't sample again until the end of marking, which takes 3 seconds.  During these 3 seconds, we have missed 30 updates.  If we had continued to sample, we would have seen that the most recent 100 ms allocated at a rate of 4.5 GB/s.  The proposed new implementation would add a sample that says we just allocated at an average rate of ~3GB/s for ~3.05 seconds.  In the current  implementation, we might find that the last_sample_value is 8GB (assume this sample happened 50 ms before GC was triggered).  The new value of allocated will be 9 GB (3 GB/s for 3 seconds).  So we'll add into our 
 average the value 1 GB allocated in 3.05 seconds, or approximately 333 MB/s (with a heavy weight because this represents 3 seconds of execution time).  This causes triggering heuristic to be overly optimistic, because they are left believing that allocations are at a lower rate than reality.

src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp line 374:

> 372:     _rate.add(rate);
> 373:     _rate_avg.add(_rate.avg());
> 374:     _last_sample_time = now;

Note: in original implementation, the first sample() collected following the start of GC typically finds that allocated < _last_sample_value, so we ignore all data associated with that time gap.  With Shenandoah, the next sample happens the first time we ask should_start_gc() following the completion of the current GC.  With GenShen, the next sample happens after marking, when we calculate the allocation runway in order to size our reserves.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/27398#issuecomment-3314220009
PR Review Comment: https://git.openjdk.org/jdk/pull/27398#discussion_r2364492203


More information about the hotspot-gc-dev mailing list