JDK-6888336 : G1: avoid explicitly marking and pushing objects in survivor spaces
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: gc
  • Affected Version: hs17
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2009-10-05
  • Updated: 2013-09-18
  • Resolved: 2012-03-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7 JDK 8 Other
7u4Fixed 8Fixed hs23Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
During marking, when we move live objects during evacuation pauses, we actually have to propagate their marks along with them and also notify the marking threads that those objects have been moved.

Consider that, during a marking cycle, we might do several evacuation pauses. It's likely that most of the objects that we copy to the survivor spaces would be objects that were allocated after the cycle started (i.e., all the objects copied from the eden to survivors during marking and after the initial-mark pause). But, according to the SATB invariant, we do not actually have to visit objects allocated after the snapshot has been taken. In fact, the only objects the marking phase really needs to visit in the survivor regions during marking are all the objects that were originally located in the survivor regions at the end of the initial-mark pause.

Unfortunately, given that we do not segregate objects in regions according to age, it's not straightforward to keep track of which objects should be explicitly marked and visited by the marking threads and which were allocated during the marking cycle and we should consider them implicitly live.

But, it would be a nice performance improvement to arrange it so that we avoid marking and scanning all survivor objects allocated during the marking cycle. The pause itself would be faster, as no object marking would be necessary and the marking threads would also have less overhead, as they would not have to deal with objects being moved during each GC pause.

EVALUATION http://hg.openjdk.java.net/lambda/lambda/hotspot/rev/2ace1c4ee8da

EVALUATION http://hg.openjdk.java.net/hsx/hotspot-rt/hotspot/rev/2ace1c4ee8da

SUGGESTED FIX Some implementation notes (from the code review request e-mail I sent out): The actual changes are not too extensive as they basically mostly disable functionality in the GC code. The tricky part was to get the TAMS fields correct at various phases (start of copying, start of marking, etc.) and especially when an evacuation failure occurs. I put all that functionality in methods on HeapRegion which do the right thing when a GC starts, a marking starts, etc. The most important changes are in the "main" GC code, i.e. G1ParCopyHelper::do_oop_work() and G1ParCopyHelper::copy_to_survivor_space(). Instead of having to propagate marks we only now need to mark objects directly reachable from roots during the initial-mark pause. The resulting code is much simplified (and hopefully more performant!). I also added a method verify_no_cset_oops() which checks that indeed all the marking data structures do not point to regions that are being GCed at the start / end of each GC. (BTW, I'm considering adding a develop flag to enable this on demand.)

PUBLIC COMMENTS The changes mostly involved removing functionality that we had during GCs to deal with propagating the marks for objects we copy during a concurrent cycle. They leave around 1,500 lines of dead code. We decided not to remove the dead code as part of this CR to make the code review process easier by keeping the number of lines changed to a minimum. The dead code will be removed in the future as a separate CR: 7127697: G1: remove dead code after recent concurrent mark changes In the initial version of this change we'll adopt the "promote all" policy during initial-mark pauses which is essentially a "big hammer" approach in dealing with the survivor objects that already exist at the start of a concurrent marking cycle (i.e., there won't be any). I opened 7127706: G1: re-enable survivors during the initial-mark pause to re-enable the survivors during initial-mark and deal with them correctly.

PUBLIC COMMENTS The obvious benefits from this change are: - No mark propagation as we copy objects during GCs: All objects we copy are (implicitly) live and do not need to be visited by the marking threads. So, we only need to make sure they are placed over NTAMS. - Minimized interaction between GC and marking: The initial-mark pause just needs to mark all the roots and after that there's nothing else to do. So, no pushes on stacks so that the marking threads visit some newly-copied objects. An additional benefit from this is that each GC will not create more work for the marking threads, so marking cycles could be shorter. There are additional (and somewhat subtle) benefits from this change: - No references to moving objects on the marking stacks (taskqueues / global stack): Given that all objects that we copy during a GC are implicitly live (Eden objects by default, Survivor objects due to this change) they will not be pushed on the marking stacks (we only push objects that we explicitly mark). So, no need to treat the marking stacks as roots any more, which cuts down on the amount of work we need to do during a GC. - No references to moving objects on the enqueued SATB buffers: When a thread fills up an SATB buffer it first filters it and removes all entries that point to live objects (and as a result: objects that will be copied during the next GC for the reasons described in the previous bullet point) before enqueueing it. So, again, we don't have to process the enqueued SATB buffers during each GC. - Minimized work for the active per-thread SATB buffers: Those buffers might contain entries that point to moving objects as the write barrier is unconditional and those buffers have not been filtered yet. However, to keep the work that needs to be done to a minimum, we can just filter them and not process them as we do now.

EVALUATION http://hg.openjdk.java.net/hsx/hotspot-gc/hotspot/rev/2ace1c4ee8da

EVALUATION Originally, we were setting NTAMS to bottom on the survivor regions to implicitly mark all objects in them. This is incorrect, as it was preventing the scanning of objects that resided in the survivor regions during the initial-mark pause (see CR 6847956 for more info on this). The above would only work if, during the initial-mark pause, we actually scanned all the objects that we copy to the survivors and mark everything reachable from them. This way they would be made part of the snapshot and not need to be visited further. Also note that, since the marking phase has not started yet, all the "fingers" are at the bottom of the heap, therefere we would not even need to push anything on the region stack, given that the marking threads would visit those as they start moving the fingers forward. Another way to avoid marking any objects during the marking cycle is to adopt a "promote all" policy for the initial-mark pause. This way, the survivor regions would be empty at the end of that pause and we would not need to mark any objects during the pause (as long as we don't do partially-young pauses!!!). It'd be sufficient to set NTAMS to top() when we allocate each GC alloc region.