CRR (L): 6888336: G1: avoid explicitly marking and pushing objects in survivor spaces
Tony Printezis
tony.printezis at oracle.com
Wed Dec 21 22:37:44 UTC 2011
Hi all,
I'd like a couple of code reviews for the following non-trivial changes
(large, not necessary in lines of code modified but more due to the fact
that the evacuation pause / concurrent marking interaction is changed
quite dramatically):
http://cr.openjdk.java.net/~tonyp/6888336/webrev.0/
Here's some background, motivation, and a summary of the changes (I felt
that it was important to write a longer then usual explanation):
* Background / Motivation
Each G1 heap region has a field top-at-mark-start (aka TAMS) which
denotes where the top of the region was when marking started. An object
is considered implicitly live if it's over TAMS (i.e., it was allocated
since marking started) or explicitly live if it's below TAMS (i.e., it
was allocated before marking started) and marked on the bitmap. (It
follows that it's unnecessary to explicitly mark objects over TAMS.)
In fact, we have two copies of the above marking information: "Next TAMS
/ Next Bitmap" and "Prev TAMS / Prev Bitmap". Prev is the copy that was
obtained by the last marking cycle that was successfully completed (so,
it is consistent: all live objects should appear as live in the prev
marking information). Next is the copy that will be obtained / is
currently being obtained and it's not consistent because it's not
guaranteed to be complete.
G1 uses SATB marking which has the advantage not to require objects
allocated since the start of marking to be visited at all by the marking
threads (they are implicitly live and they do not need to be scanned).
So, the active marking cycle can totally ignore objects over NTAMS
(since they have been allocated since marking started).
The current interaction between evacuation pauses (let's call these
"GCs" from now on) and concurrent marking is very tricky. Even though
marking ignores all objects over NTAMS (currently: all objects in Eden
regions) it still has to visit and mark objects in the Survivors
regions. But those will be moved by subsequent GCs. So, a GC needs to be
aware that it's moving objects that have been marked by the marking
threads and not only propagate those marks but also notify the marking
threads that said objects have been moved. For that we use several data
structures: pushes to the global marking stack and also to what's
referred to as the "region stack" which is only used by the GC to push a
group of objects instead of pushing them individually ("region" here is
a mem region and smaller than a G1 region).
Additionally, because the marking threads could come across objects that
could potentially move we have to make sure that we don't leave
references to regions that have been evacuated on any marking data
structure. To do that we treat as roots all entries on the taskqueues /
global stack and drained all SATB buffers (both active buffers and also
enqueued buffers).
The first issue with the above interaction is that it has performance
issues. Draining all SATB buffers and scanning the mark stack and
taskqueues has been shown to be very time-consuming in some cases. Also,
having to check whether objects are marked and propagate the marks
appropriately during GC is an extra overhead.
The second issue is that it has been shown to be very fragile. We have
discovered and fixed many issues over time which were subtle and hard to
reproduce.
We really need to simplify the GC/marking interaction to both improve
performance of GCs during marking, as well as improve our reliability.
This changeset does exactly that.
* Explanation of the changes
The goal is to ensure that all the objects that are copied by the GC do
not need to be visited by the marking threads and as a result do not
need to be explicitly marked, pushed, etc.
The first observation is that most objects copied during a GC are
allocated after marking starts and are therefore implicitly live. This
is the case for all objects on Eden regions, as well as most objects on
Survivor regions. The only exception are objects on the Survivor regions
during the initial-mark pause. Unfortunately, it's not easy to track
those separately as they will get mixed in with future Survivors. The
first decision to deal with this is to turn off Survivors during the
initial-mark pause. This ensures that all objects copied during each
subsequent GC will only visit objects that have been allocated since
marking started and are therefore implicitly live (i.e., over NTAMS).
This allows us to totally eliminate that code that propagates marks
during the GC. We just have to make sure that all copied objects are
over NTAMS. Turning off Survivors during an initial-mark pause is a bit
of a "big hammer" approach, but it will suffice for now. We have ideas
on how to re-enable them in the future and we'll explore a couple of
alternatives.
Given that the GC only copies objects that are implicitly marked it
follows that none of the objects that are copied during any GC should
appear on either the taskqueues nor the global marking stack. Also
remember that we filter SATB buffers before enqueueing them which will
filter out all implicitly marked objects. It follows that no enqueued
SATB buffer should have references to objects that are being moved. This
leaves the currently active SATB buffers given that the code that
populates them is unconditional. But if we run the filtering on those
during each GC such "offending" references are also quickly eliminated.
So, instead of having to scan all stacks and all SATB buffers we only
have to filter the active SATB buffers, which should be much, much faster.
* Implementation Notes
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.)
I should point out that this changeset will leave a lot of dead code.
However, I took the decision to keep the changes to a minimum in order
not overwhelm the code reviewers and make the important changes clearer.
(I also discussed this with a couple of potential code reviewers and
they agreed that this is a good approach.) I temporarily added
guarantees to ensure that methods that should not be called are not
called. I will remove all dead code with a future push.
I also have to apologize to John Cuthbertson for removing a lot of code
he's added to deal with various bugs we had in the GC/marking
interaction. Hopefully the new code will be less fragile compared to
what we've had so far and John will be able to concentrate on more
interesting features than trying to track down hard-to-reproduce failures!
Tony
More information about the hotspot-gc-dev
mailing list