MetaspaceClosure is used to traverse all *pointers* in the metaspace objects. It works recursively.
To avoid overflowing the C callstack due to excessively deep hierarchies, we use a side stack and limit the recursion to no more than 5 levels deep.
https://github.com/openjdk/jdk/blob/5d8ba938bef162b74816147eb1002a0620a419ba/src/hotspot/share/memory/metaspaceClosure.cpp#L35-L44
void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref) {
if (_nest_level < MAX_NEST_LEVEL) { // == 5
do_push(ref);
delete ref;
} else {
do_pending_ref(ref);
ref->set_next(_pending_refs);
_pending_refs = ref;
}
}
If we have objects that look like this
A->B->C->D->E->F->G
where
(A->B) means A has a field A.fa that points to B
(B->C) means B has a field B.fb that points to C
...
(F->G) means F has a field F.ff that points to G
We call MetaspaceClosure::push() six times to record the pointers of
A->B, B->C, C->D, ..., F->G
Each of these pointers are represented by a MetaspaceClosure::Ref object.
When visiting C->D, we call MetaspaceClosure::push() with C->D immediately (nesting level is smaller than 5). Inside push(), we call MetaspaceClosure::enclosing_ref() to find C, and mark the pointer C.fc.
When visiting F->G, the nesting level is too deep, so we save the Ref of F->G into MetaspaceClosure::_pending_refs (the "else" block in the above code snippet). However, as soon as we return from push_impl, we lose the fact that F.ff is pointing to G.
To work around this, our hack is to call do_pending_ref(), which can mark the F.ff pointer.
====================================
This is too complicated. It can be simplified by remember the address of F in the MetaspaceClosure::Ref that represents the F->G relationship.
It's kind of odd that we don't do that already. In the Ref, we already remember the address of the pointer (i.e., Ref::mpp() == &F.ff). We should just as well remember F itself.
To avoid overflowing the C callstack due to excessively deep hierarchies, we use a side stack and limit the recursion to no more than 5 levels deep.
https://github.com/openjdk/jdk/blob/5d8ba938bef162b74816147eb1002a0620a419ba/src/hotspot/share/memory/metaspaceClosure.cpp#L35-L44
void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref) {
if (_nest_level < MAX_NEST_LEVEL) { // == 5
do_push(ref);
delete ref;
} else {
do_pending_ref(ref);
ref->set_next(_pending_refs);
_pending_refs = ref;
}
}
If we have objects that look like this
A->B->C->D->E->F->G
where
(A->B) means A has a field A.fa that points to B
(B->C) means B has a field B.fb that points to C
...
(F->G) means F has a field F.ff that points to G
We call MetaspaceClosure::push() six times to record the pointers of
A->B, B->C, C->D, ..., F->G
Each of these pointers are represented by a MetaspaceClosure::Ref object.
When visiting C->D, we call MetaspaceClosure::push() with C->D immediately (nesting level is smaller than 5). Inside push(), we call MetaspaceClosure::enclosing_ref() to find C, and mark the pointer C.fc.
When visiting F->G, the nesting level is too deep, so we save the Ref of F->G into MetaspaceClosure::_pending_refs (the "else" block in the above code snippet). However, as soon as we return from push_impl, we lose the fact that F.ff is pointing to G.
To work around this, our hack is to call do_pending_ref(), which can mark the F.ff pointer.
====================================
This is too complicated. It can be simplified by remember the address of F in the MetaspaceClosure::Ref that represents the F->G relationship.
It's kind of odd that we don't do that already. In the Ref, we already remember the address of the pointer (i.e., Ref::mpp() == &F.ff). We should just as well remember F itself.