Lock-based vs lock-free MethodCounters initialization

Vladimir Ivanov vladimir.x.ivanov at oracle.com
Mon May 12 11:19:04 UTC 2014


Hi,

There's a possible memory leak when allocating MethodCounters 
concurrently [1], because there's no coordination between concurrent 
counters initialization actions. In constrast with MethodCounters, 
MethodData allocation is guarded by MethodData lock.

I'm thinking about proper fix and considering 2 options: lock-based 
(reuse MethodData_lock or introduce MethodCounters_lock) and lock-free 
(do a CAS on _method_counters field) [3].

The problem with lock-based approach is that there's a possible deadlock 
with pending list lock (see JDK-6988439 [4] for details), so I need to 
check whether PLL is held and abort initialization in case it is 
(similar to what Method::build_interpreter_method_data does before 
acquiring the lock [2]).

Lock-free solution doesn't suffer from deadlocks, but it can increase 
Metaspace fragmentation due to failed CAS updates and subsequent 
deallocations.

I'm in favor of lock-free solution, but not sure about how serious 
fragmentation issue is in NPG world. What do you think?

Thanks!

Best regards,
Vladimir Ivanov

[1] 
http://hg.openjdk.java.net/jdk9/hs-comp/hotspot/file/tip/src/share/vm/oops/method.cpp#l384

MethodCounters* Method::build_method_counters(Method* m, TRAPS) {
   methodHandle mh(m);
   ClassLoaderData* loader_data = mh->method_holder()->class_loader_data();
   MethodCounters* counters = MethodCounters::allocate(loader_data, 
CHECK_NULL);
   if (mh->method_counters() == NULL) {
     mh->set_method_counters(counters);
   } else {
     MetadataFactory::free_metadata(loader_data, counters);
   }
   return mh->method_counters();
}

[2] 
http://hg.openjdk.java.net/jdk9/hs-comp/hotspot/file/tip/src/share/vm/oops/method.cpp#l360

void Method::build_interpreter_method_data(methodHandle method, TRAPS) {
   // Do not profile method if current thread holds the pending list lock,
   // which avoids deadlock for acquiring the MethodData_lock.
   if (InstanceRefKlass::owns_pending_list_lock((JavaThread*)THREAD)) {
     return;
   }

   // Grab a lock here to prevent multiple
   // MethodData*s from being created.
   MutexLocker ml(MethodData_lock, THREAD);
   if (method->method_data() == NULL) {
     ClassLoaderData* loader_data = 
method->method_holder()->class_loader_data();
     MethodData* method_data = MethodData::allocate(loader_data, method, 
CHECK);
     method->set_method_data(method_data);
     if (PrintMethodData && (Verbose || WizardMode)) {
       ResourceMark rm(THREAD);
       tty->print("build_interpreter_method_data for ");
       method->print_name(tty);
       tty->cr();
       // At the end of the run, the MDO, full of data, will be dumped.
     }
   }
}

[3] Possible lock-free solution:
MethodCounters* Method::build_method_counters(Method* m, TRAPS) {
   methodHandle mh(m);
   if (mh->method_counters() != NULL)  return mh->method_counters();
   ClassLoaderData* loader_data = mh->method_holder()->class_loader_data();
   MethodCounters* counters = MethodCounters::allocate(loader_data, 
CHECK_NULL);
   MethodCounters* old = (MethodCounters*)Atomic::cmpxchg_ptr(counters, 
&_method_counters, NULL);
   if (old != NULL) {
     MetadataFactory::free_metadata(loader_data, counters);
   }
   return mh->method_counters();
}

[4] https://bugs.openjdk.java.net/browse/JDK-6988439


More information about the hotspot-dev mailing list