8158871: Long response times with G1 and StringDeduplication
Reviewed-by: stefank, sjohanss, tschatzl, dfazunen
--- a/hotspot/src/share/vm/gc/g1/g1StringDedupTable.cpp Sun Jun 26 20:00:45 2016 -0700
+++ b/hotspot/src/share/vm/gc/g1/g1StringDedupTable.cpp Mon Jun 27 09:35:18 2016 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -37,16 +37,16 @@
#include "runtime/mutexLocker.hpp"
//
-// Freelist in the deduplication table entry cache. Links table
+// List of deduplication table entries. Links table
// entries together using their _next fields.
//
-class G1StringDedupEntryFreeList : public CHeapObj<mtGC> {
+class G1StringDedupEntryList : public CHeapObj<mtGC> {
private:
G1StringDedupEntry* _list;
size_t _length;
public:
- G1StringDedupEntryFreeList() :
+ G1StringDedupEntryList() :
_list(NULL),
_length(0) {
}
@@ -66,6 +66,12 @@
return entry;
}
+ G1StringDedupEntry* remove_all() {
+ G1StringDedupEntry* list = _list;
+ _list = NULL;
+ return list;
+ }
+
size_t length() {
return _length;
}
@@ -87,43 +93,53 @@
//
class G1StringDedupEntryCache : public CHeapObj<mtGC> {
private:
- // One freelist per GC worker to allow lock less freeing of
- // entries while doing a parallel scan of the table. Using
- // PaddedEnd to avoid false sharing.
- PaddedEnd<G1StringDedupEntryFreeList>* _lists;
- size_t _nlists;
+ // One cache/overflow list per GC worker to allow lock less freeing of
+ // entries while doing a parallel scan of the table. Using PaddedEnd to
+ // avoid false sharing.
+ size_t _nlists;
+ size_t _max_list_length;
+ PaddedEnd<G1StringDedupEntryList>* _cached;
+ PaddedEnd<G1StringDedupEntryList>* _overflowed;
public:
- G1StringDedupEntryCache();
+ G1StringDedupEntryCache(size_t max_size);
~G1StringDedupEntryCache();
- // Get a table entry from the cache freelist, or allocate a new
- // entry if the cache is empty.
+ // Set max number of table entries to cache.
+ void set_max_size(size_t max_size);
+
+ // Get a table entry from the cache, or allocate a new entry if the cache is empty.
G1StringDedupEntry* alloc();
- // Insert a table entry into the cache freelist.
+ // Insert a table entry into the cache.
void free(G1StringDedupEntry* entry, uint worker_id);
// Returns current number of entries in the cache.
size_t size();
- // If the cache has grown above the given max size, trim it down
- // and deallocate the memory occupied by trimmed of entries.
- void trim(size_t max_size);
+ // Deletes overflowed entries.
+ void delete_overflowed();
};
-G1StringDedupEntryCache::G1StringDedupEntryCache() {
- _nlists = ParallelGCThreads;
- _lists = PaddedArray<G1StringDedupEntryFreeList, mtGC>::create_unfreeable((uint)_nlists);
+G1StringDedupEntryCache::G1StringDedupEntryCache(size_t max_size) :
+ _nlists(ParallelGCThreads),
+ _max_list_length(0),
+ _cached(PaddedArray<G1StringDedupEntryList, mtGC>::create_unfreeable((uint)_nlists)),
+ _overflowed(PaddedArray<G1StringDedupEntryList, mtGC>::create_unfreeable((uint)_nlists)) {
+ set_max_size(max_size);
}
G1StringDedupEntryCache::~G1StringDedupEntryCache() {
ShouldNotReachHere();
}
+void G1StringDedupEntryCache::set_max_size(size_t size) {
+ _max_list_length = size / _nlists;
+}
+
G1StringDedupEntry* G1StringDedupEntryCache::alloc() {
for (size_t i = 0; i < _nlists; i++) {
- G1StringDedupEntry* entry = _lists[i].remove();
+ G1StringDedupEntry* entry = _cached[i].remove();
if (entry != NULL) {
return entry;
}
@@ -134,31 +150,53 @@
void G1StringDedupEntryCache::free(G1StringDedupEntry* entry, uint worker_id) {
assert(entry->obj() != NULL, "Double free");
assert(worker_id < _nlists, "Invalid worker id");
+
entry->set_obj(NULL);
entry->set_hash(0);
- _lists[worker_id].add(entry);
+
+ if (_cached[worker_id].length() < _max_list_length) {
+ // Cache is not full
+ _cached[worker_id].add(entry);
+ } else {
+ // Cache is full, add to overflow list for later deletion
+ _overflowed[worker_id].add(entry);
+ }
}
size_t G1StringDedupEntryCache::size() {
size_t size = 0;
for (size_t i = 0; i < _nlists; i++) {
- size += _lists[i].length();
+ size += _cached[i].length();
}
return size;
}
-void G1StringDedupEntryCache::trim(size_t max_size) {
- size_t cache_size = 0;
+void G1StringDedupEntryCache::delete_overflowed() {
+ double start = os::elapsedTime();
+ uintx count = 0;
+
for (size_t i = 0; i < _nlists; i++) {
- G1StringDedupEntryFreeList* list = &_lists[i];
- cache_size += list->length();
- while (cache_size > max_size) {
- G1StringDedupEntry* entry = list->remove();
- assert(entry != NULL, "Should not be null");
- cache_size--;
+ G1StringDedupEntry* entry;
+
+ {
+ // The overflow list can be modified during safepoints, therefore
+ // we temporarily join the suspendible thread set while removing
+ // all entries from the list.
+ SuspendibleThreadSetJoiner sts_join;
+ entry = _overflowed[i].remove_all();
+ }
+
+ // Delete all entries
+ while (entry != NULL) {
+ G1StringDedupEntry* next = entry->next();
delete entry;
+ entry = next;
+ count++;
}
}
+
+ double end = os::elapsedTime();
+ log_trace(gc, stringdedup)("Deleted " UINTX_FORMAT " entries, " G1_STRDEDUP_TIME_FORMAT, count, end - start);
}
G1StringDedupTable* G1StringDedupTable::_table = NULL;
@@ -195,7 +233,7 @@
void G1StringDedupTable::create() {
assert(_table == NULL, "One string deduplication table allowed");
- _entry_cache = new G1StringDedupEntryCache();
+ _entry_cache = new G1StringDedupEntryCache(_min_size * _max_cache_factor);
_table = new G1StringDedupTable(_min_size);
}
@@ -389,6 +427,9 @@
// Update statistics
_resize_count++;
+ // Update max cache size
+ _entry_cache->set_max_size(size * _max_cache_factor);
+
// Allocate the new table. The new table will be populated by workers
// calling unlink_or_oops_do() and finally installed by finish_resize().
return new G1StringDedupTable(size, _table->_hash_seed);
@@ -441,7 +482,7 @@
removed += unlink_or_oops_do(cl, table_half + partition_begin, table_half + partition_end, worker_id);
}
- // Delayed update avoid contention on the table lock
+ // Delayed update to avoid contention on the table lock
if (removed > 0) {
MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag);
_table->_entries -= removed;
@@ -563,10 +604,8 @@
}
}
-void G1StringDedupTable::trim_entry_cache() {
- MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag);
- size_t max_cache_size = (size_t)(_table->_size * _max_cache_factor);
- _entry_cache->trim(max_cache_size);
+void G1StringDedupTable::clean_entry_cache() {
+ _entry_cache->delete_overflowed();
}
void G1StringDedupTable::print_statistics() {
--- a/hotspot/src/share/vm/gc/g1/g1StringDedupTable.hpp Sun Jun 26 20:00:45 2016 -0700
+++ b/hotspot/src/share/vm/gc/g1/g1StringDedupTable.hpp Mon Jun 27 09:35:18 2016 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -229,8 +229,8 @@
// and deletes the previously active table.
static void finish_rehash(G1StringDedupTable* rehashed_table);
- // If the table entry cache has grown too large, trim it down according to policy
- static void trim_entry_cache();
+ // If the table entry cache has grown too large, delete overflowed entries.
+ static void clean_entry_cache();
static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, uint worker_id);
--- a/hotspot/src/share/vm/gc/g1/g1StringDedupThread.cpp Sun Jun 26 20:00:45 2016 -0700
+++ b/hotspot/src/share/vm/gc/g1/g1StringDedupThread.cpp Mon Jun 27 09:35:18 2016 +0200
@@ -121,16 +121,15 @@
}
}
- G1StringDedupTable::trim_entry_cache();
-
stat.mark_done();
// Print statistics
total_stat.add(stat);
print(stat, total_stat);
}
+
+ G1StringDedupTable::clean_entry_cache();
}
-
}
void G1StringDedupThread::stop_service() {