RFR: 8372543: Shenandoah: undercalculated the available size when soft max takes effect [v3]
Kelvin Nilsen
kdnilsen at openjdk.org
Fri Dec 5 19:36:56 UTC 2025
On Thu, 4 Dec 2025 02:19:32 GMT, Rui Li <duke at openjdk.org> wrote:
>> Detailed math and repro see https://bugs.openjdk.org/browse/JDK-8372543.
>>
>> Currently in shenandoah, when deciding whether to have gc, how we calculate available size is:
>>
>>
>> available = (Xmx * (100 - ShenandoahEvacReserve) / 100) - used
>> soft_tail = Xmx - soft_max
>> if (available - soft_tail < ShenandoahMinFreeThreshold * soft_max) // trigger gc
>>
>>
>> The if condition `available - soft_tail` will be reduced to: `-(ShenandoahEvacReserve/100) * Xmx - used + soft_max`, which means when soft max is the same, the larger Xmx is, the less free size the app would have and the more gc it would have, which does not make sense, especially for the case where the app is mostly idle. This caused one of our internal customers experienced frequent gc with minimal workload, when soft max heap size was set way lower than Xmx.
>>
>>
>> Suggested fix: when deciding when to trigger gc, use logic similar to below:
>>
>> mutator_soft_capacity = soft_max * (100 - ShenandoahEvacReserve) / 100;
>> available = mutator_soft_capacity - used;
>> if (available < mutator_soft_capacity) // trigger gc
>> ```
>>
>> -------
>> This change also improved gc logging:
>>
>> Before:
>>
>> [6.831s][info][gc ] Trigger: Free (52230K) is below minimum threshold (52428K)
>> [6.831s][info][gc,free ] Free: 1587M, Max: 1024K regular, 1539M humongous, Frag: 2%
>> external, 18% internal; Used: 352M, Mutator Free: 1940 Collector Reserve: 103M, Max: 1024K; Used: 0B
>>
>>
>> After:
>>
>> [8.358s][info][gc ] Trigger: Free (Soft mutator free) (51498K) is below minimum threshold (52428K)
>> [8.358s][info][gc,free ] Whole heap stats: Total free: 1509M, Total used: 401M, Max free in a single region:
>> 1024K, Max humongous: 1490M; Frag stats: External: 0%, Internal: 21%; Mutator freeset stats: Partition count:
>> 1911, Reserved: 1509M, Max free available in a single region: 1024K; Collector freeset stats: Partition count:
>> 122, Reserved: 102M, Max free available in a single region: 1024K;
>
> Rui Li has updated the pull request incrementally with one additional commit since the last revision:
>
> Remove unused freeset includes
Changes requested by kdnilsen (Committer).
src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp line 940:
> 938:
> 939: size_t ShenandoahGeneration::soft_available_exclude_evac_reserve() const {
> 940: size_t result = available(ShenandoahHeap::heap()->soft_max_capacity() * (100.0 - ShenandoahEvacReserve) / 100);
I'm a little uncomfortable with this approach. It's mostly a question of how we name it. The evac reserve is not always this value. In particular, we may shrink the young evac reserves after we have selected the cset. Also of concern is that if someone invokes this function on old_generation(), it looks like they'll get a bogus (not meaningful) value.
I think I'd be more comfortable with naming this to something like "mutator_available_when_gc_is_idle()". If we keep it virtual, then OldGeneration should override with "assert(false, "Not relevant to old generation")
-------------
PR Review: https://git.openjdk.org/jdk/pull/28622#pullrequestreview-3546162874
PR Review Comment: https://git.openjdk.org/jdk/pull/28622#discussion_r2593766590
More information about the shenandoah-dev
mailing list