JDK-8181170 : resolved_references array leaks for RedefineClasses
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2017-05-27
  • Updated: 2019-05-22
  • Resolved: 2017-08-30
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 10
10 b23Fixed
Related Reports
Relates :  
Description
Currently when we redefine a class, we do not free the resolved_references array for the old class.  This is found via scratch_class -> constants -> cpCache currently.   When scratch_class is no longer needed, ie no old methods are running anymore, the scratch_class is reclaimed via purge_previous_versions.

But the resolved_reference array is also a jobject handle in the ClassLoaderData which is how the GC reaches it.    This reference is not cleared when scratch_class is reclaimed.

There is some GC reason why it is bad to clear out references in the ClassLoaderData::_handles block.
Comments
We were worried about making resolved_references dormant after becoming live with CDS, but this doesn't happen because the resolved_references element isn't cleared if the class is shared because the constant pool isn't deallocated, so the cache isn't either.
29-08-2017

Ok, I see that we have to do a pre barrier for G1 when clearing the _resolved_reference element. The klass_update_barrier_set() call above sets the bit in the Klass that the oops were modified for CMS to rewalk the Klass in the CLD::_klasses list. There's no equivalent for the resolved_references array: see linked bug JDK-8173988.
28-08-2017

I assume that you are going to use this function to clear the entry: void ClassLoaderData::remove_handle_unsafe(jobject h) { assert(_handles.contains((oop*) h), "Got unexpected handle " PTR_FORMAT, p2i((oop*) h)); *((oop*) h) = NULL; } This code is only used by: void ModuleEntry::set_shared_protection_domain(ClassLoaderData *loader_data, Handle pd_h) { // Create a handle for the shared ProtectionDomain and save it atomically. // If someone beats us setting the _pd cache, the created handle is destroyed. jobject obj = loader_data->add_handle(pd_h); if (Atomic::cmpxchg_ptr(obj, &_pd, NULL) != NULL) { loader_data->remove_handle_unsafe(obj); } } To clear an entry to an object we're currently holding a handle to, so during G1 concurrent marking that object is guaranteed to be found alive. For your new usage, you will have to add a G1 pre-barrier to remove_handle_unsafe, to ensure that the resolved_reference object array (and its contents) get marked. Otherwise, other parts of the code might store the object array (or its contents) into an already marked part of the object graph. If that happens, we might end up with an unmarked object and eventually a dangling pointer. This code will be similar to the barrier we use when clearing the java mirror in Klasses: klass_update_barrier_set_pre(p, v); *p = v; klass_update_barrier_set(v); // This barrier is used by G1 to remember the old oop values, so // that we don't forget any objects that were live at the snapshot at // the beginning. This function is only used when we write oops into Klasses. void Klass::klass_update_barrier_set_pre(oop* p, oop v) { #if INCLUDE_ALL_GCS if (UseG1GC) { oop obj = *p; if (obj != NULL) { G1SATBCardTableModRefBS::enqueue(obj); } } #endif }
28-08-2017