Eden Sizing | |
There are limits to the size that Eden is allowed to be. Those limits are calculated in calculate_default_min_length: | |
G1YoungGenSizer::recalculate_min_max_young_length -> G1YoungGenSizer::calculate_default_min_length | |
G1CollectorPolicy::update_young_list_target_length is responsible for | |
determining the actual size of the young list. If doing a young GC, it will | |
call G1CollectorPolicy::calculate_young_list_target_length. This function | |
attempts to find the largest eden size that will allow (predicted) GC pauses to | |
remain under the pause time target. Presumably after a certain eden size, there | |
is too much for a given young collection to clean up in eden. | |
http://stackoverflow.com/questions/36345409/why-does-g1gc-shrink-the-young-generation-before-starting-mixed-collections | |
All of this is different in mixed collections. It appears that mixed GCs make | |
Eden as small as possible, attempt to clean up the tenured generation, and then | |
get back to "normal" operation. | |
uint young_list_target_length = 0; | |
if (gcs_are_young()) { | |
young_list_target_length = | |
calculate_young_list_target_length(rs_lengths, | |
base_min_length, | |
desired_min_length, | |
desired_max_length); | |
_rs_lengths_prediction = rs_lengths; | |
} else { | |
// Don't calculate anything and let the code below bound it to | |
// the desired_min_length, i.e., do the next GC as soon as | |
// possible to maximize how many old regions we can add to it. | |
} | |
The comment there is indicative: apparently G1 wants frequent GCs during mixed | |
collections. The (allocation rate / size of Eden) is directly proportional to | |
GC frequency, so G1GC chooses a small young gen. Once young_list_target_length | |
is chosen, the JVM applies its static rules (like G1NewSizePercent). So we | |
should expect a 5% young gen during mixed collections. | |
Backpressure | |
G1GC has "Concurrent Refinement Threads", which are apparently instrumental for | |
identifying garbage in mixed collections. Mutator threads (i.e. Java code) | |
create "dirty cards", which are placed in a queue. Based on the queue size, | |
G1GC launches some number of threads to process the queue. Initially, no | |
threads process the queue: the work is deferred until a STW pause. When the | |
number of items reaches the threshold of "the green zone" | |
(G1ConcRefinementGreenZone), the JVM begins creating threads. Once there are | |
G1ConcRefinementYellowZone (default: 2*G1ConcRefinementGreenZone) items in the | |
queue, all threads are active. The number of threads active increases | |
linearly(?) as the number of queue items progresses through the green zone. All | |
refinement threads remain active up to the G1ConcRefinementRedZone (default: | |
3*G1ConcRefinementYellowZone) threshold. In the red zone, the refinement | |
threads are considered to be "falling behind", and the JVM forces threads | |
adding to the queue to do the G1 refinement work themselves. | |
The red zone provides backpressure, preventing the refinement threads from | |
falling too far behind. This is good! There is an adaptive tuning algorithm | |
which adjusts the zone thresholds based on how much time was spent doing | |
refinement during STW pauses (controlled by G1UseAdaptiveConcRefinement, | |
default true). On each STW pause, if refinement took longer than a goal amount | |
(G1RSetUpdatingPauseTimePercent, default 10) of the total G1 pause goal time | |
(default 200ms), the thresholds are adjusted down (by 10%). If it took less | |
than the goal amount, the thresholds are adjusted up (by 1.1x). | |
const int k_gy = 3, k_gr = 6; | |
const double inc_k = 1.1, dec_k = 0.9; | |
int g = cg1r->green_zone(); | |
if (update_rs_time > goal_ms) { | |
g = (int)(g * dec_k); // Can become 0, that's OK. That would mean a mutator-only processing. | |
} else { | |
if (update_rs_time < goal_ms && update_rs_processed_buffers > g) { | |
g = (int)MAX2(g * inc_k, g + 1.0); | |
} | |
} | |
// Change the refinement threads params | |
cg1r->set_green_zone(g); | |
cg1r->set_yellow_zone(g * k_gy); | |
cg1r->set_red_zone(g * k_gr); | |
cg1r->reinitialize_threads(); | |
My problem with the above code is that (to mine eyes), it does not include any | |
limits. If allocation conditions change, then this can only react to the tune | |
of 10% on each GC pause. Given that I can find no limits on how high the | |
thresholds can go when the goal is met repeatedly, this means that the heap can | |
be 100% full with garbage without all of the concurrent refinement threads even | |
running. As far as I can tell, this could mean that backpressure fails to work | |
entirely, causing heap fillup and a single threaded Full GC. It may be better | |
to disable G1UseAdaptiveConcRefinement instead. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment