RFR: 8212989: Allow CompiledMethod ExceptionCache have unloaded klasses

Erik Österlund erik.osterlund at oracle.com
Mon Nov 5 07:44:07 UTC 2018

Hi Per,

Thanks for the review.

On 2018-11-05 08:34, Per Liden wrote:
> Hi Erik,
> On 11/1/18 11:14 PM, Erik Österlund wrote:
>> Hi Vladimir,
>> Thanks for the quick reply.
>> On 2018-11-01 21:14, Vladimir Kozlov wrote:
>>> Hi Erik,
>>> It seems these  changed are not based on latest sources. I don't see 
>>> call to CodeCache::do_unloading in CodeCache::do_unloading() which is 
>>> present in latest code:
>>> http://hg.openjdk.java.net/jdk/jdk/file/5ea020bcaa0d/src/hotspot/share/code/codeCache.cpp#l683 
>> Yeah this code is taken from the context of the future. Here is the 
>> exact same patch rebased against jdk-jdk:
>> http://cr.openjdk.java.net/~eosterlund/8212989/webrev.01/
>> And new webrev with your suggestions:
>> http://cr.openjdk.java.net/~eosterlund/8212989/webrev.02/
> Looks good to me. Just one minor nit:
> src/hotspot/share/code/codeCache.cpp
> ------------------------------------
> 912       ExceptionCache* free_list_head = 
> Atomic::load(&_exception_cache_purge_list);
> May I suggest that we rename free_list_head to purge_list_head.

Ahh yes. Will do before pushing.


> cheers,
> Per
>> And incremental of that to the jdk-jdk rebase:
>> http://cr.openjdk.java.net/~eosterlund/8212989/webrev.01_02/
>>> May be make some methods private, like 
>>> CompiledMethod::exception_cache_acquire().
>> Sure. Made the accessors protected, as some are used in nmethod too 
>> and it seems fine for subclasses to access.
>>> It seems you remove the only case when release_set_exception_cache() 
>>> is called.
>> That is true. Removed.
>>> I don't see where purge_exception_caches() is used.
>> It's only called by a future version of ZGC from its new concurrent 
>> class unloading feature that is currently baking in the ZGC 
>> repository. The release_exception_cache member function deletes 
>> entries synchronously if cleanup is triggered in a safepoint (all GCs 
>> except ZGC), and otherwise defers deletion to the 
>> purge_exception_caches() method, that is expected to happen one global 
>> handshake operation after the exception caches have been unlinked by 
>> cleanup.
>>> Next sentence seems incomplete:
>>> +  // unlinked. That is also when the 
>>> CodeCache::exception_cache_free_list()
>> Fixed.
>>> Why you use exception_cache_acquire() in clean_exception_cache() and 
>>> simple exception_cache() in add_exception_cache_entry()?
>> The reason is that in the context of insertion, you are holding the 
>> lock protecting against concurrent insertions. Therefore, entries you 
>> load are either entries that were protected by the same lock (other 
>> insertions), or entries that are as old or older than entries that 
>> have died a whole safepoint operation earlier (due to prepending 
>> inserts), which makes its memory state stable. Therefore, no acquire 
>> is required when reading the head in the insertion path.
>> Thanks,
>> /Erik
>>> Thanks,
>>> Vladimir
>>> On 11/1/18 5:58 AM, Erik Österlund wrote:
>>>> Hi,
>>>> The ExceptionCaches of compiled methods is traditionally pruned in 
>>>> safepoints during class unloading. This allows the exception cache 
>>>> to have lock-free reads, while performing inserts under a lock, and 
>>>> pruning under a safepoint. With concurrent class unloading, the 
>>>> exception caches need to be cleaned concurrently.
>>>> To retain the lock-free reads that seem good to keep lock-free, a 
>>>> lock-free cleaning mechanism was introduced. Only one thread per 
>>>> compiled method cleans each exception cache, but that thread races 
>>>> with both single writers (under the ExceptionCache_lock) and any 
>>>> number of readers.
>>>> The head of the exception cache list is concurrently cleaned by both 
>>>> inserting threads and cleaning threads. This allows having an 
>>>> invariant that no newly prepended entries ever produce next pointers 
>>>> to dead exception caches, that are concurrently removed from the 
>>>> list. As for the internal next pointers, they are only pruned by the 
>>>> one concurrent cleaning thread. This happens concurrent to reads 
>>>> that simply skip over dead entries as they have different Klass* 
>>>> pointers to the one being searched for.
>>>> The single concurrent cleanup thread does not delete things removed 
>>>> from the list straight away. Instead, they are placed on a purge 
>>>> list that is freed up after a subsequent global handshaking 
>>>> operation. That allows ABA-free CAS instructions in the lock-free 
>>>> paths, and allows safe concurrent reading of entries in the 
>>>> exception cache list.
>>>> Note that we already incorrectly "relied on memory_order_consume" 
>>>> for the head pointer. But it used a volatile load. Assuming volatile 
>>>> loads retain memory_order_consume semantics is not allowed in 
>>>> HotSpot runtime code, so I changed those loads to use acquire 
>>>> accordingly.
>>>> Webrev:
>>>> http://cr.openjdk.java.net/~eosterlund/8212989/webrev.00/
>>>> Bug:
>>>> https://bugs.openjdk.java.net/browse/JDK-8212989
>>>> Thanks,
>>>> /Erik

More information about the hotspot-dev mailing list