Lock-based vs lock-free MethodCounters initialization
John Rose
john.r.rose at oracle.com
Mon May 12 19:17:24 UTC 2014
Lock-free should be our default if we think contention is rare, for reasons already given.
BUT, we should back up our intuition with a measurement, such as a pair of counters that track successful vs. failed CAS events. We can then detect unexpectedly frequent failed CAS events and treat them as a possible bug.
Since we are noticing occasional fragmentation problems, we can also correlate them to those new counters. If fragmentation is a risk, we need to detect it routinely and mechanically. Mikael G., which metrics do we use to detect excessive holes in the metaspace heap?
— John
On May 12, 2014, at 11:56 AM, Vladimir Ivanov <vladimir.x.ivanov at oracle.com> wrote:
> Thanks, Jiangli & David.
>
> Yes, I agree that more understanding of the behavior will be very helpful. I'll try to come up with a stress test and run some benchmarks to see how common is the contention during allocation.
>
> Best regards,
> Vladimir Ivanov
>
> On 5/12/14 8:51 PM, Jiangli Zhou wrote:
>> Hi Vladimir,
>>
>> I worked on MethodCounters allocation and planned to look into the
>> possible memory leak at some point, but haven't gotten to it. Thanks for
>> looking into this! The possible contention of a MethodCounters
>> allocation only happens when the method is being executed the first
>> time. I did some quick profiling and determined that it was rare. If
>> that was the case, metspace fragmentation due to deallocations of
>> MethodCounters followed by failed CAS would also be uncommon. The
>> lock-free based solution seems to be the safer and more efficient one to
>> go for.
>>
>> I see David and Mikael already responded to your question already. Both
>> of them provided very good information. Maybe it's worth to do some
>> profiling first?
>>
>> Thanks,
>>
>> Jiangli
>>
>> On 05/12/2014 04:19 AM, Vladimir Ivanov wrote:
>>> 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