1 min read

How does the JVM change tenuring thresholds, and when?

On a recent Tuesday evening I ran into this 3 year old JVM question in Stack Overflow. GC features and its many knobs are profusely documented (search adaptive policy, tenuring threshold..) but asking too many questions inevitably leads to hitting the confines of the proverbial black box. Yeah, tenuring thresholds are dynamic.. but how, why and when do they change?

Luckily, the JVM codebase is there to take a peek. GC stuff can be found under src/share/vm/gc_implementation.

According to ageTable.cpp L81 and next (method compute_tenuring_threshold) the JVM will iterate through each age and add up the size of objects with that age. As soon as it exceeds the desired_survivor_size it’ll stop and assume the last age it got to as the candidate new threshold. The new chosen threshold is min(candidateAge, MaxTenuringThreshold).

compute_tenuring_threshold is called in G1 from g1CollectorPolicy.cpp L1459 which choses a _max_survivor_regions based on ceil(_young_list_target_length / SurvivorRatio) before calling the compute method mentioned above.

That young_list_target_length is updated in g1CollectorPolicy.cpp as explained in the source:

587   // Update the young list target length either by setting it to the
588   // desired fixed value or by calculating it using G1's pause
589   // prediction model. If no rs_lengths parameter is passed, predict
590   // the RS lengths using the prediction model, otherwise use the
591   // given rs_lengths as the prediction.
592   void update_young_list_target_length(size_t rs_lengths = (size_t) -1);

I’ll leave the model for another day, but this should already answer the first question: tenuring threshold changes are triggered after a lower number of ages is enough to keep the desired_survivor_size (which AFAIK is explained here). The new threshold is chosen based on given / predicted RS (Remember Set) lengths.

This process happens right at the beginning of a new collection pause, as described in g1CollectorPolicy.cpp L839:

839   // We only need to do this here as the policy will only be applied
840   // to the GC we're about to start. so, no point is calculating this
841   // every time we calculate / recalculate the target young length.
842   update_survivors_policy();

I understand that the threshold will thus be updated before the GC runs. If that’s the case, as live objects in survivor regions are visited, all objects that have object.age > newAge will be tenured (including those that had age < threshold at a previous collection, but now exceed it).

PRs with corrections are very welcome.