RFR (L): 8244603 and 8238858: Improve young gen sizing

stefan.johansson at oracle.com stefan.johansson at oracle.com
Thu Jun 11 07:51:00 UTC 2020

Hi Thomas,

Sorry for not getting to this sooner.

On 2020-05-19 15:37, Thomas Schatzl wrote:
> Hi all,
>    can I have reviews for this change that improves young gen sizing a 
> lot to prepare for heap shrinking during young gc (JDK-8238687) ;)
> In particular, with this change the following issues two related issues 
> are fixed:
> * 8238858: G1 Mixed gc young gen sizing might cause the first mixed gc 
> to immediately follow the prepare mixed gc
> * 8244603: G1 incorrectly limiting young gen size when using the reserve 
> can result in repeated full gcs
> These have been grouped together because it is too hard to separate them 
> out as the bugs required significant rewrite in the young gen sizing.
> This results in G1 following GCTimeRatio much better than before, 
> leading to less erratic heap expansion. That is, constant loads do not 
> result in that much overcommit any more.
> Some background:
> At end of gc and when the remembered set sampling thread (re-)evaluates 
> the young gen G1 calculates two values:
> - the *desired* (unconstrained) size of the young gen; 
> desired/unconstrained meaning that these values are not limited by 
> actually existing free regions. This value is interesting for adaptive 
> IHOP so that it (better) converges to a fixed value. (There is some 
> bugfix, JDK-8238163, coming for this to fix a few issues with that)
> - the actual *target* size for the young gen, i.e. after taking 
> constraints in available free regions into account, and whether we are 
> currently already needing to use parts of the reserve or not.
> Some problems that were fixed with the current code:
> - during calculation of the *desired* young gen length G1 basically 
> sizes the young gen during mixed gc to the minimum allowed size always. 
> This causes unnecessary spikes in short/long term ratios, causing lots 
> of heap increases even with a constant load.
> Since we do not shrink the heap yet during regular gcs, this typically 
> ended up in fully expanding the heap (unless the reclamations during 
> Remark actually reclaimed something, but the equilibrium of committed 
> heap between these two mechanisms is much higher).
> E.g. on specjbb2015 fixed IR (constant load) with "small" and "medium" 
> load G1 will use half the heap now while staying < GCTimeRatio.
> - at end of gc g1 calculates the young gen for the *next* gc, i.e. 
> during the prepare mixed gc g1 should already use the "reduced" amount 
> of regions (that's JDK-8238858); similarly the *last* mixed gc in the 
> mixed gc phase should already use the calculation for the young phase. 
> The current code does not. This partially fixes some "at end of mixed gc 
> it takes a while for g1 to achieve previous young gen size again" issues.
> (There is a CR for that, but as mentioned, this change does not 
> completely solve it).
> - there were some calculations to ensure that "at least one region will 
> be allocated" every time g1 recalculates young gen but that really 
> resulted in g1 increasing the young gen by at least one. You do not 
> always want that, particularly since we regularly revise the young gen. 
> What you want is a minimum desired *eden* size.
> - the desired and target young gen size calculation was done in a single 
> huge method. This change splits up the code a bit.
> - the code that calculated the actual *target* young length has been 
> very inconsistent in handling the reserve. I.e. the limit to the 
> actually available free regions only applied in some cases, and notably 
> not if you were already using the reserve, causing strange behavior 
> where at least the calculated young gen target length was higher than 
> available free regions. This could cause full gcs.
> - I added some detailed trace-level logging for the ergonomic decisions 
> which really helps when debugging issues, but might be too 
> large/intrusive for the code - I am not sure whether to leave it in.
> Reviewing: I think the best entry point for this change is 
> G1Policy::update_young_list_target_length(size_t) where the new code 
> first calculates a desired young gen length and then target young length.
> Some additional notes:
> - eden before a mixed gc is calculated by predicting the time for the 
> minimum amount of old gen regions we will definitely take 
> (min_old_cset_length) and then letting eden fill up the remainder.
> Often this results in significantly larger young gens than before this 
> change, at worst young gen will be limited to minimum young gen size (as 
> before). Overall it works fairly well, i.e. gives much smoother cpu 
> usage. There is a caveat to that in that it depends on accuracy of 
> predictions. Since G1 predictions are often too high, we might want to 
> take more a lot more optional regions in the future to not be required 
> to early terminate the mixed gc
> I.e. I have often seen that we do not use up the 200ms pause time goal.
> - currently, and like before, MMU desired length overrides the pause 
> time desired length. I.e. *if* a GCPauseTimeIntervalMillis is set, the 
> spacing between gcs is more important than actual pause times. The 
> difference is that now that there is an explicit comment about this 
> behavior there :)
> - when eating into the reserve (last 10% of heap), we at most allow use 
> of the sizer's min young length or half of the reserve regions (rounded 
> up!), whichever is higher. This is an arbitrary decision, but since 
> supposedly at that time we are already close to the next mixed gc due to 
> adaptive ihop, so we can take more.
> - note that all this is about calculating a *target* length, not the 
> actual length. Actual length may be higher e.g. due to gclocker.
> - there may be some out-of-box performance regressions since G1 does not 
> expand the heap that much more. Performance can be restored by either 
> decreasing GCTimeRatio, or better setting minimum heap sizes.
> Actually, in the future, when shrinking is implemented (JDK-8238687), 
> these may be more severe (in some benchmarks, actual gc usage is still 
> <2%). I will likely try to balance that with decreasing default 
> GCTimeRatio value in the future.
> CR:
> https://bugs.openjdk.java.net/browse/JDK-8244603
> https://bugs.openjdk.java.net/browse/JDK-8238858
> Webrev:
> http://cr.openjdk.java.net/~tschatzl/8244603/webrev/
Very nice change Thomas, really helpful with all comments.

As I've mentioned to you offline I think we can re-structure the code a 
bit, to separate the updating of young length bounds from the returning 
of values. Here's a suggestion on how to do that:

  226 double G1Analytics::predict_alloc_rate_ms() const {
  227   if (!enough_samples_available(_alloc_rate_ms_seq)) {
  228     return predict_zero_bounded(_alloc_rate_ms_seq);
  229   } else {
  230     return 0.0;
  231   }
  232 }

As discussed, on line 227 the ! should be removed.

Apart from this I think it is all good. There are a few places in 
g1Policy.cpp where local variables could be either merged or skipped, 
but I think they add to the overall ease of understanding.


> Testing:
> mach5 tier1-5, perf testing
> Thanks,
>    Thomas

More information about the hotspot-gc-dev mailing list