CRR (M): 7132029: G1: mixed GC phase lasts for longer than it should

Tony Printezis tony.printezis at oracle.com
Wed Feb 15 10:36:56 PST 2012


Bengt,


(We discussed this over the phone, but for the record) move_to_next() is 
not quite descriptive either, give that the region is actually removed. 
I left the remove() method as is but renamed it 
remove_and_move_to_next() as a compromise.

Tony

On 02/15/2012 09:28 AM, Bengt Rutisson wrote:
>
> Tony,
>
> Very short reply inline.
>
> On 2012-02-15 14:47, Tony Printezis wrote:
>> Bengt,
>>
>> Thanks for looking at this at such short notice! Inline.
>>
>> On 02/15/2012 03:46 AM, Bengt Rutisson wrote:
>>>
>>> Hi Tony,
>>>
>>> I really like this cleanup! Thanks for fixing it.
>>>
>>> Some comments:
>>>
>>> collectionSetChooser.hpp:
>>>
>>> _numMarkedRegions is kind of a confusing name to me. It does not 
>>> contain all the marked regions. 
>>
>> Well, I left it as it was. And it's also not called 
>> _numAllMarkedRegions. :-) How about I just call it _length (and 
>> _curMarkedIndex -> _curr_index)?
>
> OK.
>
>>
>>> It contains all the old regions that we would like to include in the 
>>> collection set. How about _num_selected_regions or even 
>>> _num_selected_old_regions?
>>>
>>> I think the remove(HeapRegion* hr) method is kind of strange. I 
>>> understand the need for peek() from finalize_cset(), but I think it 
>>> is strange that the remove() dependes on the code first calling 
>>> peek(). I'd prefer if we could make this more iterator style or at 
>>> least change remove() to someting like pop() that will return the 
>>> next HeapRegion instead of taking it as a parameter.
>>
>> Well, peek() and remove() are only called from the loop in 
>> finalize_cset(), so they are tailored for that. I originally had 
>> remove() with no parameters that just removed the current region. I 
>> then thought of passing the region as a parameter mainly to do sanity 
>> testing (i.e., the region that it's being removed is the one we think 
>> it should be removed). And I then realized that, given we already 
>> have the region, there's no point in actually re-reading it from the 
>> array. Si we . I could generalize remove() to accept hr optionally, 
>> but given that we only use it when we know what the region to be 
>> removed is, I don't see the point. Also, I'm not sure that using 
>> HeapRegion* pop() is appropriate either, given that the caller 
>> doesn't need to be given the region that just got removed (it already 
>> knows which one it is).
>>
>> Also note that the peek() / remove() pattern is similar to the 
>> ListIterator class in Java which has a E next() (equivalent to 
>> peek()) and void remove(). I just added extra information to the 
>> remove() method. (What I'm trying to say is: there's precedence to 
>> such a pattern.)
>
> I'm sorry. I still don't like the remove() method. Can we inline the 
> "_remainingReclaimableBytes -= hr->reclaimable_bytes();" where we now 
> call remove() and then call remove() something like move_to_next() 
> without any HeapRegion at all? Not sure that this was a good 
> suggestion...
>
> Bengt
>
>>
>>>
>>> collectionSetChooser.cpp:
>>>
>>> Why is the method CollectionSetChooser::updateAfterFullCollection() 
>>> needed? Can't G1CollectorPolicy::record_full_collection_end() do 
>>> _collectionSetChooser->clearMarkedHeapRegions() just like 
>>> G1CollectorPolicy::record_concurrent_mark_cleanup_end() does?
>>
>> I'm OK either way. Maybe we used to do more in 
>> updateAfterFullCollection() once upon the time but we don't do any 
>> more. I'll remove it and replace it with a direct call to 
>> clearMarkedHeapRegions() as you suggested.
>>
>>> g1_globals.hpp:
>>>
>>> It seems to me that at least the G1MaxMixedGCNum and the 
>>> G1OldCSetRegionThresholdPercent can be things that end users might 
>>> need to tweak until we have the heuristics more thoroughly tested 
>>> out. Would it be ok to make them available in product builds? Maybe 
>>> as experiemental flags?
>>
>> Actually, it might be helpful to expose all four develop flags I 
>> added to the user so that they can control different aspects of the 
>> heuristics:
>>
>> a) G1OldCSetRegionLiveThresholdPercent and 
>> G1OldReclaimableThresholdPercent: control how much free space to 
>> "sacrifice" in order to decrease mixed GC times (BTW, it'd be nice if 
>> we could somehow combine these two.)
>>
>> b) G1MaxMixedGCNum: control how promptly G1 will collect the 
>> candidate old regions: more promptly -> more free space is made 
>> available quicker, less promptly -> each mixed GC might be shorter.
>>
>> c) G1OldCSetRegionThresholdPercent: control how aggressive G1 will be 
>> in collecting old regions during each mixed GC: more aggressive -> 
>> more free space is made available quicker, less promptly -> each 
>> mixed GC might be shorter.
>>
>> (come to think about it, maybe we should think of combining the last 
>> two too!)
>>
>> But before we expose these we need to a) get a better understanding 
>> of what effect each has on G1's performance and b) find better names 
>> for them. :-) (I didn't try very hard, as I assumed that they will be 
>> develop parameters.)
>>
>>> g1CollectorPolicy.cpp:
>>>
>>>       if (next_gc_should_be_mixed("end of young GC")) {
>>>         ergo_verbose0(ErgoMixedGCs,
>>>                       "start mixed GCs",
>>>                       ergo_format_reason("appropriate old regions 
>>> available"));
>>>         set_gcs_are_young(false);
>>>       } else {
>>>         ergo_verbose0(ErgoMixedGCs,
>>>                       "do not start mixed GCs",
>>>                       ergo_format_reason("appropriate old regions 
>>> available"));
>>>       }
>>>
>>> I think the ergo_verbose message in the else case at lines 1490-1492 
>>> should say "appropriate old regions not available", right?
>>
>> Correct, thanks. John also caught that.
>>
>>> We log the ergo decisions in next_gc_should_be_mixed(). Will it not 
>>> be duplicate information to log it in 
>>> G1CollectorPolicy::record_collection_pause_end() as well?
>>
>> That's a good point. This is what the output looks like right now:
>>
>>  3.708: [G1Ergonomics (Mixed GCs) next GC should be mixed, reason: 
>> appropriate old regions available, phase: end of young GC, remaining 
>> old regions: 16 regions, reclaimable: 9803640 bytes (29.22 %), 
>> threshold: 1.00 %]
>>  3.708: [G1Ergonomics (Mixed GCs) start mixed GCs, reason: 
>> appropriate old regions available]
>>  23M->18M(32M), 0.0411940 secs]
>>
>> ...
>>
>>  3.938: [G1Ergonomics (Mixed GCs) next GC should be mixed, reason: 
>> appropriate old regions available, phase: end of mixed GC, remaining 
>> old regions: 12 regions, reclaimable: 5823976 bytes (17.36 %), 
>> threshold: 1.00 %]
>>  3.938: [G1Ergonomics (Mixed GCs) do not end mixed GCs, reason: 
>> appropriate old regions available]
>>  23M->15M(32M), 0.0322850 secs]
>>
>> I'll see how easy will be to parametarize next_gc_should_be_mixed() 
>> with the "do" / "do not" action strings (instead of the phase_str).
>>
>> I'll send out a new webrev in a bit.
>>
>> Tony
>>
>>> Bengt
>>>
>>>
>>> On 2012-02-14 16:18, Tony Printezis wrote:
>>>> Hi all,
>>>>
>>>> I'd like a couple of (quick please!) code reviews for this change:
>>>>
>>>> http://cr.openjdk.java.net/~tonyp/7132029/webrev.0/
>>>>
>>>> The policy that was choosing old regions for collection during 
>>>> mixed GCs was buggy and it could exhibit many strange behaviors, 
>>>> one of which was to go into mixed GC mode, do "mixed GCs" (which 
>>>> they did not actually collect any old regions), and not get out of 
>>>> it for a while while preventing subsequent marking cycles not to 
>>>> start. This frequently caused evac failures and Full GCs.
>>>>
>>>> The logic on which old regions to add to the CSet was spread to 
>>>> many places. I simplified that and put it mainly in the loop in the 
>>>> finalize_cset() method (renamed from choose_collection_set(), 
>>>> "finalize" is more descriptive on what the method does). I think 
>>>> the new version is easier to follow.
>>>>
>>>> Description of the changes:
>>>>
>>>> * I have introduced min and max number of old regions to be added 
>>>> to the CSet of each mixed GC. Max number is calculated as a 
>>>> percentage over the heap size (default: 10% - thanks to Jesper for 
>>>> suggesting to use a heap percentage for this) and ensures that 
>>>> collections will not get super long. Min number is calculated based 
>>>> on a desired mixed GC num after a marking cycle (default: 4) and 
>>>> ensures that each mixed GC will make some progress in collecting 
>>>> old regions (so that the candidate old regions are collected in a 
>>>> timely fashion).
>>>>
>>>> * We now don't add any regions with live percentage over a 
>>>> threshold (default: 95%) to the CSet chooser and we do not consider 
>>>> them for collection.
>>>>
>>>> * I stopped using the cache in the CSet chooser class (it was used 
>>>> to resort regions according to their latest GC efficiency), since 
>>>> it's not necessary any more: we'll now go through all the candidate 
>>>> old regions in the CSet chooser class and we don't have a heuristic 
>>>> of when to stop mixed GCs based on GC efficiency.
>>>>
>>>> * I introduced the notion of "reclaimable bytes" on HeapRegions, 
>>>> which not only includes the predicted garbage bytes on each region, 
>>>> but also the unused space in the area [top,end) which will also be 
>>>> reclaimed. The CSet chooser class now keeps track of the total 
>>>> reclaimable bytes of all the regions that it tracks. If that falls 
>>>> under a certain threshold (default: 1% of the heap) we stop doing 
>>>> mixed GCs as we'll reclaim very little out of the remaining 
>>>> candidate old regions.
>>>>
>>>> * I eliminated the case where a mixed GC starts and picks no old 
>>>> regions to collect (I hope!). Now the information on the CSet 
>>>> chooser (remaining region num / remaining reclaimable bytes) can 
>>>> tell us whether we want to do more mixed GCs or not. If we do, it's 
>>>> guaranteed that there will be old regions to collect. Because of 
>>>> that, I removed the _should_revert_to_young_gcs flag as it's not 
>>>> needed any more. It was used so that the CSet choice code could 
>>>> flag that we should stop doing mixed GCs at the end of the GC. Now, 
>>>> we can decide that by just looking at the information on the CSet 
>>>> chooser (the heuristic is encapsulated in the 
>>>> next_gc_should_be_mixed()).
>>>>
>>>> * I also changed the policy in the case where a fixed young gen is 
>>>> used. Before, we were a bit arbitrary: during mixed GCs we'd cut 
>>>> the young gen size in half and fill up the rest with old regions. I 
>>>> thought that trying to re-use the "desired mixed GC number" 
>>>> heuristic for this made sense. So, I now add the min number of old 
>>>> regions to each mixed GC (as long as we don't go over the max). I 
>>>> think this is a more reasonable and less arbitrary heuristic to 
>>>> what we had before and it's more consistent with the non-fixed 
>>>> young gen policy. I didn't know whether I should decrease the young 
>>>> gen size for each mixed GC, like how we did before, and by how 
>>>> much. So, I now leave it unchanged (the user decided they want a 
>>>> fixed young gen, they will now always get it!).
>>>>
>>>> * Updated all related ergo output to print the new information.
>>>>
>>>> Like the marking changes, this change leaves a fair amount of 
>>>> unused code that we can remove. I had to draw the line somewhere on 
>>>> how much I should remove now and how much I should leave for later. 
>>>> I opened a new CR for the remaining cleanup:
>>>>
>>>> 7145441: G1: collection set chooser-related cleanup
>>>>
>>>> Many thanks to Charlie for doing some last-minute performance 
>>>> testing with my workspace. He was the one who discovered the 
>>>> problem with the never-ending mixed GCs and this fix eliminated the 
>>>> problem. Correctness-testing-wise, I've run overnight with the GC 
>>>> test suite.
>>>>
>>>> Thanks,
>>>>
>>>> Tony
>>>>
>>>>
>>>
>


More information about the hotspot-gc-dev mailing list