--- old/src/hotspot/share/gc/g1/g1StringDedup.cpp 2018-05-28 11:50:26.790067312 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedup.cpp 2018-05-28 11:50:26.674067270 -0400 @@ -29,26 +29,16 @@ #include "gc/g1/g1StringDedup.hpp" #include "gc/g1/g1StringDedupQueue.hpp" #include "gc/g1/g1StringDedupStat.hpp" -#include "gc/g1/g1StringDedupTable.hpp" -#include "gc/g1/g1StringDedupThread.hpp" +#include "gc/shared/stringdedup/stringDedup.inline.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.hpp" +#include "gc/shared/stringdedup/stringDedupTable.hpp" +#include "gc/shared/stringdedup/stringDedupThread.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" -bool G1StringDedup::_enabled = false; - void G1StringDedup::initialize() { - assert(UseG1GC, "String deduplication only available with G1"); - if (UseStringDeduplication) { - _enabled = true; - G1StringDedupQueue::create(); - G1StringDedupTable::create(); - G1StringDedupThread::create(); - } -} - -void G1StringDedup::stop() { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupThread::thread()->stop(); + assert(UseG1GC, "String deduplication available with G1"); + StringDedup::initialize_impl(); } bool G1StringDedup::is_candidate_from_mark(oop obj) { @@ -99,12 +89,6 @@ } } -void G1StringDedup::deduplicate(oop java_string) { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupStat dummy; // Statistics from this path is never used - G1StringDedupTable::deduplicate(java_string, dummy); -} - void G1StringDedup::oops_do(OopClosure* keep_alive) { assert(is_enabled(), "String deduplication not enabled"); unlink_or_oops_do(NULL, keep_alive, true /* allow_resize_and_rehash */); @@ -112,8 +96,8 @@ void G1StringDedup::parallel_unlink(G1StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id) { assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupQueue::unlink_or_oops_do(unlink); - G1StringDedupTable::unlink_or_oops_do(unlink, worker_id); + StringDedupQueue::unlink_or_oops_do(unlink); + StringDedupTable::unlink_or_oops_do(unlink, worker_id); } // @@ -136,11 +120,11 @@ virtual void work(uint worker_id) { { G1GCParPhaseTimesTracker x(_phase_times, G1GCPhaseTimes::StringDedupQueueFixup, worker_id); - G1StringDedupQueue::unlink_or_oops_do(&_cl); + StringDedupQueue::unlink_or_oops_do(&_cl); } { G1GCParPhaseTimesTracker x(_phase_times, G1GCPhaseTimes::StringDedupTableFixup, worker_id); - G1StringDedupTable::unlink_or_oops_do(&_cl, worker_id); + StringDedupTable::unlink_or_oops_do(&_cl, worker_id); } } }; @@ -155,61 +139,3 @@ G1CollectedHeap* g1h = G1CollectedHeap::heap(); g1h->workers()->run_task(&task); } - -void G1StringDedup::threads_do(ThreadClosure* tc) { - assert(is_enabled(), "String deduplication not enabled"); - tc->do_thread(G1StringDedupThread::thread()); -} - -void G1StringDedup::print_worker_threads_on(outputStream* st) { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupThread::thread()->print_on(st); - st->cr(); -} - -void G1StringDedup::verify() { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupQueue::verify(); - G1StringDedupTable::verify(); -} - -G1StringDedupUnlinkOrOopsDoClosure::G1StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, - OopClosure* keep_alive, - bool allow_resize_and_rehash) : - _is_alive(is_alive), - _keep_alive(keep_alive), - _resized_table(NULL), - _rehashed_table(NULL), - _next_queue(0), - _next_bucket(0) { - if (allow_resize_and_rehash) { - // If both resize and rehash is needed, only do resize. Rehash of - // the table will eventually happen if the situation persists. - _resized_table = G1StringDedupTable::prepare_resize(); - if (!is_resizing()) { - _rehashed_table = G1StringDedupTable::prepare_rehash(); - } - } -} - -G1StringDedupUnlinkOrOopsDoClosure::~G1StringDedupUnlinkOrOopsDoClosure() { - assert(!is_resizing() || !is_rehashing(), "Can not both resize and rehash"); - if (is_resizing()) { - G1StringDedupTable::finish_resize(_resized_table); - } else if (is_rehashing()) { - G1StringDedupTable::finish_rehash(_rehashed_table); - } -} - -// Atomically claims the next available queue for exclusive access by -// the current thread. Returns the queue number of the claimed queue. -size_t G1StringDedupUnlinkOrOopsDoClosure::claim_queue() { - return Atomic::add((size_t)1, &_next_queue) - 1; -} - -// Atomically claims the next available table partition for exclusive -// access by the current thread. Returns the table bucket number where -// the claimed partition starts. -size_t G1StringDedupUnlinkOrOopsDoClosure::claim_table_partition(size_t partition_size) { - return Atomic::add(partition_size, &_next_bucket) - partition_size; -} --- old/src/hotspot/share/gc/g1/g1StringDedup.hpp 2018-05-28 11:50:27.139067439 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedup.hpp 2018-05-28 11:50:27.029067399 -0400 @@ -82,24 +82,43 @@ // http://openjdk.java.net/jeps/192 // +#include "gc/shared/stringdedup/stringDedup.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" class OopClosure; class BoolObjectClosure; -class ThreadClosure; -class outputStream; -class G1StringDedupTable; -class G1StringDedupUnlinkOrOopsDoClosure; class G1GCPhaseTimes; +class G1StringDedupUnlinkOrOopsDoClosure; + +// +// Candidate selection +// +// An object is considered a deduplication candidate if all of the following +// statements are true: +// +// - The object is an instance of java.lang.String +// +// - The object is being evacuated from a young heap region +// +// - The object is being evacuated to a young/survivor heap region and the +// object's age is equal to the deduplication age threshold +// +// or +// +// The object is being evacuated to an old heap region and the object's age is +// less than the deduplication age threshold +// +// Once an string object has been promoted to an old region, or its age is higher +// than the deduplication age threshold, is will never become a candidate again. +// This approach avoids making the same object a candidate more than once. + // -// Main interface for interacting with string deduplication. +// G1 interface for interacting with string deduplication. // -class G1StringDedup : public AllStatic { +class G1StringDedup : public StringDedup { private: - // Single state for checking if both G1 and string deduplication is enabled. - static bool _enabled; // Candidate selection policies, returns true if the given object is // candidate for string deduplication. @@ -107,21 +126,9 @@ static bool is_candidate_from_evacuation(bool from_young, bool to_young, oop obj); public: - // Returns true if both G1 and string deduplication is enabled. - static bool is_enabled() { - return _enabled; - } - // Initialize string deduplication. static void initialize(); - // Stop the deduplication thread. - static void stop(); - - // Immediately deduplicates the given String object, bypassing the - // the deduplication queue. - static void deduplicate(oop java_string); - // Enqueues a deduplication candidate for later processing by the deduplication // thread. Before enqueuing, these functions apply the appropriate candidate // selection policy to filters out non-candidates. @@ -133,70 +140,28 @@ static void parallel_unlink(G1StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id); static void unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive, bool allow_resize_and_rehash, G1GCPhaseTimes* phase_times = NULL); - - static void threads_do(ThreadClosure* tc); - static void print_worker_threads_on(outputStream* st); - static void verify(); }; // // This closure encapsulates the state and the closures needed when scanning // the deduplication queue and table during the unlink_or_oops_do() operation. // A single instance of this closure is created and then shared by all worker -// threads participating in the scan. The _next_queue and _next_bucket fields -// provide a simple mechanism for GC workers to claim exclusive access to a -// queue or a table partition. +// threads participating in the scan. // -class G1StringDedupUnlinkOrOopsDoClosure : public StackObj { -private: - BoolObjectClosure* _is_alive; - OopClosure* _keep_alive; - G1StringDedupTable* _resized_table; - G1StringDedupTable* _rehashed_table; - size_t _next_queue; - size_t _next_bucket; - +class G1StringDedupUnlinkOrOopsDoClosure : public StringDedupUnlinkOrOopsDoClosure { public: G1StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, OopClosure* keep_alive, - bool allow_resize_and_rehash); - ~G1StringDedupUnlinkOrOopsDoClosure(); - - bool is_resizing() { - return _resized_table != NULL; - } - - G1StringDedupTable* resized_table() { - return _resized_table; - } - - bool is_rehashing() { - return _rehashed_table != NULL; - } - - // Atomically claims the next available queue for exclusive access by - // the current thread. Returns the queue number of the claimed queue. - size_t claim_queue(); - - // Atomically claims the next available table partition for exclusive - // access by the current thread. Returns the table bucket number where - // the claimed partition starts. - size_t claim_table_partition(size_t partition_size); - - // Applies and returns the result from the is_alive closure, or - // returns true if no such closure was provided. - bool is_alive(oop o) { - if (_is_alive != NULL) { - return _is_alive->do_object_b(o); + bool allow_resize_and_rehash) : + StringDedupUnlinkOrOopsDoClosure(is_alive, keep_alive) { + if (G1StringDedup::is_enabled()) { + G1StringDedup::gc_prologue(allow_resize_and_rehash); + } } - return true; - } - - // Applies the keep_alive closure, or does nothing if no such - // closure was provided. - void keep_alive(oop* p) { - if (_keep_alive != NULL) { - _keep_alive->do_oop(p); + + ~G1StringDedupUnlinkOrOopsDoClosure() { + if (G1StringDedup::is_enabled()) { + G1StringDedup::gc_epilogue(); } } }; --- old/src/hotspot/share/gc/g1/g1StringDedupQueue.cpp 2018-05-28 11:50:27.548067587 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedupQueue.cpp 2018-05-28 11:50:27.422067541 -0400 @@ -34,7 +34,6 @@ #include "runtime/safepointVerifiers.hpp" #include "utilities/stack.inline.hpp" -G1StringDedupQueue* G1StringDedupQueue::_queue = NULL; const size_t G1StringDedupQueue::_max_size = 1000000; // Max number of elements per queue const size_t G1StringDedupQueue::_max_cache_size = 0; // Max cache size per queue @@ -54,54 +53,49 @@ ShouldNotReachHere(); } -void G1StringDedupQueue::create() { - assert(_queue == NULL, "One string deduplication queue allowed"); - _queue = new G1StringDedupQueue(); -} - -void G1StringDedupQueue::wait() { +void G1StringDedupQueue::queue_wait() { MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag); - while (_queue->_empty && !_queue->_cancel) { + while (_empty && !_cancel) { ml.wait(Mutex::_no_safepoint_check_flag); } } -void G1StringDedupQueue::cancel_wait() { +void G1StringDedupQueue::queue_cancel_wait() { MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag); - _queue->_cancel = true; + _cancel = true; ml.notify(); } -void G1StringDedupQueue::push(uint worker_id, oop java_string) { +void G1StringDedupQueue::queue_push(uint worker_id, oop java_string) { assert(SafepointSynchronize::is_at_safepoint(), "Must be at safepoint"); - assert(worker_id < _queue->_nqueues, "Invalid queue"); + assert(worker_id < _nqueues, "Invalid queue"); // Push and notify waiter - G1StringDedupWorkerQueue& worker_queue = _queue->_queues[worker_id]; + G1StringDedupWorkerQueue& worker_queue = _queues[worker_id]; if (!worker_queue.is_full()) { worker_queue.push(java_string); - if (_queue->_empty) { + if (_empty) { MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag); - if (_queue->_empty) { + if (_empty) { // Mark non-empty and notify waiter - _queue->_empty = false; + _empty = false; ml.notify(); } } } else { // Queue is full, drop the string and update the statistics - Atomic::inc(&_queue->_dropped); + Atomic::inc(&_dropped); } } -oop G1StringDedupQueue::pop() { +oop G1StringDedupQueue::queue_pop() { assert(!SafepointSynchronize::is_at_safepoint(), "Must not be at safepoint"); NoSafepointVerifier nsv; // Try all queues before giving up - for (size_t tries = 0; tries < _queue->_nqueues; tries++) { + for (size_t tries = 0; tries < _nqueues; tries++) { // The cursor indicates where we left of last time - G1StringDedupWorkerQueue* queue = &_queue->_queues[_queue->_cursor]; + G1StringDedupWorkerQueue* queue = &_queues[_cursor]; while (!queue->is_empty()) { oop obj = queue->pop(); // The oop we pop can be NULL if it was marked @@ -112,34 +106,18 @@ } // Try next queue - _queue->_cursor = (_queue->_cursor + 1) % _queue->_nqueues; + _cursor = (_cursor + 1) % _nqueues; } // Mark empty - _queue->_empty = true; + _empty = true; return NULL; } -void G1StringDedupQueue::unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl) { - // A worker thread first claims a queue, which ensures exclusive - // access to that queue, then continues to process it. - for (;;) { - // Grab next queue to scan - size_t queue = cl->claim_queue(); - if (queue >= _queue->_nqueues) { - // End of queues - break; - } - - // Scan the queue - unlink_or_oops_do(cl, queue); - } -} - -void G1StringDedupQueue::unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, size_t queue) { - assert(queue < _queue->_nqueues, "Invalid queue"); - StackIterator iter(_queue->_queues[queue]); +void G1StringDedupQueue::queue_unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, size_t queue) { + assert(queue < _nqueues, "Invalid queue"); + StackIterator iter(_queues[queue]); while (!iter.is_empty()) { oop* p = iter.next_addr(); if (*p != NULL) { @@ -153,14 +131,14 @@ } } -void G1StringDedupQueue::print_statistics() { +void G1StringDedupQueue::queue_print_statistics() { log_debug(gc, stringdedup)(" Queue"); - log_debug(gc, stringdedup)(" Dropped: " UINTX_FORMAT, _queue->_dropped); + log_debug(gc, stringdedup)(" Dropped: " UINTX_FORMAT, _dropped); } -void G1StringDedupQueue::verify() { - for (size_t i = 0; i < _queue->_nqueues; i++) { - StackIterator iter(_queue->_queues[i]); +void G1StringDedupQueue::queue_verify() { + for (size_t i = 0; i < _nqueues; i++) { + StackIterator iter(_queues[i]); while (!iter.is_empty()) { oop obj = iter.next(); if (obj != NULL) { --- old/src/hotspot/share/gc/g1/g1StringDedupQueue.hpp 2018-05-28 11:50:27.895067712 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedupQueue.hpp 2018-05-28 11:50:27.785067672 -0400 @@ -25,40 +25,21 @@ #ifndef SHARE_VM_GC_G1_G1STRINGDEDUPQUEUE_HPP #define SHARE_VM_GC_G1_G1STRINGDEDUPQUEUE_HPP +#include "gc/shared/stringdedup/stringDedupQueue.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" #include "utilities/stack.hpp" -class G1StringDedupUnlinkOrOopsDoClosure; +class StringDedupUnlinkOrOopsDoClosure; // -// The deduplication queue acts as the communication channel between the stop-the-world -// mark/evacuation phase and the concurrent deduplication phase. Deduplication candidates -// found during mark/evacuation are placed on this queue for later processing in the -// deduplication thread. A queue entry is an oop pointing to a String object (as opposed -// to entries in the deduplication hashtable which points to character arrays). +// G1 enqueues candidates during the stop-the-world mark/evacuation phase. // -// While users of the queue treat it as a single queue, it is implemented as a set of -// queues, one queue per GC worker thread, to allow lock-free and cache-friendly enqueue -// operations by the GC workers. -// -// The oops in the queue are treated as weak pointers, meaning the objects they point to -// can become unreachable and pruned (cleared) before being popped by the deduplication -// thread. -// -// Pushing to the queue is thread safe (this relies on each thread using a unique worker -// id), but only allowed during a safepoint. Popping from the queue is NOT thread safe -// and can only be done by the deduplication thread outside a safepoint. -// -// The StringDedupQueue_lock is only used for blocking and waking up the deduplication -// thread in case the queue is empty or becomes non-empty, respectively. This lock does -// not otherwise protect the queue content. -// -class G1StringDedupQueue : public CHeapObj { + +class G1StringDedupQueue : public StringDedupQueue { private: typedef Stack G1StringDedupWorkerQueue; - static G1StringDedupQueue* _queue; static const size_t _max_size; static const size_t _max_cache_size; @@ -71,31 +52,36 @@ // Statistics counter, only used for logging. uintx _dropped; - G1StringDedupQueue(); ~G1StringDedupQueue(); - static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, size_t queue); + void unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, size_t queue); public: - static void create(); + G1StringDedupQueue(); + +protected: // Blocks and waits for the queue to become non-empty. - static void wait(); + void queue_wait(); // Wakes up any thread blocked waiting for the queue to become non-empty. - static void cancel_wait(); + void queue_cancel_wait(); // Pushes a deduplication candidate onto a specific GC worker queue. - static void push(uint worker_id, oop java_string); + void queue_push(uint worker_id, oop java_string); // Pops a deduplication candidate from any queue, returns NULL if // all queues are empty. - static oop pop(); + oop queue_pop(); + + size_t num_queues() const { + return _nqueues; + } - static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl); + void queue_unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, size_t queue); - static void print_statistics(); - static void verify(); + void queue_print_statistics(); + void queue_verify(); }; #endif // SHARE_VM_GC_G1_G1STRINGDEDUPQUEUE_HPP --- old/src/hotspot/share/gc/g1/g1StringDedupStat.cpp 2018-05-28 11:50:28.238067836 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedupStat.cpp 2018-05-28 11:50:28.127067796 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2018, 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 @@ -23,121 +23,60 @@ */ #include "precompiled.hpp" + +#include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1StringDedupStat.hpp" #include "logging/log.hpp" -G1StringDedupStat::G1StringDedupStat() : - _inspected(0), - _skipped(0), - _hashed(0), - _known(0), - _new(0), - _new_bytes(0), - _deduped(0), - _deduped_bytes(0), +G1StringDedupStat::G1StringDedupStat() : StringDedupStat(), _deduped_young(0), _deduped_young_bytes(0), _deduped_old(0), _deduped_old_bytes(0), - _idle(0), - _exec(0), - _block(0), - _start_concurrent(0.0), - _end_concurrent(0.0), - _start_phase(0.0), - _idle_elapsed(0.0), - _exec_elapsed(0.0), - _block_elapsed(0.0) { + _heap(G1CollectedHeap::heap()) { } -void G1StringDedupStat::add(const G1StringDedupStat& stat) { - _inspected += stat._inspected; - _skipped += stat._skipped; - _hashed += stat._hashed; - _known += stat._known; - _new += stat._new; - _new_bytes += stat._new_bytes; - _deduped += stat._deduped; - _deduped_bytes += stat._deduped_bytes; - _deduped_young += stat._deduped_young; - _deduped_young_bytes += stat._deduped_young_bytes; - _deduped_old += stat._deduped_old; - _deduped_old_bytes += stat._deduped_old_bytes; - _idle += stat._idle; - _exec += stat._exec; - _block += stat._block; - _idle_elapsed += stat._idle_elapsed; - _exec_elapsed += stat._exec_elapsed; - _block_elapsed += stat._block_elapsed; + + +void G1StringDedupStat::deduped(oop obj, uintx bytes) { + StringDedupStat::deduped(obj, bytes); + if (_heap->is_in_young(obj)) { + _deduped_young ++; + _deduped_young_bytes += bytes; + } else { + _deduped_old ++; + _deduped_old_bytes += bytes; + } } -void G1StringDedupStat::print_start(const G1StringDedupStat& last_stat) { - log_info(gc, stringdedup)( - "Concurrent String Deduplication (" G1_STRDEDUP_TIME_FORMAT ")", - G1_STRDEDUP_TIME_PARAM(last_stat._start_concurrent)); +void G1StringDedupStat::add(const StringDedupStat* const stat) { + StringDedupStat::add(stat); + const G1StringDedupStat* const g1_stat = (const G1StringDedupStat* const)stat; + _deduped_young += g1_stat->_deduped_young; + _deduped_young_bytes += g1_stat->_deduped_young_bytes; + _deduped_old += g1_stat->_deduped_old; + _deduped_old_bytes += g1_stat->_deduped_old_bytes; } -void G1StringDedupStat::print_end(const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat) { - double total_deduped_bytes_percent = 0.0; +void G1StringDedupStat::print_statistics(bool total) const { + StringDedupStat::print_statistics(total); - if (total_stat._new_bytes > 0) { - // Avoid division by zero - total_deduped_bytes_percent = percent_of(total_stat._deduped_bytes, total_stat._new_bytes); - } + double deduped_young_percent = percent_of(_deduped_young, _deduped); + double deduped_young_bytes_percent = percent_of(_deduped_young_bytes, _deduped_bytes); + double deduped_old_percent = percent_of(_deduped_old, _deduped); + double deduped_old_bytes_percent = percent_of(_deduped_old_bytes, _deduped_bytes); + + log_debug(gc, stringdedup)(" Young: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", + _deduped_young, deduped_young_percent, STRDEDUP_BYTES_PARAM(_deduped_young_bytes), deduped_young_bytes_percent); + log_debug(gc, stringdedup)(" Old: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", + _deduped_old, deduped_old_percent, STRDEDUP_BYTES_PARAM(_deduped_old_bytes), deduped_old_bytes_percent); - log_info(gc, stringdedup)( - "Concurrent String Deduplication " - G1_STRDEDUP_BYTES_FORMAT_NS "->" G1_STRDEDUP_BYTES_FORMAT_NS "(" G1_STRDEDUP_BYTES_FORMAT_NS ") " - "avg " G1_STRDEDUP_PERCENT_FORMAT_NS " " - "(" G1_STRDEDUP_TIME_FORMAT ", " G1_STRDEDUP_TIME_FORMAT ") " G1_STRDEDUP_TIME_FORMAT_MS, - G1_STRDEDUP_BYTES_PARAM(last_stat._new_bytes), - G1_STRDEDUP_BYTES_PARAM(last_stat._new_bytes - last_stat._deduped_bytes), - G1_STRDEDUP_BYTES_PARAM(last_stat._deduped_bytes), - total_deduped_bytes_percent, - G1_STRDEDUP_TIME_PARAM(last_stat._start_concurrent), - G1_STRDEDUP_TIME_PARAM(last_stat._end_concurrent), - G1_STRDEDUP_TIME_PARAM_MS(last_stat._exec_elapsed)); } -void G1StringDedupStat::print_statistics(const G1StringDedupStat& stat, bool total) { - double skipped_percent = percent_of(stat._skipped, stat._inspected); - double hashed_percent = percent_of(stat._hashed, stat._inspected); - double known_percent = percent_of(stat._known, stat._inspected); - double new_percent = percent_of(stat._new, stat._inspected); - double deduped_percent = percent_of(stat._deduped, stat._new); - double deduped_bytes_percent = percent_of(stat._deduped_bytes, stat._new_bytes); - double deduped_young_percent = percent_of(stat._deduped_young, stat._deduped); - double deduped_young_bytes_percent = percent_of(stat._deduped_young_bytes, stat._deduped_bytes); - double deduped_old_percent = percent_of(stat._deduped_old, stat._deduped); - double deduped_old_bytes_percent = percent_of(stat._deduped_old_bytes, stat._deduped_bytes); - - if (total) { - log_debug(gc, stringdedup)( - " Total Exec: " UINTX_FORMAT "/" G1_STRDEDUP_TIME_FORMAT_MS - ", Idle: " UINTX_FORMAT "/" G1_STRDEDUP_TIME_FORMAT_MS - ", Blocked: " UINTX_FORMAT "/" G1_STRDEDUP_TIME_FORMAT_MS, - stat._exec, G1_STRDEDUP_TIME_PARAM_MS(stat._exec_elapsed), - stat._idle, G1_STRDEDUP_TIME_PARAM_MS(stat._idle_elapsed), - stat._block, G1_STRDEDUP_TIME_PARAM_MS(stat._block_elapsed)); - } else { - log_debug(gc, stringdedup)( - " Last Exec: " G1_STRDEDUP_TIME_FORMAT_MS - ", Idle: " G1_STRDEDUP_TIME_FORMAT_MS - ", Blocked: " UINTX_FORMAT "/" G1_STRDEDUP_TIME_FORMAT_MS, - G1_STRDEDUP_TIME_PARAM_MS(stat._exec_elapsed), - G1_STRDEDUP_TIME_PARAM_MS(stat._idle_elapsed), - stat._block, G1_STRDEDUP_TIME_PARAM_MS(stat._block_elapsed)); - } - log_debug(gc, stringdedup)(" Inspected: " G1_STRDEDUP_OBJECTS_FORMAT, stat._inspected); - log_debug(gc, stringdedup)(" Skipped: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", stat._skipped, skipped_percent); - log_debug(gc, stringdedup)(" Hashed: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", stat._hashed, hashed_percent); - log_debug(gc, stringdedup)(" Known: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", stat._known, known_percent); - log_debug(gc, stringdedup)(" New: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ") " G1_STRDEDUP_BYTES_FORMAT, - stat._new, new_percent, G1_STRDEDUP_BYTES_PARAM(stat._new_bytes)); - log_debug(gc, stringdedup)(" Deduplicated: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ") " G1_STRDEDUP_BYTES_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", - stat._deduped, deduped_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_bytes), deduped_bytes_percent); - log_debug(gc, stringdedup)(" Young: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ") " G1_STRDEDUP_BYTES_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", - stat._deduped_young, deduped_young_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_young_bytes), deduped_young_bytes_percent); - log_debug(gc, stringdedup)(" Old: " G1_STRDEDUP_OBJECTS_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ") " G1_STRDEDUP_BYTES_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT ")", - stat._deduped_old, deduped_old_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_old_bytes), deduped_old_bytes_percent); +void G1StringDedupStat::reset() { + StringDedupStat::reset(); + _deduped_young = 0; + _deduped_young_bytes = 0; + _deduped_old = 0; + _deduped_old_bytes = 0; } --- old/src/hotspot/share/gc/g1/g1StringDedupStat.hpp 2018-05-28 11:50:28.596067966 -0400 +++ new/src/hotspot/share/gc/g1/g1StringDedupStat.hpp 2018-05-28 11:50:28.486067926 -0400 @@ -25,126 +25,28 @@ #ifndef SHARE_VM_GC_G1_G1STRINGDEDUPSTAT_HPP #define SHARE_VM_GC_G1_G1STRINGDEDUPSTAT_HPP -#include "memory/allocation.hpp" -#include "runtime/os.hpp" +#include "gc/shared/stringdedup/stringDedupStat.hpp" -// Macros for GC log output formating -#define G1_STRDEDUP_OBJECTS_FORMAT UINTX_FORMAT_W(12) -#define G1_STRDEDUP_TIME_FORMAT "%.3fs" -#define G1_STRDEDUP_TIME_PARAM(time) (time) -#define G1_STRDEDUP_TIME_FORMAT_MS "%.3fms" -#define G1_STRDEDUP_TIME_PARAM_MS(time) ((time) * MILLIUNITS) -#define G1_STRDEDUP_PERCENT_FORMAT "%5.1f%%" -#define G1_STRDEDUP_PERCENT_FORMAT_NS "%.1f%%" -#define G1_STRDEDUP_BYTES_FORMAT "%8.1f%s" -#define G1_STRDEDUP_BYTES_FORMAT_NS "%.1f%s" -#define G1_STRDEDUP_BYTES_PARAM(bytes) byte_size_in_proper_unit((double)(bytes)), proper_unit_for_byte_size((bytes)) - -// -// Statistics gathered by the deduplication thread. -// -class G1StringDedupStat : public StackObj { +// G1 extension for gathering/reporting generational statistics +class G1StringDedupStat : public StringDedupStat { private: - // Counters - uintx _inspected; - uintx _skipped; - uintx _hashed; - uintx _known; - uintx _new; - uintx _new_bytes; - uintx _deduped; - uintx _deduped_bytes; uintx _deduped_young; uintx _deduped_young_bytes; uintx _deduped_old; uintx _deduped_old_bytes; - uintx _idle; - uintx _exec; - uintx _block; - - // Time spent by the deduplication thread in different phases - double _start_concurrent; - double _end_concurrent; - double _start_phase; - double _idle_elapsed; - double _exec_elapsed; - double _block_elapsed; + + G1CollectedHeap* const _heap; public: G1StringDedupStat(); - void inc_inspected() { - _inspected++; - } - - void inc_skipped() { - _skipped++; - } - - void inc_hashed() { - _hashed++; - } - - void inc_known() { - _known++; - } - - void inc_new(uintx bytes) { - _new++; - _new_bytes += bytes; - } - - void inc_deduped_young(uintx bytes) { - _deduped++; - _deduped_bytes += bytes; - _deduped_young++; - _deduped_young_bytes += bytes; - } - - void inc_deduped_old(uintx bytes) { - _deduped++; - _deduped_bytes += bytes; - _deduped_old++; - _deduped_old_bytes += bytes; - } - - void mark_idle() { - _start_phase = os::elapsedTime(); - _idle++; - } - - void mark_exec() { - double now = os::elapsedTime(); - _idle_elapsed = now - _start_phase; - _start_phase = now; - _start_concurrent = now; - _exec++; - } - - void mark_block() { - double now = os::elapsedTime(); - _exec_elapsed += now - _start_phase; - _start_phase = now; - _block++; - } - - void mark_unblock() { - double now = os::elapsedTime(); - _block_elapsed += now - _start_phase; - _start_phase = now; - } - - void mark_done() { - double now = os::elapsedTime(); - _exec_elapsed += now - _start_phase; - _end_concurrent = now; - } - - void add(const G1StringDedupStat& stat); - - static void print_start(const G1StringDedupStat& last_stat); - static void print_end(const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat); - static void print_statistics(const G1StringDedupStat& stat, bool total); + void deduped(oop obj, uintx bytes); + + void add(const StringDedupStat* const stat); + + void print_statistics(bool total) const; + + void reset(); }; #endif // SHARE_VM_GC_G1_G1STRINGDEDUPSTAT_HPP --- old/src/hotspot/share/gc/g1/g1StringDedup.cpp 2018-05-28 11:50:28.998068112 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2014, 2018, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "precompiled.hpp" -#include "classfile/javaClasses.inline.hpp" -#include "gc/g1/g1CollectedHeap.inline.hpp" -#include "gc/g1/g1GCPhaseTimes.hpp" -#include "gc/g1/g1StringDedup.hpp" -#include "gc/g1/g1StringDedupQueue.hpp" -#include "gc/g1/g1StringDedupStat.hpp" -#include "gc/g1/g1StringDedupTable.hpp" -#include "gc/g1/g1StringDedupThread.hpp" -#include "oops/oop.inline.hpp" -#include "runtime/atomic.hpp" - -bool G1StringDedup::_enabled = false; - -void G1StringDedup::initialize() { - assert(UseG1GC, "String deduplication only available with G1"); - if (UseStringDeduplication) { - _enabled = true; - G1StringDedupQueue::create(); - G1StringDedupTable::create(); - G1StringDedupThread::create(); - } -} - -void G1StringDedup::stop() { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupThread::thread()->stop(); -} - -bool G1StringDedup::is_candidate_from_mark(oop obj) { - if (java_lang_String::is_instance_inlined(obj)) { - bool from_young = G1CollectedHeap::heap()->heap_region_containing(obj)->is_young(); - if (from_young && obj->age() < StringDeduplicationAgeThreshold) { - // Candidate found. String is being evacuated from young to old but has not - // reached the deduplication age threshold, i.e. has not previously been a - // candidate during its life in the young generation. - return true; - } - } - - // Not a candidate - return false; -} - -void G1StringDedup::enqueue_from_mark(oop java_string, uint worker_id) { - assert(is_enabled(), "String deduplication not enabled"); - if (is_candidate_from_mark(java_string)) { - G1StringDedupQueue::push(worker_id, java_string); - } -} - -bool G1StringDedup::is_candidate_from_evacuation(bool from_young, bool to_young, oop obj) { - if (from_young && java_lang_String::is_instance_inlined(obj)) { - if (to_young && obj->age() == StringDeduplicationAgeThreshold) { - // Candidate found. String is being evacuated from young to young and just - // reached the deduplication age threshold. - return true; - } - if (!to_young && obj->age() < StringDeduplicationAgeThreshold) { - // Candidate found. String is being evacuated from young to old but has not - // reached the deduplication age threshold, i.e. has not previously been a - // candidate during its life in the young generation. - return true; - } - } - - // Not a candidate - return false; -} - -void G1StringDedup::enqueue_from_evacuation(bool from_young, bool to_young, uint worker_id, oop java_string) { - assert(is_enabled(), "String deduplication not enabled"); - if (is_candidate_from_evacuation(from_young, to_young, java_string)) { - G1StringDedupQueue::push(worker_id, java_string); - } -} - -void G1StringDedup::deduplicate(oop java_string) { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupStat dummy; // Statistics from this path is never used - G1StringDedupTable::deduplicate(java_string, dummy); -} - -void G1StringDedup::oops_do(OopClosure* keep_alive) { - assert(is_enabled(), "String deduplication not enabled"); - unlink_or_oops_do(NULL, keep_alive, true /* allow_resize_and_rehash */); -} - -void G1StringDedup::parallel_unlink(G1StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id) { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupQueue::unlink_or_oops_do(unlink); - G1StringDedupTable::unlink_or_oops_do(unlink, worker_id); -} - -// -// Task for parallel unlink_or_oops_do() operation on the deduplication queue -// and table. -// -class G1StringDedupUnlinkOrOopsDoTask : public AbstractGangTask { -private: - G1StringDedupUnlinkOrOopsDoClosure _cl; - G1GCPhaseTimes* _phase_times; - -public: - G1StringDedupUnlinkOrOopsDoTask(BoolObjectClosure* is_alive, - OopClosure* keep_alive, - bool allow_resize_and_rehash, - G1GCPhaseTimes* phase_times) : - AbstractGangTask("G1StringDedupUnlinkOrOopsDoTask"), - _cl(is_alive, keep_alive, allow_resize_and_rehash), _phase_times(phase_times) { } - - virtual void work(uint worker_id) { - { - G1GCParPhaseTimesTracker x(_phase_times, G1GCPhaseTimes::StringDedupQueueFixup, worker_id); - G1StringDedupQueue::unlink_or_oops_do(&_cl); - } - { - G1GCParPhaseTimesTracker x(_phase_times, G1GCPhaseTimes::StringDedupTableFixup, worker_id); - G1StringDedupTable::unlink_or_oops_do(&_cl, worker_id); - } - } -}; - -void G1StringDedup::unlink_or_oops_do(BoolObjectClosure* is_alive, - OopClosure* keep_alive, - bool allow_resize_and_rehash, - G1GCPhaseTimes* phase_times) { - assert(is_enabled(), "String deduplication not enabled"); - - G1StringDedupUnlinkOrOopsDoTask task(is_alive, keep_alive, allow_resize_and_rehash, phase_times); - G1CollectedHeap* g1h = G1CollectedHeap::heap(); - g1h->workers()->run_task(&task); -} - -void G1StringDedup::threads_do(ThreadClosure* tc) { - assert(is_enabled(), "String deduplication not enabled"); - tc->do_thread(G1StringDedupThread::thread()); -} - -void G1StringDedup::print_worker_threads_on(outputStream* st) { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupThread::thread()->print_on(st); - st->cr(); -} - -void G1StringDedup::verify() { - assert(is_enabled(), "String deduplication not enabled"); - G1StringDedupQueue::verify(); - G1StringDedupTable::verify(); -} - -G1StringDedupUnlinkOrOopsDoClosure::G1StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, - OopClosure* keep_alive, - bool allow_resize_and_rehash) : - _is_alive(is_alive), - _keep_alive(keep_alive), - _resized_table(NULL), - _rehashed_table(NULL), - _next_queue(0), - _next_bucket(0) { - if (allow_resize_and_rehash) { - // If both resize and rehash is needed, only do resize. Rehash of - // the table will eventually happen if the situation persists. - _resized_table = G1StringDedupTable::prepare_resize(); - if (!is_resizing()) { - _rehashed_table = G1StringDedupTable::prepare_rehash(); - } - } -} - -G1StringDedupUnlinkOrOopsDoClosure::~G1StringDedupUnlinkOrOopsDoClosure() { - assert(!is_resizing() || !is_rehashing(), "Can not both resize and rehash"); - if (is_resizing()) { - G1StringDedupTable::finish_resize(_resized_table); - } else if (is_rehashing()) { - G1StringDedupTable::finish_rehash(_rehashed_table); - } -} - -// Atomically claims the next available queue for exclusive access by -// the current thread. Returns the queue number of the claimed queue. -size_t G1StringDedupUnlinkOrOopsDoClosure::claim_queue() { - return Atomic::add((size_t)1, &_next_queue) - 1; -} - -// Atomically claims the next available table partition for exclusive -// access by the current thread. Returns the table bucket number where -// the claimed partition starts. -size_t G1StringDedupUnlinkOrOopsDoClosure::claim_table_partition(size_t partition_size) { - return Atomic::add(partition_size, &_next_bucket) - partition_size; -} --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedup.cpp 2018-05-28 11:50:28.825068049 -0400 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#include "precompiled.hpp" + +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.hpp" +#include "gc/shared/stringdedup/stringDedupTable.hpp" +#include "gc/shared/stringdedup/stringDedupThread.hpp" + +bool StringDedup::_enabled = false; + +void StringDedup::gc_prologue(bool resize_and_rehash_table) { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupQueue::gc_prologue(); + StringDedupTable::gc_prologue(resize_and_rehash_table); + +} +void StringDedup::gc_epilogue() { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupQueue::gc_epilogue(); + StringDedupTable::gc_epilogue(); +} + +void StringDedup::stop() { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupThread::thread()->stop(); +} + +void StringDedup::deduplicate(oop java_string) { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupStat dummy; // Statistics from this path is never used + StringDedupTable::deduplicate(java_string, &dummy); +} + + +void StringDedup::parallel_unlink(StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id) { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupQueue::unlink_or_oops_do(unlink); + StringDedupTable::unlink_or_oops_do(unlink, worker_id); +} + +void StringDedup::threads_do(ThreadClosure* tc) { + assert(is_enabled(), "String deduplication not enabled"); + tc->do_thread(StringDedupThread::thread()); +} + +void StringDedup::print_worker_threads_on(outputStream* st) { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupThread::thread()->print_on(st); + st->cr(); +} + +void StringDedup::verify() { + assert(is_enabled(), "String deduplication not enabled"); + StringDedupQueue::verify(); + StringDedupTable::verify(); +} + + +StringDedupUnlinkOrOopsDoClosure::StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, + OopClosure* keep_alive) : + _is_alive(is_alive), _keep_alive(keep_alive) { +} --- old/src/hotspot/share/gc/g1/g1StringDedup.hpp 2018-05-28 11:50:29.455068277 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2014, 2017, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_VM_GC_G1_G1STRINGDEDUP_HPP -#define SHARE_VM_GC_G1_G1STRINGDEDUP_HPP - -// -// String Deduplication -// -// String deduplication aims to reduce the heap live-set by deduplicating identical -// instances of String so that they share the same backing character array. -// -// The deduplication process is divided in two main parts, 1) finding the objects to -// deduplicate, and 2) deduplicating those objects. The first part is done as part of -// a normal GC cycle when objects are marked or evacuated. At this time a check is -// applied on each object to check if it is a candidate for deduplication. If so, the -// object is placed on the deduplication queue for later processing. The second part, -// processing the objects on the deduplication queue, is a concurrent phase which -// starts right after the stop-the-wold marking/evacuation phase. This phase is -// executed by the deduplication thread, which pulls deduplication candidates of the -// deduplication queue and tries to deduplicate them. -// -// A deduplication hashtable is used to keep track of all unique character arrays -// used by String objects. When deduplicating, a lookup is made in this table to see -// if there is already an identical character array somewhere on the heap. If so, the -// String object is adjusted to point to that character array, releasing the reference -// to the original array allowing it to eventually be garbage collected. If the lookup -// fails the character array is instead inserted into the hashtable so that this array -// can be shared at some point in the future. -// -// Candidate selection -// -// An object is considered a deduplication candidate if all of the following -// statements are true: -// -// - The object is an instance of java.lang.String -// -// - The object is being evacuated from a young heap region -// -// - The object is being evacuated to a young/survivor heap region and the -// object's age is equal to the deduplication age threshold -// -// or -// -// The object is being evacuated to an old heap region and the object's age is -// less than the deduplication age threshold -// -// Once an string object has been promoted to an old region, or its age is higher -// than the deduplication age threshold, is will never become a candidate again. -// This approach avoids making the same object a candidate more than once. -// -// Interned strings are a bit special. They are explicitly deduplicated just before -// being inserted into the StringTable (to avoid counteracting C2 optimizations done -// on string literals), then they also become deduplication candidates if they reach -// the deduplication age threshold or are evacuated to an old heap region. The second -// attempt to deduplicate such strings will be in vain, but we have no fast way of -// filtering them out. This has not shown to be a problem, as the number of interned -// strings is usually dwarfed by the number of normal (non-interned) strings. -// -// For additional information on string deduplication, please see JEP 192, -// http://openjdk.java.net/jeps/192 -// - -#include "memory/allocation.hpp" -#include "oops/oop.hpp" - -class OopClosure; -class BoolObjectClosure; -class ThreadClosure; -class outputStream; -class G1StringDedupTable; -class G1StringDedupUnlinkOrOopsDoClosure; -class G1GCPhaseTimes; - -// -// Main interface for interacting with string deduplication. -// -class G1StringDedup : public AllStatic { -private: - // Single state for checking if both G1 and string deduplication is enabled. - static bool _enabled; - - // Candidate selection policies, returns true if the given object is - // candidate for string deduplication. - static bool is_candidate_from_mark(oop obj); - static bool is_candidate_from_evacuation(bool from_young, bool to_young, oop obj); - -public: - // Returns true if both G1 and string deduplication is enabled. - static bool is_enabled() { - return _enabled; - } - - // Initialize string deduplication. - static void initialize(); - - // Stop the deduplication thread. - static void stop(); - - // Immediately deduplicates the given String object, bypassing the - // the deduplication queue. - static void deduplicate(oop java_string); - - // Enqueues a deduplication candidate for later processing by the deduplication - // thread. Before enqueuing, these functions apply the appropriate candidate - // selection policy to filters out non-candidates. - static void enqueue_from_mark(oop java_string, uint worker_id); - static void enqueue_from_evacuation(bool from_young, bool to_young, - unsigned int queue, oop java_string); - - static void oops_do(OopClosure* keep_alive); - static void parallel_unlink(G1StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id); - static void unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive, - bool allow_resize_and_rehash, G1GCPhaseTimes* phase_times = NULL); - - static void threads_do(ThreadClosure* tc); - static void print_worker_threads_on(outputStream* st); - static void verify(); -}; - -// -// This closure encapsulates the state and the closures needed when scanning -// the deduplication queue and table during the unlink_or_oops_do() operation. -// A single instance of this closure is created and then shared by all worker -// threads participating in the scan. The _next_queue and _next_bucket fields -// provide a simple mechanism for GC workers to claim exclusive access to a -// queue or a table partition. -// -class G1StringDedupUnlinkOrOopsDoClosure : public StackObj { -private: - BoolObjectClosure* _is_alive; - OopClosure* _keep_alive; - G1StringDedupTable* _resized_table; - G1StringDedupTable* _rehashed_table; - size_t _next_queue; - size_t _next_bucket; - -public: - G1StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, - OopClosure* keep_alive, - bool allow_resize_and_rehash); - ~G1StringDedupUnlinkOrOopsDoClosure(); - - bool is_resizing() { - return _resized_table != NULL; - } - - G1StringDedupTable* resized_table() { - return _resized_table; - } - - bool is_rehashing() { - return _rehashed_table != NULL; - } - - // Atomically claims the next available queue for exclusive access by - // the current thread. Returns the queue number of the claimed queue. - size_t claim_queue(); - - // Atomically claims the next available table partition for exclusive - // access by the current thread. Returns the table bucket number where - // the claimed partition starts. - size_t claim_table_partition(size_t partition_size); - - // Applies and returns the result from the is_alive closure, or - // returns true if no such closure was provided. - bool is_alive(oop o) { - if (_is_alive != NULL) { - return _is_alive->do_object_b(o); - } - return true; - } - - // Applies the keep_alive closure, or does nothing if no such - // closure was provided. - void keep_alive(oop* p) { - if (_keep_alive != NULL) { - _keep_alive->do_oop(p); - } - } -}; - -#endif // SHARE_VM_GC_G1_G1STRINGDEDUP_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedup.hpp 2018-05-28 11:50:29.249068203 -0400 @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_HPP + +// +// String Deduplication +// +// String deduplication aims to reduce the heap live-set by deduplicating identical +// instances of String so that they share the same backing character array. +// +// The deduplication process is divided in two main parts, 1) finding the objects to +// deduplicate, and 2) deduplicating those objects. The first part is done as part of +// a normal GC cycle when objects are marked or evacuated. At this time a check is +// applied on each object to check if it is a candidate for deduplication. If so, the +// object is placed on the deduplication queue for later processing. The second part, +// processing the objects on the deduplication queue, is a concurrent phase which +// starts right after the stop-the-wold marking/evacuation phase. This phase is +// executed by the deduplication thread, which pulls deduplication candidates of the +// deduplication queue and tries to deduplicate them. +// +// A deduplication hashtable is used to keep track of all unique character arrays +// used by String objects. When deduplicating, a lookup is made in this table to see +// if there is already an identical character array somewhere on the heap. If so, the +// String object is adjusted to point to that character array, releasing the reference +// to the original array allowing it to eventually be garbage collected. If the lookup +// fails the character array is instead inserted into the hashtable so that this array +// can be shared at some point in the future. +// +// Candidate selection criteria is GC specific. +// +// Interned strings are a bit special. They are explicitly deduplicated just before +// being inserted into the StringTable (to avoid counteracting C2 optimizations done +// on string literals), then they also become deduplication candidates if they reach +// the deduplication age threshold or are evacuated to an old heap region. The second +// attempt to deduplicate such strings will be in vain, but we have no fast way of +// filtering them out. This has not shown to be a problem, as the number of interned +// strings is usually dwarfed by the number of normal (non-interned) strings. +// +// For additional information on string deduplication, please see JEP 192, +// http://openjdk.java.net/jeps/192 +// + +#include "gc/shared/stringdedup/stringDedupQueue.hpp" +#include "gc/shared/stringdedup/stringDedupStat.hpp" +#include "gc/shared/stringdedup/stringDedupTable.hpp" +#include "memory/allocation.hpp" +#include "runtime/thread.hpp" + +// +// Main interface for interacting with string deduplication. +// +class StringDedup : public AllStatic { +private: + // Single state for checking if string deduplication is enabled. + static bool _enabled; + +public: + // Returns true if string deduplication is enabled. + static bool is_enabled() { + return _enabled; + } + + // Stop the deduplication thread. + static void stop(); + + // Immediately deduplicates the given String object, bypassing the + // the deduplication queue. + static void deduplicate(oop java_string); + + static void parallel_unlink(StringDedupUnlinkOrOopsDoClosure* unlink, uint worker_id); + + static void threads_do(ThreadClosure* tc); + static void print_worker_threads_on(outputStream* st); + static void verify(); + + // GC support + static void gc_prologue(bool resize_and_rehash_table); + static void gc_epilogue(); + +protected: + // Initialize string deduplication. + // QUEUE: String Dedup Queue implementation + // STAT: String Dedup Stat implementation + template + static void initialize_impl(); +}; + +// +// This closure encapsulates the closures needed when scanning +// the deduplication queue and table during the unlink_or_oops_do() operation. +// +class StringDedupUnlinkOrOopsDoClosure : public StackObj { +private: + BoolObjectClosure* _is_alive; + OopClosure* _keep_alive; + +public: + StringDedupUnlinkOrOopsDoClosure(BoolObjectClosure* is_alive, + OopClosure* keep_alive); + + // Applies and returns the result from the is_alive closure, or + // returns true if no such closure was provided. + bool is_alive(oop o) { + if (_is_alive != NULL) { + return _is_alive->do_object_b(o); + } + return true; + } + + // Applies the keep_alive closure, or does nothing if no such + // closure was provided. + void keep_alive(oop* p) { + if (_keep_alive != NULL) { + _keep_alive->do_oop(p); + } + } +}; + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedup.inline.hpp 2018-05-28 11:50:29.699068365 -0400 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_INLINE_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_INLINE_HPP + +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupThread.inline.hpp" + +template +void StringDedup::initialize_impl() { + if (UseStringDeduplication) { + _enabled = true; + StringDedupQueue::create(); + StringDedupTable::create(); + StringDedupThreadImpl::create(); + } +} + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUP_INLINE_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupQueue.cpp 2018-05-28 11:50:30.055068495 -0400 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" + +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.hpp" +#include "runtime/atomic.hpp" + +StringDedupQueue* StringDedupQueue::_queue = NULL; +volatile size_t StringDedupQueue::_claimed_index = 0; + +size_t StringDedupQueue::claim() { + return Atomic::add(size_t(1), &_claimed_index) - 1; +} + +void StringDedupQueue::unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl) { + size_t claimed_queue = claim(); + while (claimed_queue < queue()->num_queues()) { + queue()->queue_unlink_or_oops_do(cl, claimed_queue); + claimed_queue = claim(); + } +} + +void StringDedupQueue::print_statistics() { + queue()->queue_print_statistics(); +} + +void StringDedupQueue::verify() { + queue()->queue_verify(); +} + +StringDedupQueue* const StringDedupQueue::queue() { + assert(_queue != NULL, "Not yet initialized"); + return _queue; +} + + +void StringDedupQueue::gc_prologue() { + _claimed_index = 0; +} + +void StringDedupQueue::gc_epilogue() { + assert(_claimed_index >= queue()->num_queues() || _claimed_index == 0, "All or nothing"); +} --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupQueue.hpp 2018-05-28 11:50:30.422068627 -0400 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_HPP + +#include "memory/allocation.hpp" +#include "oops/oop.hpp" + +class StringDedupUnlinkOrOopsDoClosure; + +// +// The deduplication queue acts as the communication channel between mark/evacuation +// phase and the concurrent deduplication phase. Deduplication candidates +// found during mark/evacuation are placed on this queue for later processing in the +// deduplication thread. A queue entry is an oop pointing to a String object (as opposed +// to entries in the deduplication hashtable which points to character arrays). +// +// While users of the queue treat it as a single queue, it is implemented as a set of +// queues, one queue per GC worker thread, to allow lock-free and cache-friendly enqueue +// operations by the GC workers. +// +// The oops in the queue are treated as weak pointers, meaning the objects they point to +// can become unreachable and pruned (cleared) before being popped by the deduplication +// thread. +// +// Pushing to the queue is thread safe (this relies on each thread using a unique worker +// id). Popping from the queue is NOT thread safe and can only be done by the deduplication +// thread outside a safepoint. +// + +class StringDedupQueue : public CHeapObj { +private: + static StringDedupQueue* _queue; + static volatile size_t _claimed_index; + +public: + template + static void create(); + + // Blocks and waits for the queue to become non-empty. + static inline void wait(); + + // Wakes up any thread blocked waiting for the queue to become non-empty. + static inline void cancel_wait(); + + // Pushes a deduplication candidate onto a specific GC worker queue. + static inline void push(uint worker_id, oop java_string); + + // Pops a deduplication candidate from any queue, returns NULL if + // all queues are empty. + static inline oop pop(); + + static void unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl); + + static void print_statistics(); + static void verify(); + + // GC support + static void gc_prologue(); + static void gc_epilogue(); + +protected: + static StringDedupQueue* const queue(); + + // Queue interface. + + // Blocks and waits for the queue to become non-empty. + virtual void queue_wait() = 0; + + // Wakes up any thread blocked waiting for the queue to become non-empty. + virtual void queue_cancel_wait() = 0; + + // Pushes a deduplication candidate onto a specific GC worker queue. + virtual void queue_push(uint worker_id, oop java_string) = 0; + + // Pops a deduplication candidate from any queue, returns NULL if + // all queues are empty. + virtual oop queue_pop() = 0; + + virtual void queue_unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, size_t queue) = 0; + + virtual void queue_print_statistics() = 0; + virtual void queue_verify() = 0; + + virtual size_t num_queues() const = 0; + + static size_t claim(); +}; + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupQueue.inline.hpp 2018-05-28 11:50:30.785068759 -0400 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_INLINE_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_INLINE_HPP + +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.hpp" + +template +void StringDedupQueue::create() { + assert(StringDedup::is_enabled(), "Must be enabled"); + assert(_queue == NULL, "Can have only one queue"); + _queue = new Q; +} + +void StringDedupQueue::wait() { + queue()->queue_wait(); +} + +void StringDedupQueue::cancel_wait() { + queue()->queue_cancel_wait(); +} + +void StringDedupQueue::push(uint worker_id, oop java_string) { + queue()->queue_push(worker_id, java_string); +} + +oop StringDedupQueue::pop() { + return queue()->queue_pop(); +} + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPQUEUE_INLINE_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp 2018-05-28 11:50:31.145068889 -0400 @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/shared/stringdedup/stringDedupStat.hpp" +#include "logging/log.hpp" + +StringDedupStat::StringDedupStat() : + _inspected(0), + _skipped(0), + _hashed(0), + _known(0), + _new(0), + _new_bytes(0), + _deduped(0), + _deduped_bytes(0), + _idle(0), + _exec(0), + _block(0), + _start_concurrent(0.0), + _end_concurrent(0.0), + _start_phase(0.0), + _idle_elapsed(0.0), + _exec_elapsed(0.0), + _block_elapsed(0.0) { +} + +void StringDedupStat::add(const StringDedupStat* const stat) { + _inspected += stat->_inspected; + _skipped += stat->_skipped; + _hashed += stat->_hashed; + _known += stat->_known; + _new += stat->_new; + _new_bytes += stat->_new_bytes; + _deduped += stat->_deduped; + _deduped_bytes += stat->_deduped_bytes; + _idle += stat->_idle; + _exec += stat->_exec; + _block += stat->_block; + _idle_elapsed += stat->_idle_elapsed; + _exec_elapsed += stat->_exec_elapsed; + _block_elapsed += stat->_block_elapsed; +} + +void StringDedupStat::print_start(const StringDedupStat* last_stat) { + log_info(gc, stringdedup)( + "Concurrent String Deduplication (" STRDEDUP_TIME_FORMAT ")", + STRDEDUP_TIME_PARAM(last_stat->_start_concurrent)); +} + +void StringDedupStat::print_end(const StringDedupStat* last_stat, const StringDedupStat* total_stat) { + double total_deduped_bytes_percent = 0.0; + + if (total_stat->_new_bytes > 0) { + // Avoid division by zero + total_deduped_bytes_percent = percent_of(total_stat->_deduped_bytes, total_stat->_new_bytes); + } + + log_info(gc, stringdedup)( + "Concurrent String Deduplication " + STRDEDUP_BYTES_FORMAT_NS "->" STRDEDUP_BYTES_FORMAT_NS "(" STRDEDUP_BYTES_FORMAT_NS ") " + "avg " STRDEDUP_PERCENT_FORMAT_NS " " + "(" STRDEDUP_TIME_FORMAT ", " STRDEDUP_TIME_FORMAT ") " STRDEDUP_TIME_FORMAT_MS, + STRDEDUP_BYTES_PARAM(last_stat->_new_bytes), + STRDEDUP_BYTES_PARAM(last_stat->_new_bytes - last_stat->_deduped_bytes), + STRDEDUP_BYTES_PARAM(last_stat->_deduped_bytes), + total_deduped_bytes_percent, + STRDEDUP_TIME_PARAM(last_stat->_start_concurrent), + STRDEDUP_TIME_PARAM(last_stat->_end_concurrent), + STRDEDUP_TIME_PARAM_MS(last_stat->_exec_elapsed)); +} + +void StringDedupStat::reset() { + _inspected = 0; + _skipped = 0; + _hashed = 0; + _known = 0; + _new = 0; + _new_bytes = 0; + _deduped = 0; + _deduped_bytes = 0; + _idle = 0; + _exec = 0; + _block = 0; + _start_concurrent = 0.0; + _end_concurrent = 0.0; + _start_phase = 0.0; + _idle_elapsed = 0.0; + _exec_elapsed = 0.0; + _block_elapsed = 0.0; +} + +void StringDedupStat::print_statistics(bool total) const { + double skipped_percent = percent_of(_skipped, _inspected); + double hashed_percent = percent_of(_hashed, _inspected); + double known_percent = percent_of(_known, _inspected); + double new_percent = percent_of(_new, _inspected); + double deduped_percent = percent_of(_deduped, _new); + double deduped_bytes_percent = percent_of(_deduped_bytes, _new_bytes); +/* + double deduped_young_percent = percent_of(stat._deduped_young, stat._deduped); + double deduped_young_bytes_percent = percent_of(stat._deduped_young_bytes, stat._deduped_bytes); + double deduped_old_percent = percent_of(stat._deduped_old, stat._deduped); + double deduped_old_bytes_percent = percent_of(stat._deduped_old_bytes, stat._deduped_bytes); +*/ + if (total) { + log_debug(gc, stringdedup)( + " Total Exec: " UINTX_FORMAT "/" STRDEDUP_TIME_FORMAT_MS + ", Idle: " UINTX_FORMAT "/" STRDEDUP_TIME_FORMAT_MS + ", Blocked: " UINTX_FORMAT "/" STRDEDUP_TIME_FORMAT_MS, + _exec, STRDEDUP_TIME_PARAM_MS(_exec_elapsed), + _idle, STRDEDUP_TIME_PARAM_MS(_idle_elapsed), + _block, STRDEDUP_TIME_PARAM_MS(_block_elapsed)); + } else { + log_debug(gc, stringdedup)( + " Last Exec: " STRDEDUP_TIME_FORMAT_MS + ", Idle: " STRDEDUP_TIME_FORMAT_MS + ", Blocked: " UINTX_FORMAT "/" STRDEDUP_TIME_FORMAT_MS, + STRDEDUP_TIME_PARAM_MS(_exec_elapsed), + STRDEDUP_TIME_PARAM_MS(_idle_elapsed), + _block, STRDEDUP_TIME_PARAM_MS(_block_elapsed)); + } + log_debug(gc, stringdedup)(" Inspected: " STRDEDUP_OBJECTS_FORMAT, _inspected); + log_debug(gc, stringdedup)(" Skipped: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", _skipped, skipped_percent); + log_debug(gc, stringdedup)(" Hashed: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", _hashed, hashed_percent); + log_debug(gc, stringdedup)(" Known: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", _known, known_percent); + log_debug(gc, stringdedup)(" New: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT, + _new, new_percent, STRDEDUP_BYTES_PARAM(_new_bytes)); + log_debug(gc, stringdedup)(" Deduplicated: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", + _deduped, deduped_percent, STRDEDUP_BYTES_PARAM(_deduped_bytes), deduped_bytes_percent); +/* + log_debug(gc, stringdedup)(" Young: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", + stat._deduped_young, deduped_young_percent, STRDEDUP_BYTES_PARAM(stat._deduped_young_bytes), deduped_young_bytes_percent); + log_debug(gc, stringdedup)(" Old: " STRDEDUP_OBJECTS_FORMAT "(" STRDEDUP_PERCENT_FORMAT ") " STRDEDUP_BYTES_FORMAT "(" STRDEDUP_PERCENT_FORMAT ")", + stat._deduped_old, deduped_old_percent, STRDEDUP_BYTES_PARAM(stat._deduped_old_bytes), deduped_old_bytes_percent); +*/ +} --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp 2018-05-28 11:50:31.507069020 -0400 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPSTAT_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPSTAT_HPP + +#include "memory/allocation.hpp" +#include "runtime/os.hpp" + +// Macros for GC log output formating +#define STRDEDUP_OBJECTS_FORMAT UINTX_FORMAT_W(12) +#define STRDEDUP_TIME_FORMAT "%.3fs" +#define STRDEDUP_TIME_PARAM(time) (time) +#define STRDEDUP_TIME_FORMAT_MS "%.3fms" +#define STRDEDUP_TIME_PARAM_MS(time) ((time) * MILLIUNITS) +#define STRDEDUP_PERCENT_FORMAT "%5.1f%%" +#define STRDEDUP_PERCENT_FORMAT_NS "%.1f%%" +#define STRDEDUP_BYTES_FORMAT "%8.1f%s" +#define STRDEDUP_BYTES_FORMAT_NS "%.1f%s" +#define STRDEDUP_BYTES_PARAM(bytes) byte_size_in_proper_unit((double)(bytes)), proper_unit_for_byte_size((bytes)) + +// +// Statistics gathered by the deduplication thread. +// +class StringDedupStat : public CHeapObj { +protected: + // Counters + uintx _inspected; + uintx _skipped; + uintx _hashed; + uintx _known; + uintx _new; + uintx _new_bytes; + uintx _deduped; + uintx _deduped_bytes; + uintx _idle; + uintx _exec; + uintx _block; + + // Time spent by the deduplication thread in different phases + double _start_concurrent; + double _end_concurrent; + double _start_phase; + double _idle_elapsed; + double _exec_elapsed; + double _block_elapsed; + +public: + StringDedupStat(); + + void inc_inspected() { + _inspected++; + } + + void inc_skipped() { + _skipped++; + } + + void inc_hashed() { + _hashed++; + } + + void inc_known() { + _known++; + } + + void inc_new(uintx bytes) { + _new++; + _new_bytes += bytes; + } + + virtual void deduped(oop obj, uintx bytes) { + _deduped++; + _deduped_bytes += bytes; + } + + void mark_idle() { + _start_phase = os::elapsedTime(); + _idle++; + } + + void mark_exec() { + double now = os::elapsedTime(); + _idle_elapsed = now - _start_phase; + _start_phase = now; + _start_concurrent = now; + _exec++; + } + + void mark_block() { + double now = os::elapsedTime(); + _exec_elapsed += now - _start_phase; + _start_phase = now; + _block++; + } + + void mark_unblock() { + double now = os::elapsedTime(); + _block_elapsed += now - _start_phase; + _start_phase = now; + } + + void mark_done() { + double now = os::elapsedTime(); + _exec_elapsed += now - _start_phase; + _end_concurrent = now; + } + + virtual void reset(); + virtual void add(const StringDedupStat* const stat); + virtual void print_statistics(bool total) const; + + static void print_start(const StringDedupStat* last_stat); + static void print_end(const StringDedupStat* last_stat, const StringDedupStat* total_stat); +}; + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPSTAT_HPP + --- old/src/hotspot/share/gc/g1/g1StringDedupTable.cpp 2018-05-28 11:50:32.030069209 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,625 +0,0 @@ -/* - * Copyright (c) 2014, 2018, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "precompiled.hpp" -#include "classfile/altHashing.hpp" -#include "classfile/javaClasses.inline.hpp" -#include "gc/g1/g1BarrierSet.hpp" -#include "gc/g1/g1CollectedHeap.inline.hpp" -#include "gc/g1/g1StringDedup.hpp" -#include "gc/g1/g1StringDedupTable.hpp" -#include "logging/log.hpp" -#include "memory/padded.inline.hpp" -#include "oops/arrayOop.inline.hpp" -#include "oops/oop.inline.hpp" -#include "oops/typeArrayOop.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/safepointVerifiers.hpp" - -// -// List of deduplication table entries. Links table -// entries together using their _next fields. -// -class G1StringDedupEntryList : public CHeapObj { -private: - G1StringDedupEntry* _list; - size_t _length; - -public: - G1StringDedupEntryList() : - _list(NULL), - _length(0) { - } - - void add(G1StringDedupEntry* entry) { - entry->set_next(_list); - _list = entry; - _length++; - } - - G1StringDedupEntry* remove() { - G1StringDedupEntry* entry = _list; - if (entry != NULL) { - _list = entry->next(); - _length--; - } - return entry; - } - - G1StringDedupEntry* remove_all() { - G1StringDedupEntry* list = _list; - _list = NULL; - return list; - } - - size_t length() { - return _length; - } -}; - -// -// Cache of deduplication table entries. This cache provides fast allocation and -// reuse of table entries to lower the pressure on the underlying allocator. -// But more importantly, it provides fast/deferred freeing of table entries. This -// is important because freeing of table entries is done during stop-the-world -// phases and it is not uncommon for large number of entries to be freed at once. -// Tables entries that are freed during these phases are placed onto a freelist in -// the cache. The deduplication thread, which executes in a concurrent phase, will -// later reuse or free the underlying memory for these entries. -// -// The cache allows for single-threaded allocations and multi-threaded frees. -// Allocations are synchronized by StringDedupTable_lock as part of a table -// modification. -// -class G1StringDedupEntryCache : public CHeapObj { -private: - // 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* _cached; - PaddedEnd* _overflowed; - -public: - G1StringDedupEntryCache(size_t max_size); - ~G1StringDedupEntryCache(); - - // 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. - void free(G1StringDedupEntry* entry, uint worker_id); - - // Returns current number of entries in the cache. - size_t size(); - - // Deletes overflowed entries. - void delete_overflowed(); -}; - -G1StringDedupEntryCache::G1StringDedupEntryCache(size_t max_size) : - _nlists(ParallelGCThreads), - _max_list_length(0), - _cached(PaddedArray::create_unfreeable((uint)_nlists)), - _overflowed(PaddedArray::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 = _cached[i].remove(); - if (entry != NULL) { - return entry; - } - } - return new G1StringDedupEntry(); -} - -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); - - 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 += _cached[i].length(); - } - return size; -} - -void G1StringDedupEntryCache::delete_overflowed() { - double start = os::elapsedTime(); - uintx count = 0; - - for (size_t i = 0; i < _nlists; i++) { - 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_MS, - count, G1_STRDEDUP_TIME_PARAM_MS(end - start)); -} - -G1StringDedupTable* G1StringDedupTable::_table = NULL; -G1StringDedupEntryCache* G1StringDedupTable::_entry_cache = NULL; - -const size_t G1StringDedupTable::_min_size = (1 << 10); // 1024 -const size_t G1StringDedupTable::_max_size = (1 << 24); // 16777216 -const double G1StringDedupTable::_grow_load_factor = 2.0; // Grow table at 200% load -const double G1StringDedupTable::_shrink_load_factor = _grow_load_factor / 3.0; // Shrink table at 67% load -const double G1StringDedupTable::_max_cache_factor = 0.1; // Cache a maximum of 10% of the table size -const uintx G1StringDedupTable::_rehash_multiple = 60; // Hash bucket has 60 times more collisions than expected -const uintx G1StringDedupTable::_rehash_threshold = (uintx)(_rehash_multiple * _grow_load_factor); - -uintx G1StringDedupTable::_entries_added = 0; -uintx G1StringDedupTable::_entries_removed = 0; -uintx G1StringDedupTable::_resize_count = 0; -uintx G1StringDedupTable::_rehash_count = 0; - -G1StringDedupTable::G1StringDedupTable(size_t size, jint hash_seed) : - _size(size), - _entries(0), - _grow_threshold((uintx)(size * _grow_load_factor)), - _shrink_threshold((uintx)(size * _shrink_load_factor)), - _rehash_needed(false), - _hash_seed(hash_seed) { - assert(is_power_of_2(size), "Table size must be a power of 2"); - _buckets = NEW_C_HEAP_ARRAY(G1StringDedupEntry*, _size, mtGC); - memset(_buckets, 0, _size * sizeof(G1StringDedupEntry*)); -} - -G1StringDedupTable::~G1StringDedupTable() { - FREE_C_HEAP_ARRAY(G1StringDedupEntry*, _buckets); -} - -void G1StringDedupTable::create() { - assert(_table == NULL, "One string deduplication table allowed"); - _entry_cache = new G1StringDedupEntryCache(_min_size * _max_cache_factor); - _table = new G1StringDedupTable(_min_size); -} - -void G1StringDedupTable::add(typeArrayOop value, bool latin1, unsigned int hash, G1StringDedupEntry** list) { - G1StringDedupEntry* entry = _entry_cache->alloc(); - entry->set_obj(value); - entry->set_hash(hash); - entry->set_latin1(latin1); - entry->set_next(*list); - *list = entry; - _entries++; -} - -void G1StringDedupTable::remove(G1StringDedupEntry** pentry, uint worker_id) { - G1StringDedupEntry* entry = *pentry; - *pentry = entry->next(); - _entry_cache->free(entry, worker_id); -} - -void G1StringDedupTable::transfer(G1StringDedupEntry** pentry, G1StringDedupTable* dest) { - G1StringDedupEntry* entry = *pentry; - *pentry = entry->next(); - unsigned int hash = entry->hash(); - size_t index = dest->hash_to_index(hash); - G1StringDedupEntry** list = dest->bucket(index); - entry->set_next(*list); - *list = entry; -} - -bool G1StringDedupTable::equals(typeArrayOop value1, typeArrayOop value2) { - return (value1 == value2 || - (value1->length() == value2->length() && - (!memcmp(value1->base(T_BYTE), - value2->base(T_BYTE), - value1->length() * sizeof(jbyte))))); -} - -typeArrayOop G1StringDedupTable::lookup(typeArrayOop value, bool latin1, unsigned int hash, - G1StringDedupEntry** list, uintx &count) { - for (G1StringDedupEntry* entry = *list; entry != NULL; entry = entry->next()) { - if (entry->hash() == hash && entry->latin1() == latin1) { - typeArrayOop existing_value = entry->obj(); - if (equals(value, existing_value)) { - // Match found - return existing_value; - } - } - count++; - } - - // Not found - return NULL; -} - -typeArrayOop G1StringDedupTable::lookup_or_add_inner(typeArrayOop value, bool latin1, unsigned int hash) { - size_t index = hash_to_index(hash); - G1StringDedupEntry** list = bucket(index); - uintx count = 0; - - // Lookup in list - typeArrayOop existing_value = lookup(value, latin1, hash, list, count); - - // Check if rehash is needed - if (count > _rehash_threshold) { - _rehash_needed = true; - } - - if (existing_value == NULL) { - // Not found, add new entry - add(value, latin1, hash, list); - - // Update statistics - _entries_added++; - } - - return existing_value; -} - -unsigned int G1StringDedupTable::hash_code(typeArrayOop value, bool latin1) { - unsigned int hash; - int length = value->length(); - if (latin1) { - const jbyte* data = (jbyte*)value->base(T_BYTE); - if (use_java_hash()) { - hash = java_lang_String::hash_code(data, length); - } else { - hash = AltHashing::murmur3_32(_table->_hash_seed, data, length); - } - } else { - length /= sizeof(jchar) / sizeof(jbyte); // Convert number of bytes to number of chars - const jchar* data = (jchar*)value->base(T_CHAR); - if (use_java_hash()) { - hash = java_lang_String::hash_code(data, length); - } else { - hash = AltHashing::murmur3_32(_table->_hash_seed, data, length); - } - } - - return hash; -} - -void G1StringDedupTable::deduplicate(oop java_string, G1StringDedupStat& stat) { - assert(java_lang_String::is_instance(java_string), "Must be a string"); - NoSafepointVerifier nsv; - - stat.inc_inspected(); - - typeArrayOop value = java_lang_String::value(java_string); - if (value == NULL) { - // String has no value - stat.inc_skipped(); - return; - } - - bool latin1 = java_lang_String::is_latin1(java_string); - unsigned int hash = 0; - - if (use_java_hash()) { - // Get hash code from cache - hash = java_lang_String::hash(java_string); - } - - if (hash == 0) { - // Compute hash - hash = hash_code(value, latin1); - stat.inc_hashed(); - - if (use_java_hash() && hash != 0) { - // Store hash code in cache - java_lang_String::set_hash(java_string, hash); - } - } - - typeArrayOop existing_value = lookup_or_add(value, latin1, hash); - if (existing_value == value) { - // Same value, already known - stat.inc_known(); - return; - } - - // Get size of value array - uintx size_in_bytes = value->size() * HeapWordSize; - stat.inc_new(size_in_bytes); - - if (existing_value != NULL) { - // Enqueue the reference to make sure it is kept alive. Concurrent mark might - // otherwise declare it dead if there are no other strong references to this object. - G1BarrierSet::enqueue(existing_value); - - // Existing value found, deduplicate string - java_lang_String::set_value(java_string, existing_value); - - if (G1CollectedHeap::heap()->is_in_young(value)) { - stat.inc_deduped_young(size_in_bytes); - } else { - stat.inc_deduped_old(size_in_bytes); - } - } -} - -G1StringDedupTable* G1StringDedupTable::prepare_resize() { - size_t size = _table->_size; - - // Check if the hashtable needs to be resized - if (_table->_entries > _table->_grow_threshold) { - // Grow table, double the size - size *= 2; - if (size > _max_size) { - // Too big, don't resize - return NULL; - } - } else if (_table->_entries < _table->_shrink_threshold) { - // Shrink table, half the size - size /= 2; - if (size < _min_size) { - // Too small, don't resize - return NULL; - } - } else if (StringDeduplicationResizeALot) { - // Force grow - size *= 2; - if (size > _max_size) { - // Too big, force shrink instead - size /= 4; - } - } else { - // Resize not needed - return NULL; - } - - // 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); -} - -void G1StringDedupTable::finish_resize(G1StringDedupTable* resized_table) { - assert(resized_table != NULL, "Invalid table"); - - resized_table->_entries = _table->_entries; - - // Free old table - delete _table; - - // Install new table - _table = resized_table; -} - -void G1StringDedupTable::unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, uint worker_id) { - // The table is divided into partitions to allow lock-less parallel processing by - // multiple worker threads. A worker thread first claims a partition, which ensures - // exclusive access to that part of the table, then continues to process it. To allow - // shrinking of the table in parallel we also need to make sure that the same worker - // thread processes all partitions where entries will hash to the same destination - // partition. Since the table size is always a power of two and we always shrink by - // dividing the table in half, we know that for a given partition there is only one - // other partition whoes entries will hash to the same destination partition. That - // other partition is always the sibling partition in the second half of the table. - // For example, if the table is divided into 8 partitions, the sibling of partition 0 - // is partition 4, the sibling of partition 1 is partition 5, etc. - size_t table_half = _table->_size / 2; - - // Let each partition be one page worth of buckets - size_t partition_size = MIN2(table_half, os::vm_page_size() / sizeof(G1StringDedupEntry*)); - assert(table_half % partition_size == 0, "Invalid partition size"); - - // Number of entries removed during the scan - uintx removed = 0; - - for (;;) { - // Grab next partition to scan - size_t partition_begin = cl->claim_table_partition(partition_size); - size_t partition_end = partition_begin + partition_size; - if (partition_begin >= table_half) { - // End of table - break; - } - - // Scan the partition followed by the sibling partition in the second half of the table - removed += unlink_or_oops_do(cl, partition_begin, partition_end, worker_id); - removed += unlink_or_oops_do(cl, table_half + partition_begin, table_half + partition_end, worker_id); - } - - // Delayed update to avoid contention on the table lock - if (removed > 0) { - MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag); - _table->_entries -= removed; - _entries_removed += removed; - } -} - -uintx G1StringDedupTable::unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, - size_t partition_begin, - size_t partition_end, - uint worker_id) { - uintx removed = 0; - for (size_t bucket = partition_begin; bucket < partition_end; bucket++) { - G1StringDedupEntry** entry = _table->bucket(bucket); - while (*entry != NULL) { - oop* p = (oop*)(*entry)->obj_addr(); - if (cl->is_alive(*p)) { - cl->keep_alive(p); - if (cl->is_resizing()) { - // We are resizing the table, transfer entry to the new table - _table->transfer(entry, cl->resized_table()); - } else { - if (cl->is_rehashing()) { - // We are rehashing the table, rehash the entry but keep it - // in the table. We can't transfer entries into the new table - // at this point since we don't have exclusive access to all - // destination partitions. finish_rehash() will do a single - // threaded transfer of all entries. - typeArrayOop value = (typeArrayOop)*p; - bool latin1 = (*entry)->latin1(); - unsigned int hash = hash_code(value, latin1); - (*entry)->set_hash(hash); - } - - // Move to next entry - entry = (*entry)->next_addr(); - } - } else { - // Not alive, remove entry from table - _table->remove(entry, worker_id); - removed++; - } - } - } - - return removed; -} - -G1StringDedupTable* G1StringDedupTable::prepare_rehash() { - if (!_table->_rehash_needed && !StringDeduplicationRehashALot) { - // Rehash not needed - return NULL; - } - - // Update statistics - _rehash_count++; - - // Compute new hash seed - _table->_hash_seed = AltHashing::compute_seed(); - - // Allocate the new table, same size and hash seed - return new G1StringDedupTable(_table->_size, _table->_hash_seed); -} - -void G1StringDedupTable::finish_rehash(G1StringDedupTable* rehashed_table) { - assert(rehashed_table != NULL, "Invalid table"); - - // Move all newly rehashed entries into the correct buckets in the new table - for (size_t bucket = 0; bucket < _table->_size; bucket++) { - G1StringDedupEntry** entry = _table->bucket(bucket); - while (*entry != NULL) { - _table->transfer(entry, rehashed_table); - } - } - - rehashed_table->_entries = _table->_entries; - - // Free old table - delete _table; - - // Install new table - _table = rehashed_table; -} - -void G1StringDedupTable::verify() { - for (size_t bucket = 0; bucket < _table->_size; bucket++) { - // Verify entries - G1StringDedupEntry** entry = _table->bucket(bucket); - while (*entry != NULL) { - typeArrayOop value = (*entry)->obj(); - guarantee(value != NULL, "Object must not be NULL"); - guarantee(G1CollectedHeap::heap()->is_in_reserved(value), "Object must be on the heap"); - guarantee(!value->is_forwarded(), "Object must not be forwarded"); - guarantee(value->is_typeArray(), "Object must be a typeArrayOop"); - bool latin1 = (*entry)->latin1(); - unsigned int hash = hash_code(value, latin1); - guarantee((*entry)->hash() == hash, "Table entry has inorrect hash"); - guarantee(_table->hash_to_index(hash) == bucket, "Table entry has incorrect index"); - entry = (*entry)->next_addr(); - } - - // Verify that we do not have entries with identical oops or identical arrays. - // We only need to compare entries in the same bucket. If the same oop or an - // identical array has been inserted more than once into different/incorrect - // buckets the verification step above will catch that. - G1StringDedupEntry** entry1 = _table->bucket(bucket); - while (*entry1 != NULL) { - typeArrayOop value1 = (*entry1)->obj(); - bool latin1_1 = (*entry1)->latin1(); - G1StringDedupEntry** entry2 = (*entry1)->next_addr(); - while (*entry2 != NULL) { - typeArrayOop value2 = (*entry2)->obj(); - bool latin1_2 = (*entry2)->latin1(); - guarantee(latin1_1 != latin1_2 || !equals(value1, value2), "Table entries must not have identical arrays"); - entry2 = (*entry2)->next_addr(); - } - entry1 = (*entry1)->next_addr(); - } - } -} - -void G1StringDedupTable::clean_entry_cache() { - _entry_cache->delete_overflowed(); -} - -void G1StringDedupTable::print_statistics() { - Log(gc, stringdedup) log; - log.debug(" Table"); - log.debug(" Memory Usage: " G1_STRDEDUP_BYTES_FORMAT_NS, - G1_STRDEDUP_BYTES_PARAM(_table->_size * sizeof(G1StringDedupEntry*) + (_table->_entries + _entry_cache->size()) * sizeof(G1StringDedupEntry))); - log.debug(" Size: " SIZE_FORMAT ", Min: " SIZE_FORMAT ", Max: " SIZE_FORMAT, _table->_size, _min_size, _max_size); - log.debug(" Entries: " UINTX_FORMAT ", Load: " G1_STRDEDUP_PERCENT_FORMAT_NS ", Cached: " UINTX_FORMAT ", Added: " UINTX_FORMAT ", Removed: " UINTX_FORMAT, - _table->_entries, percent_of(_table->_entries, _table->_size), _entry_cache->size(), _entries_added, _entries_removed); - log.debug(" Resize Count: " UINTX_FORMAT ", Shrink Threshold: " UINTX_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT_NS "), Grow Threshold: " UINTX_FORMAT "(" G1_STRDEDUP_PERCENT_FORMAT_NS ")", - _resize_count, _table->_shrink_threshold, _shrink_load_factor * 100.0, _table->_grow_threshold, _grow_load_factor * 100.0); - log.debug(" Rehash Count: " UINTX_FORMAT ", Rehash Threshold: " UINTX_FORMAT ", Hash Seed: 0x%x", _rehash_count, _rehash_threshold, _table->_hash_seed); - log.debug(" Age Threshold: " UINTX_FORMAT, StringDeduplicationAgeThreshold); -} --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp 2018-05-28 11:50:31.870069152 -0400 @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2014, 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/altHashing.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupTable.hpp" +#include "gc/shared/suspendibleThreadSet.hpp" +#include "logging/log.hpp" +#include "memory/padded.inline.hpp" +#include "oops/access.inline.hpp" +#include "oops/arrayOop.inline.hpp" +#include "oops/oop.inline.hpp" +#include "oops/typeArrayOop.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/safepointVerifiers.hpp" + +// +// List of deduplication table entries. Links table +// entries together using their _next fields. +// +class StringDedupEntryList : public CHeapObj { +private: + StringDedupEntry* _list; + size_t _length; + +public: + StringDedupEntryList() : + _list(NULL), + _length(0) { + } + + void add(StringDedupEntry* entry) { + entry->set_next(_list); + _list = entry; + _length++; + } + + StringDedupEntry* remove() { + StringDedupEntry* entry = _list; + if (entry != NULL) { + _list = entry->next(); + _length--; + } + return entry; + } + + StringDedupEntry* remove_all() { + StringDedupEntry* list = _list; + _list = NULL; + return list; + } + + size_t length() { + return _length; + } +}; + +// +// Cache of deduplication table entries. This cache provides fast allocation and +// reuse of table entries to lower the pressure on the underlying allocator. +// But more importantly, it provides fast/deferred freeing of table entries. This +// is important because freeing of table entries is done during stop-the-world +// phases and it is not uncommon for large number of entries to be freed at once. +// Tables entries that are freed during these phases are placed onto a freelist in +// the cache. The deduplication thread, which executes in a concurrent phase, will +// later reuse or free the underlying memory for these entries. +// +// The cache allows for single-threaded allocations and multi-threaded frees. +// Allocations are synchronized by StringDedupTable_lock as part of a table +// modification. +// +class StringDedupEntryCache : public CHeapObj { +private: + // 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* _cached; + PaddedEnd* _overflowed; + +public: + StringDedupEntryCache(size_t max_size); + ~StringDedupEntryCache(); + + // 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. + StringDedupEntry* alloc(); + + // Insert a table entry into the cache. + void free(StringDedupEntry* entry, uint worker_id); + + // Returns current number of entries in the cache. + size_t size(); + + // Deletes overflowed entries. + void delete_overflowed(); +}; + +StringDedupEntryCache::StringDedupEntryCache(size_t max_size) : + _nlists(ParallelGCThreads), + _max_list_length(0), + _cached(PaddedArray::create_unfreeable((uint)_nlists)), + _overflowed(PaddedArray::create_unfreeable((uint)_nlists)) { + set_max_size(max_size); +} + +StringDedupEntryCache::~StringDedupEntryCache() { + ShouldNotReachHere(); +} + +void StringDedupEntryCache::set_max_size(size_t size) { + _max_list_length = size / _nlists; +} + +StringDedupEntry* StringDedupEntryCache::alloc() { + for (size_t i = 0; i < _nlists; i++) { + StringDedupEntry* entry = _cached[i].remove(); + if (entry != NULL) { + return entry; + } + } + return new StringDedupEntry(); +} + +void StringDedupEntryCache::free(StringDedupEntry* 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); + + 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 StringDedupEntryCache::size() { + size_t size = 0; + for (size_t i = 0; i < _nlists; i++) { + size += _cached[i].length(); + } + return size; +} + +void StringDedupEntryCache::delete_overflowed() { + double start = os::elapsedTime(); + uintx count = 0; + + for (size_t i = 0; i < _nlists; i++) { + StringDedupEntry* 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) { + StringDedupEntry* next = entry->next(); + delete entry; + entry = next; + count++; + } + } + + double end = os::elapsedTime(); + log_trace(gc, stringdedup)("Deleted " UINTX_FORMAT " entries, " STRDEDUP_TIME_FORMAT_MS, + count, STRDEDUP_TIME_PARAM_MS(end - start)); +} + +StringDedupTable* StringDedupTable::_table = NULL; +StringDedupEntryCache* StringDedupTable::_entry_cache = NULL; + +const size_t StringDedupTable::_min_size = (1 << 10); // 1024 +const size_t StringDedupTable::_max_size = (1 << 24); // 16777216 +const double StringDedupTable::_grow_load_factor = 2.0; // Grow table at 200% load +const double StringDedupTable::_shrink_load_factor = _grow_load_factor / 3.0; // Shrink table at 67% load +const double StringDedupTable::_max_cache_factor = 0.1; // Cache a maximum of 10% of the table size +const uintx StringDedupTable::_rehash_multiple = 60; // Hash bucket has 60 times more collisions than expected +const uintx StringDedupTable::_rehash_threshold = (uintx)(_rehash_multiple * _grow_load_factor); + +uintx StringDedupTable::_entries_added = 0; +uintx StringDedupTable::_entries_removed = 0; +uintx StringDedupTable::_resize_count = 0; +uintx StringDedupTable::_rehash_count = 0; + +StringDedupTable* StringDedupTable::_resized_table = NULL; +StringDedupTable* StringDedupTable::_rehashed_table = NULL; +volatile size_t StringDedupTable::_claimed_index = 0; + +StringDedupTable::StringDedupTable(size_t size, jint hash_seed) : + _size(size), + _entries(0), + _grow_threshold((uintx)(size * _grow_load_factor)), + _shrink_threshold((uintx)(size * _shrink_load_factor)), + _rehash_needed(false), + _hash_seed(hash_seed) { + assert(is_power_of_2(size), "Table size must be a power of 2"); + _buckets = NEW_C_HEAP_ARRAY(StringDedupEntry*, _size, mtGC); + memset(_buckets, 0, _size * sizeof(StringDedupEntry*)); +} + +StringDedupTable::~StringDedupTable() { + FREE_C_HEAP_ARRAY(G1StringDedupEntry*, _buckets); +} + +void StringDedupTable::create() { + assert(_table == NULL, "One string deduplication table allowed"); + _entry_cache = new StringDedupEntryCache(_min_size * _max_cache_factor); + _table = new StringDedupTable(_min_size); +} + +void StringDedupTable::add(typeArrayOop value, bool latin1, unsigned int hash, StringDedupEntry** list) { + StringDedupEntry* entry = _entry_cache->alloc(); + entry->set_obj(value); + entry->set_hash(hash); + entry->set_latin1(latin1); + entry->set_next(*list); + *list = entry; + _entries++; +} + +void StringDedupTable::remove(StringDedupEntry** pentry, uint worker_id) { + StringDedupEntry* entry = *pentry; + *pentry = entry->next(); + _entry_cache->free(entry, worker_id); +} + +void StringDedupTable::transfer(StringDedupEntry** pentry, StringDedupTable* dest) { + StringDedupEntry* entry = *pentry; + *pentry = entry->next(); + unsigned int hash = entry->hash(); + size_t index = dest->hash_to_index(hash); + StringDedupEntry** list = dest->bucket(index); + entry->set_next(*list); + *list = entry; +} + +bool StringDedupTable::equals(typeArrayOop value1, typeArrayOop value2) { + return (oopDesc::equals(value1, value2) || + (value1->length() == value2->length() && + (!memcmp(value1->base(T_BYTE), + value2->base(T_BYTE), + value1->length() * sizeof(jbyte))))); +} + +typeArrayOop StringDedupTable::lookup(typeArrayOop value, bool latin1, unsigned int hash, + StringDedupEntry** list, uintx &count) { + for (StringDedupEntry* entry = *list; entry != NULL; entry = entry->next()) { + if (entry->hash() == hash && entry->latin1() == latin1) { + typeArrayOop existing_value = entry->obj(); + if (equals(value, existing_value)) { + // Apply proper barrier to make sure it is kept alive. Concurrent mark might + // otherwise declare it dead if there are no other strong references to this object. + oop* obj_addr = (oop*)entry->obj_addr(); + oop obj = RootAccess::oop_load(obj_addr); + return typeArrayOop(obj); + } + } + count++; + } + + // Not found + return NULL; +} + +typeArrayOop StringDedupTable::lookup_or_add_inner(typeArrayOop value, bool latin1, unsigned int hash) { + size_t index = hash_to_index(hash); + StringDedupEntry** list = bucket(index); + uintx count = 0; + + // Lookup in list + typeArrayOop existing_value = lookup(value, latin1, hash, list, count); + + // Check if rehash is needed + if (count > _rehash_threshold) { + _rehash_needed = true; + } + + if (existing_value == NULL) { + // Not found, add new entry + add(value, latin1, hash, list); + + // Update statistics + _entries_added++; + } + + return existing_value; +} + +unsigned int StringDedupTable::hash_code(typeArrayOop value, bool latin1) { + unsigned int hash; + int length = value->length(); + if (latin1) { + const jbyte* data = (jbyte*)value->base(T_BYTE); + if (use_java_hash()) { + hash = java_lang_String::hash_code(data, length); + } else { + hash = AltHashing::murmur3_32(_table->_hash_seed, data, length); + } + } else { + length /= sizeof(jchar) / sizeof(jbyte); // Convert number of bytes to number of chars + const jchar* data = (jchar*)value->base(T_CHAR); + if (use_java_hash()) { + hash = java_lang_String::hash_code(data, length); + } else { + hash = AltHashing::murmur3_32(_table->_hash_seed, data, length); + } + } + + return hash; +} + +void StringDedupTable::deduplicate(oop java_string, StringDedupStat* stat) { + assert(java_lang_String::is_instance(java_string), "Must be a string"); + NoSafepointVerifier nsv; + + stat->inc_inspected(); + + typeArrayOop value = java_lang_String::value(java_string); + if (value == NULL) { + // String has no value + stat->inc_skipped(); + return; + } + + bool latin1 = java_lang_String::is_latin1(java_string); + unsigned int hash = 0; + + if (use_java_hash()) { + // Get hash code from cache + hash = java_lang_String::hash(java_string); + } + + if (hash == 0) { + // Compute hash + hash = hash_code(value, latin1); + stat->inc_hashed(); + + if (use_java_hash() && hash != 0) { + // Store hash code in cache + java_lang_String::set_hash(java_string, hash); + } + } + + typeArrayOop existing_value = lookup_or_add(value, latin1, hash); + if (existing_value == value) { + // Same value, already known + stat->inc_known(); + return; + } + + // Get size of value array + uintx size_in_bytes = value->size() * HeapWordSize; + stat->inc_new(size_in_bytes); + + if (existing_value != NULL) { + // Existing value found, deduplicate string + java_lang_String::set_value(java_string, existing_value); + stat->deduped(value, size_in_bytes); + } +} + +bool StringDedupTable::is_resizing() { + return _resized_table != NULL; +} + +bool StringDedupTable::is_rehashing() { + return _rehashed_table != NULL; +} + +StringDedupTable* StringDedupTable::prepare_resize() { + size_t size = _table->_size; + + // Check if the hashtable needs to be resized + if (_table->_entries > _table->_grow_threshold) { + // Grow table, double the size + size *= 2; + if (size > _max_size) { + // Too big, don't resize + return NULL; + } + } else if (_table->_entries < _table->_shrink_threshold) { + // Shrink table, half the size + size /= 2; + if (size < _min_size) { + // Too small, don't resize + return NULL; + } + } else if (StringDeduplicationResizeALot) { + // Force grow + size *= 2; + if (size > _max_size) { + // Too big, force shrink instead + size /= 4; + } + } else { + // Resize not needed + return NULL; + } + + // 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 StringDedupTable(size, _table->_hash_seed); +} + +void StringDedupTable::finish_resize(StringDedupTable* resized_table) { + assert(resized_table != NULL, "Invalid table"); + + resized_table->_entries = _table->_entries; + + // Free old table + delete _table; + + // Install new table + _table = resized_table; +} + +void StringDedupTable::unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, uint worker_id) { + // The table is divided into partitions to allow lock-less parallel processing by + // multiple worker threads. A worker thread first claims a partition, which ensures + // exclusive access to that part of the table, then continues to process it. To allow + // shrinking of the table in parallel we also need to make sure that the same worker + // thread processes all partitions where entries will hash to the same destination + // partition. Since the table size is always a power of two and we always shrink by + // dividing the table in half, we know that for a given partition there is only one + // other partition whoes entries will hash to the same destination partition. That + // other partition is always the sibling partition in the second half of the table. + // For example, if the table is divided into 8 partitions, the sibling of partition 0 + // is partition 4, the sibling of partition 1 is partition 5, etc. + size_t table_half = _table->_size / 2; + + // Let each partition be one page worth of buckets + size_t partition_size = MIN2(table_half, os::vm_page_size() / sizeof(StringDedupEntry*)); + assert(table_half % partition_size == 0, "Invalid partition size"); + + // Number of entries removed during the scan + uintx removed = 0; + + for (;;) { + // Grab next partition to scan + size_t partition_begin = claim_table_partition(partition_size); + size_t partition_end = partition_begin + partition_size; + if (partition_begin >= table_half) { + // End of table + break; + } + + // Scan the partition followed by the sibling partition in the second half of the table + removed += unlink_or_oops_do(cl, partition_begin, partition_end, worker_id); + removed += unlink_or_oops_do(cl, table_half + partition_begin, table_half + partition_end, worker_id); + } + + // Delayed update to avoid contention on the table lock + if (removed > 0) { + MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag); + _table->_entries -= removed; + _entries_removed += removed; + } +} + +uintx StringDedupTable::unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, + size_t partition_begin, + size_t partition_end, + uint worker_id) { + uintx removed = 0; + for (size_t bucket = partition_begin; bucket < partition_end; bucket++) { + StringDedupEntry** entry = _table->bucket(bucket); + while (*entry != NULL) { + oop* p = (oop*)(*entry)->obj_addr(); + if (cl->is_alive(*p)) { + cl->keep_alive(p); + if (is_resizing()) { + // We are resizing the table, transfer entry to the new table + _table->transfer(entry, _resized_table); + } else { + if (is_rehashing()) { + // We are rehashing the table, rehash the entry but keep it + // in the table. We can't transfer entries into the new table + // at this point since we don't have exclusive access to all + // destination partitions. finish_rehash() will do a single + // threaded transfer of all entries. + typeArrayOop value = (typeArrayOop)*p; + bool latin1 = (*entry)->latin1(); + unsigned int hash = hash_code(value, latin1); + (*entry)->set_hash(hash); + } + + // Move to next entry + entry = (*entry)->next_addr(); + } + } else { + // Not alive, remove entry from table + _table->remove(entry, worker_id); + removed++; + } + } + } + + return removed; +} + +void StringDedupTable::gc_prologue(bool resize_and_rehash_table) { + assert(!is_resizing() && !is_rehashing(), "Already in progress?"); + + _claimed_index = 0; + if (resize_and_rehash_table) { + // If both resize and rehash is needed, only do resize. Rehash of + // the table will eventually happen if the situation persists. + _resized_table = StringDedupTable::prepare_resize(); + if (!is_resizing()) { + _rehashed_table = StringDedupTable::prepare_rehash(); + } + } +} + +void StringDedupTable::gc_epilogue() { + assert(!is_resizing() || !is_rehashing(), "Can not both resize and rehash"); + assert(_claimed_index >= _table->_size / 2 || _claimed_index == 0, "All or nothing"); + + if (is_resizing()) { + StringDedupTable::finish_resize(_resized_table); + _resized_table = NULL; + } else if (is_rehashing()) { + StringDedupTable::finish_rehash(_rehashed_table); + _rehashed_table = NULL; + } +} + +StringDedupTable* StringDedupTable::prepare_rehash() { + if (!_table->_rehash_needed && !StringDeduplicationRehashALot) { + // Rehash not needed + return NULL; + } + + // Update statistics + _rehash_count++; + + // Compute new hash seed + _table->_hash_seed = AltHashing::compute_seed(); + + // Allocate the new table, same size and hash seed + return new StringDedupTable(_table->_size, _table->_hash_seed); +} + +void StringDedupTable::finish_rehash(StringDedupTable* rehashed_table) { + assert(rehashed_table != NULL, "Invalid table"); + + // Move all newly rehashed entries into the correct buckets in the new table + for (size_t bucket = 0; bucket < _table->_size; bucket++) { + StringDedupEntry** entry = _table->bucket(bucket); + while (*entry != NULL) { + _table->transfer(entry, rehashed_table); + } + } + + rehashed_table->_entries = _table->_entries; + + // Free old table + delete _table; + + // Install new table + _table = rehashed_table; +} + +size_t StringDedupTable::claim_table_partition(size_t partition_size) { + return Atomic::add(partition_size, &_claimed_index) - partition_size; +} + +void StringDedupTable::verify() { + for (size_t bucket = 0; bucket < _table->_size; bucket++) { + // Verify entries + StringDedupEntry** entry = _table->bucket(bucket); + while (*entry != NULL) { + typeArrayOop value = (*entry)->obj(); + guarantee(value != NULL, "Object must not be NULL"); + guarantee(Universe::heap()->is_in_reserved(value), "Object must be on the heap"); + guarantee(!value->is_forwarded(), "Object must not be forwarded"); + guarantee(value->is_typeArray(), "Object must be a typeArrayOop"); + bool latin1 = (*entry)->latin1(); + unsigned int hash = hash_code(value, latin1); + guarantee((*entry)->hash() == hash, "Table entry has inorrect hash"); + guarantee(_table->hash_to_index(hash) == bucket, "Table entry has incorrect index"); + entry = (*entry)->next_addr(); + } + + // Verify that we do not have entries with identical oops or identical arrays. + // We only need to compare entries in the same bucket. If the same oop or an + // identical array has been inserted more than once into different/incorrect + // buckets the verification step above will catch that. + StringDedupEntry** entry1 = _table->bucket(bucket); + while (*entry1 != NULL) { + typeArrayOop value1 = (*entry1)->obj(); + bool latin1_1 = (*entry1)->latin1(); + StringDedupEntry** entry2 = (*entry1)->next_addr(); + while (*entry2 != NULL) { + typeArrayOop value2 = (*entry2)->obj(); + bool latin1_2 = (*entry2)->latin1(); + guarantee(latin1_1 != latin1_2 || !equals(value1, value2), "Table entries must not have identical arrays"); + entry2 = (*entry2)->next_addr(); + } + entry1 = (*entry1)->next_addr(); + } + } +} + +void StringDedupTable::clean_entry_cache() { + _entry_cache->delete_overflowed(); +} + +void StringDedupTable::print_statistics() { + Log(gc, stringdedup) log; + log.debug(" Table"); + log.debug(" Memory Usage: " STRDEDUP_BYTES_FORMAT_NS, + STRDEDUP_BYTES_PARAM(_table->_size * sizeof(StringDedupEntry*) + (_table->_entries + _entry_cache->size()) * sizeof(StringDedupEntry))); + log.debug(" Size: " SIZE_FORMAT ", Min: " SIZE_FORMAT ", Max: " SIZE_FORMAT, _table->_size, _min_size, _max_size); + log.debug(" Entries: " UINTX_FORMAT ", Load: " STRDEDUP_PERCENT_FORMAT_NS ", Cached: " UINTX_FORMAT ", Added: " UINTX_FORMAT ", Removed: " UINTX_FORMAT, + _table->_entries, percent_of(_table->_entries, _table->_size), _entry_cache->size(), _entries_added, _entries_removed); + log.debug(" Resize Count: " UINTX_FORMAT ", Shrink Threshold: " UINTX_FORMAT "(" STRDEDUP_PERCENT_FORMAT_NS "), Grow Threshold: " UINTX_FORMAT "(" STRDEDUP_PERCENT_FORMAT_NS ")", + _resize_count, _table->_shrink_threshold, _shrink_load_factor * 100.0, _table->_grow_threshold, _grow_load_factor * 100.0); + log.debug(" Rehash Count: " UINTX_FORMAT ", Rehash Threshold: " UINTX_FORMAT ", Hash Seed: 0x%x", _rehash_count, _rehash_threshold, _table->_hash_seed); + log.debug(" Age Threshold: " UINTX_FORMAT, StringDeduplicationAgeThreshold); +} --- old/src/hotspot/share/gc/g1/g1StringDedupTable.hpp 2018-05-28 11:50:32.464069367 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,241 +0,0 @@ -/* - * 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_VM_GC_G1_G1STRINGDEDUPTABLE_HPP -#define SHARE_VM_GC_G1_G1STRINGDEDUPTABLE_HPP - -#include "gc/g1/g1StringDedupStat.hpp" -#include "runtime/mutexLocker.hpp" - -class G1StringDedupEntryCache; -class G1StringDedupUnlinkOrOopsDoClosure; - -// -// Table entry in the deduplication hashtable. Points weakly to the -// character array. Can be chained in a linked list in case of hash -// collisions or when placed in a freelist in the entry cache. -// -class G1StringDedupEntry : public CHeapObj { -private: - G1StringDedupEntry* _next; - unsigned int _hash; - bool _latin1; - typeArrayOop _obj; - -public: - G1StringDedupEntry() : - _next(NULL), - _hash(0), - _latin1(false), - _obj(NULL) { - } - - G1StringDedupEntry* next() { - return _next; - } - - G1StringDedupEntry** next_addr() { - return &_next; - } - - void set_next(G1StringDedupEntry* next) { - _next = next; - } - - unsigned int hash() { - return _hash; - } - - void set_hash(unsigned int hash) { - _hash = hash; - } - - bool latin1() { - return _latin1; - } - - void set_latin1(bool latin1) { - _latin1 = latin1; - } - - typeArrayOop obj() { - return _obj; - } - - typeArrayOop* obj_addr() { - return &_obj; - } - - void set_obj(typeArrayOop obj) { - _obj = obj; - } -}; - -// -// The deduplication hashtable keeps track of all unique character arrays used -// by String objects. Each table entry weakly points to an character array, allowing -// otherwise unreachable character arrays to be declared dead and pruned from the -// table. -// -// The table is dynamically resized to accommodate the current number of table entries. -// The table has hash buckets with chains for hash collision. If the average chain -// length goes above or below given thresholds the table grows or shrinks accordingly. -// -// The table is also dynamically rehashed (using a new hash seed) if it becomes severely -// unbalanced, i.e., a hash chain is significantly longer than average. -// -// All access to the table is protected by the StringDedupTable_lock, except under -// safepoints in which case GC workers are allowed to access a table partitions they -// have claimed without first acquiring the lock. Note however, that this applies only -// the table partition (i.e. a range of elements in _buckets), not other parts of the -// table such as the _entries field, statistics counters, etc. -// -class G1StringDedupTable : public CHeapObj { -private: - // The currently active hashtable instance. Only modified when - // the table is resizes or rehashed. - static G1StringDedupTable* _table; - - // Cache for reuse and fast alloc/free of table entries. - static G1StringDedupEntryCache* _entry_cache; - - G1StringDedupEntry** _buckets; - size_t _size; - uintx _entries; - uintx _shrink_threshold; - uintx _grow_threshold; - bool _rehash_needed; - - // The hash seed also dictates which hash function to use. A - // zero hash seed means we will use the Java compatible hash - // function (which doesn't use a seed), and a non-zero hash - // seed means we use the murmur3 hash function. - jint _hash_seed; - - // Constants governing table resize/rehash/cache. - static const size_t _min_size; - static const size_t _max_size; - static const double _grow_load_factor; - static const double _shrink_load_factor; - static const uintx _rehash_multiple; - static const uintx _rehash_threshold; - static const double _max_cache_factor; - - // Table statistics, only used for logging. - static uintx _entries_added; - static uintx _entries_removed; - static uintx _resize_count; - static uintx _rehash_count; - - G1StringDedupTable(size_t size, jint hash_seed = 0); - ~G1StringDedupTable(); - - // Returns the hash bucket at the given index. - G1StringDedupEntry** bucket(size_t index) { - return _buckets + index; - } - - // Returns the hash bucket index for the given hash code. - size_t hash_to_index(unsigned int hash) { - return (size_t)hash & (_size - 1); - } - - // Adds a new table entry to the given hash bucket. - void add(typeArrayOop value, bool latin1, unsigned int hash, G1StringDedupEntry** list); - - // Removes the given table entry from the table. - void remove(G1StringDedupEntry** pentry, uint worker_id); - - // Transfers a table entry from the current table to the destination table. - void transfer(G1StringDedupEntry** pentry, G1StringDedupTable* dest); - - // Returns an existing character array in the given hash bucket, or NULL - // if no matching character array exists. - typeArrayOop lookup(typeArrayOop value, bool latin1, unsigned int hash, - G1StringDedupEntry** list, uintx &count); - - // Returns an existing character array in the table, or inserts a new - // table entry if no matching character array exists. - typeArrayOop lookup_or_add_inner(typeArrayOop value, bool latin1, unsigned int hash); - - // Thread safe lookup or add of table entry - static typeArrayOop lookup_or_add(typeArrayOop value, bool latin1, unsigned int hash) { - // Protect the table from concurrent access. Also note that this lock - // acts as a fence for _table, which could have been replaced by a new - // instance if the table was resized or rehashed. - MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag); - return _table->lookup_or_add_inner(value, latin1, hash); - } - - // Returns true if the hashtable is currently using a Java compatible - // hash function. - static bool use_java_hash() { - return _table->_hash_seed == 0; - } - - static bool equals(typeArrayOop value1, typeArrayOop value2); - - // Computes the hash code for the given character array, using the - // currently active hash function and hash seed. - static unsigned int hash_code(typeArrayOop value, bool latin1); - - static uintx unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, - size_t partition_begin, - size_t partition_end, - uint worker_id); - -public: - static void create(); - - // Deduplicates the given String object, or adds its backing - // character array to the deduplication hashtable. - static void deduplicate(oop java_string, G1StringDedupStat& stat); - - // If a table resize is needed, returns a newly allocated empty - // hashtable of the proper size. - static G1StringDedupTable* prepare_resize(); - - // Installs a newly resized table as the currently active table - // and deletes the previously active table. - static void finish_resize(G1StringDedupTable* resized_table); - - // If a table rehash is needed, returns a newly allocated empty - // hashtable and updates the hash seed. - static G1StringDedupTable* prepare_rehash(); - - // Transfers rehashed entries from the currently active table into - // the new table. Installs the new table as the currently active table - // and deletes the previously active table. - static void finish_rehash(G1StringDedupTable* rehashed_table); - - // 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); - - static void print_statistics(); - static void verify(); -}; - -#endif // SHARE_VM_GC_G1_G1STRINGDEDUPTABLE_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.hpp 2018-05-28 11:50:32.291069304 -0400 @@ -0,0 +1,255 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTABLE_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTABLE_HPP + +#include "gc/shared/stringdedup/stringDedupStat.hpp" +#include "runtime/mutexLocker.hpp" + +class StringDedupEntryCache; +class StringDedupUnlinkOrOopsDoClosure; + +// +// Table entry in the deduplication hashtable. Points weakly to the +// character array. Can be chained in a linked list in case of hash +// collisions or when placed in a freelist in the entry cache. +// +class StringDedupEntry : public CHeapObj { +private: + StringDedupEntry* _next; + unsigned int _hash; + bool _latin1; + typeArrayOop _obj; + +public: + StringDedupEntry() : + _next(NULL), + _hash(0), + _latin1(false), + _obj(NULL) { + } + + StringDedupEntry* next() { + return _next; + } + + StringDedupEntry** next_addr() { + return &_next; + } + + void set_next(StringDedupEntry* next) { + _next = next; + } + + unsigned int hash() { + return _hash; + } + + void set_hash(unsigned int hash) { + _hash = hash; + } + + bool latin1() { + return _latin1; + } + + void set_latin1(bool latin1) { + _latin1 = latin1; + } + + typeArrayOop obj() { + return _obj; + } + + typeArrayOop* obj_addr() { + return &_obj; + } + + void set_obj(typeArrayOop obj) { + _obj = obj; + } +}; + +// +// The deduplication hashtable keeps track of all unique character arrays used +// by String objects. Each table entry weakly points to an character array, allowing +// otherwise unreachable character arrays to be declared dead and pruned from the +// table. +// +// The table is dynamically resized to accommodate the current number of table entries. +// The table has hash buckets with chains for hash collision. If the average chain +// length goes above or below given thresholds the table grows or shrinks accordingly. +// +// The table is also dynamically rehashed (using a new hash seed) if it becomes severely +// unbalanced, i.e., a hash chain is significantly longer than average. +// +// All access to the table is protected by the StringDedupTable_lock, except under +// safepoints in which case GC workers are allowed to access a table partitions they +// have claimed without first acquiring the lock. Note however, that this applies only +// the table partition (i.e. a range of elements in _buckets), not other parts of the +// table such as the _entries field, statistics counters, etc. +// +class StringDedupTable : public CHeapObj { +private: + // The currently active hashtable instance. Only modified when + // the table is resizes or rehashed. + static StringDedupTable* _table; + + // Cache for reuse and fast alloc/free of table entries. + static StringDedupEntryCache* _entry_cache; + + StringDedupEntry** _buckets; + size_t _size; + uintx _entries; + uintx _shrink_threshold; + uintx _grow_threshold; + bool _rehash_needed; + + // The hash seed also dictates which hash function to use. A + // zero hash seed means we will use the Java compatible hash + // function (which doesn't use a seed), and a non-zero hash + // seed means we use the murmur3 hash function. + jint _hash_seed; + + // Constants governing table resize/rehash/cache. + static const size_t _min_size; + static const size_t _max_size; + static const double _grow_load_factor; + static const double _shrink_load_factor; + static const uintx _rehash_multiple; + static const uintx _rehash_threshold; + static const double _max_cache_factor; + + // Table statistics, only used for logging. + static uintx _entries_added; + static uintx _entries_removed; + static uintx _resize_count; + static uintx _rehash_count; + + static volatile size_t _claimed_index; + + static StringDedupTable* _resized_table; + static StringDedupTable* _rehashed_table; + + StringDedupTable(size_t size, jint hash_seed = 0); + ~StringDedupTable(); + + // Returns the hash bucket at the given index. + StringDedupEntry** bucket(size_t index) { + return _buckets + index; + } + + // Returns the hash bucket index for the given hash code. + size_t hash_to_index(unsigned int hash) { + return (size_t)hash & (_size - 1); + } + + // Adds a new table entry to the given hash bucket. + void add(typeArrayOop value, bool latin1, unsigned int hash, StringDedupEntry** list); + + // Removes the given table entry from the table. + void remove(StringDedupEntry** pentry, uint worker_id); + + // Transfers a table entry from the current table to the destination table. + void transfer(StringDedupEntry** pentry, StringDedupTable* dest); + + // Returns an existing character array in the given hash bucket, or NULL + // if no matching character array exists. + typeArrayOop lookup(typeArrayOop value, bool latin1, unsigned int hash, + StringDedupEntry** list, uintx &count); + + // Returns an existing character array in the table, or inserts a new + // table entry if no matching character array exists. + typeArrayOop lookup_or_add_inner(typeArrayOop value, bool latin1, unsigned int hash); + + // Thread safe lookup or add of table entry + static typeArrayOop lookup_or_add(typeArrayOop value, bool latin1, unsigned int hash) { + // Protect the table from concurrent access. Also note that this lock + // acts as a fence for _table, which could have been replaced by a new + // instance if the table was resized or rehashed. + MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag); + return _table->lookup_or_add_inner(value, latin1, hash); + } + + // Returns true if the hashtable is currently using a Java compatible + // hash function. + static bool use_java_hash() { + return _table->_hash_seed == 0; + } + + static bool equals(typeArrayOop value1, typeArrayOop value2); + + // Computes the hash code for the given character array, using the + // currently active hash function and hash seed. + static unsigned int hash_code(typeArrayOop value, bool latin1); + + static uintx unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, + size_t partition_begin, + size_t partition_end, + uint worker_id); + + static size_t claim_table_partition(size_t partition_size); + + static bool is_resizing(); + static bool is_rehashing(); + + // If a table resize is needed, returns a newly allocated empty + // hashtable of the proper size. + static StringDedupTable* prepare_resize(); + + // Installs a newly resized table as the currently active table + // and deletes the previously active table. + static void finish_resize(StringDedupTable* resized_table); + + // If a table rehash is needed, returns a newly allocated empty + // hashtable and updates the hash seed. + static StringDedupTable* prepare_rehash(); + + // Transfers rehashed entries from the currently active table into + // the new table. Installs the new table as the currently active table + // and deletes the previously active table. + static void finish_rehash(StringDedupTable* rehashed_table); + +public: + static void create(); + + // Deduplicates the given String object, or adds its backing + // character array to the deduplication hashtable. + static void deduplicate(oop java_string, StringDedupStat* stat); + + static void unlink_or_oops_do(StringDedupUnlinkOrOopsDoClosure* cl, uint worker_id); + + static void print_statistics(); + static void verify(); + + // If the table entry cache has grown too large, delete overflowed entries. + static void clean_entry_cache(); + + // GC support + static void gc_prologue(bool resize_and_rehash_table); + static void gc_epilogue(); +}; + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTABLE_HPP --- old/src/hotspot/share/gc/g1/g1StringDedupThread.cpp 2018-05-28 11:50:32.886069520 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2014, 2017, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "precompiled.hpp" -#include "classfile/stringTable.hpp" -#include "gc/g1/g1StringDedup.hpp" -#include "gc/g1/g1StringDedupQueue.hpp" -#include "gc/g1/g1StringDedupTable.hpp" -#include "gc/g1/g1StringDedupThread.hpp" -#include "gc/shared/suspendibleThreadSet.hpp" -#include "logging/log.hpp" -#include "oops/access.inline.hpp" -#include "oops/oop.inline.hpp" -#include "runtime/atomic.hpp" - -G1StringDedupThread* G1StringDedupThread::_thread = NULL; - -G1StringDedupThread::G1StringDedupThread() : - ConcurrentGCThread() { - set_name("G1 StrDedup"); - create_and_start(); -} - -G1StringDedupThread::~G1StringDedupThread() { - ShouldNotReachHere(); -} - -void G1StringDedupThread::create() { - assert(G1StringDedup::is_enabled(), "String deduplication not enabled"); - assert(_thread == NULL, "One string deduplication thread allowed"); - _thread = new G1StringDedupThread(); -} - -G1StringDedupThread* G1StringDedupThread::thread() { - assert(G1StringDedup::is_enabled(), "String deduplication not enabled"); - assert(_thread != NULL, "String deduplication thread not created"); - return _thread; -} - -class G1StringDedupSharedClosure: public OopClosure { - private: - G1StringDedupStat& _stat; - - public: - G1StringDedupSharedClosure(G1StringDedupStat& stat) : _stat(stat) {} - - virtual void do_oop(oop* p) { ShouldNotReachHere(); } - virtual void do_oop(narrowOop* p) { - oop java_string = RawAccess<>::oop_load(p); - G1StringDedupTable::deduplicate(java_string, _stat); - } -}; - -// The CDS archive does not include the string dedupication table. Only the string -// table is saved in the archive. The shared strings from CDS archive need to be -// added to the string dedupication table before deduplication occurs. That is -// done in the begining of the G1StringDedupThread (see G1StringDedupThread::run() -// below). -void G1StringDedupThread::deduplicate_shared_strings(G1StringDedupStat& stat) { - G1StringDedupSharedClosure sharedStringDedup(stat); - StringTable::shared_oops_do(&sharedStringDedup); -} - -void G1StringDedupThread::run_service() { - G1StringDedupStat total_stat; - - deduplicate_shared_strings(total_stat); - - // Main loop - for (;;) { - G1StringDedupStat stat; - - stat.mark_idle(); - - // Wait for the queue to become non-empty - G1StringDedupQueue::wait(); - if (should_terminate()) { - break; - } - - { - // Include thread in safepoints - SuspendibleThreadSetJoiner sts_join; - - stat.mark_exec(); - print_start(stat); - - // Process the queue - for (;;) { - oop java_string = G1StringDedupQueue::pop(); - if (java_string == NULL) { - break; - } - - G1StringDedupTable::deduplicate(java_string, stat); - - // Safepoint this thread if needed - if (sts_join.should_yield()) { - stat.mark_block(); - sts_join.yield(); - stat.mark_unblock(); - } - } - - stat.mark_done(); - - total_stat.add(stat); - print_end(stat, total_stat); - } - - G1StringDedupTable::clean_entry_cache(); - } -} - -void G1StringDedupThread::stop_service() { - G1StringDedupQueue::cancel_wait(); -} - -void G1StringDedupThread::print_start(const G1StringDedupStat& last_stat) { - G1StringDedupStat::print_start(last_stat); -} - -void G1StringDedupThread::print_end(const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat) { - G1StringDedupStat::print_end(last_stat, total_stat); - if (log_is_enabled(Debug, gc, stringdedup)) { - G1StringDedupStat::print_statistics(last_stat, false); - G1StringDedupStat::print_statistics(total_stat, true); - G1StringDedupTable::print_statistics(); - G1StringDedupQueue::print_statistics(); - } -} --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupThread.cpp 2018-05-28 11:50:32.712069456 -0400 @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014, 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "classfile/stringTable.hpp" +#include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.inline.hpp" +#include "gc/shared/stringdedup/stringDedupTable.hpp" +#include "gc/shared/stringdedup/stringDedupThread.hpp" +#include "gc/shared/suspendibleThreadSet.hpp" +#include "logging/log.hpp" +#include "oops/access.inline.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" + +StringDedupThread* StringDedupThread::_thread = NULL; + +StringDedupThread::StringDedupThread() : + ConcurrentGCThread() { + set_name("StrDedup"); + create_and_start(); +} + +StringDedupThread::~StringDedupThread() { + ShouldNotReachHere(); +} + +StringDedupThread* StringDedupThread::thread() { + assert(_thread != NULL, "String deduplication thread not created"); + return _thread; +} + +class StringDedupSharedClosure: public OopClosure { + private: + StringDedupStat* _stat; + + public: + StringDedupSharedClosure(StringDedupStat* stat) : _stat(stat) {} + + virtual void do_oop(oop* p) { ShouldNotReachHere(); } + virtual void do_oop(narrowOop* p) { + oop java_string = RawAccess<>::oop_load(p); + StringDedupTable::deduplicate(java_string, _stat); + } +}; + +// The CDS archive does not include the string dedupication table. Only the string +// table is saved in the archive. The shared strings from CDS archive need to be +// added to the string dedupication table before deduplication occurs. That is +// done in the begining of the StringDedupThread (see StringDedupThread::do_deduplication()). +void StringDedupThread::deduplicate_shared_strings(StringDedupStat* stat) { + StringDedupSharedClosure sharedStringDedup(stat); + StringTable::shared_oops_do(&sharedStringDedup); +} + +void StringDedupThread::stop_service() { + StringDedupQueue::cancel_wait(); +} + +void StringDedupThread::print_start(const StringDedupStat* last_stat) { + StringDedupStat::print_start(last_stat); +} + +void StringDedupThread::print_end(const StringDedupStat* last_stat, const StringDedupStat* total_stat) { + StringDedupStat::print_end(last_stat, total_stat); + if (log_is_enabled(Debug, gc, stringdedup)) { + last_stat->print_statistics(false); + total_stat->print_statistics(true); + + StringDedupTable::print_statistics(); + StringDedupQueue::print_statistics(); + } +} --- old/src/hotspot/share/gc/g1/g1StringDedupThread.hpp 2018-05-28 11:50:33.293069667 -0400 +++ /dev/null 2018-05-23 08:05:51.220661352 -0400 @@ -1,60 +0,0 @@ -/* - * 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_VM_GC_G1_G1STRINGDEDUPTHREAD_HPP -#define SHARE_VM_GC_G1_G1STRINGDEDUPTHREAD_HPP - -#include "gc/g1/g1StringDedupStat.hpp" -#include "gc/shared/concurrentGCThread.hpp" - -// -// The deduplication thread is where the actual deduplication occurs. It waits for -// deduplication candidates to appear on the deduplication queue, removes them from -// the queue and tries to deduplicate them. It uses the deduplication hashtable to -// find identical, already existing, character arrays on the heap. The thread runs -// concurrently with the Java application but participates in safepoints to allow -// the GC to adjust and unlink oops from the deduplication queue and table. -// -class G1StringDedupThread: public ConcurrentGCThread { -private: - static G1StringDedupThread* _thread; - - G1StringDedupThread(); - ~G1StringDedupThread(); - - void print_start(const G1StringDedupStat& last_stat); - void print_end(const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat); - - void run_service(); - void stop_service(); - -public: - static void create(); - - static G1StringDedupThread* thread(); - - void deduplicate_shared_strings(G1StringDedupStat& stat); -}; - -#endif // SHARE_VM_GC_G1_G1STRINGDEDUPTHREAD_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupThread.hpp 2018-05-28 11:50:33.131069608 -0400 @@ -0,0 +1,72 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP + +#include "gc/shared/concurrentGCThread.hpp" +#include "gc/shared/stringdedup/stringDedupStat.hpp" + +// +// The deduplication thread is where the actual deduplication occurs. It waits for +// deduplication candidates to appear on the deduplication queue, removes them from +// the queue and tries to deduplicate them. It uses the deduplication hashtable to +// find identical, already existing, character arrays on the heap. The thread runs +// concurrently with the Java application but participates in safepoints to allow +// the GC to adjust and unlink oops from the deduplication queue and table. +// +class StringDedupThread: public ConcurrentGCThread { +protected: + static StringDedupThread* _thread; + + StringDedupThread(); + ~StringDedupThread(); + + void print_start(const StringDedupStat* last_stat); + void print_end(const StringDedupStat* last_stat, const StringDedupStat* total_stat); + + void run_service() { this->do_deduplication(); } + void stop_service(); + + void deduplicate_shared_strings(StringDedupStat* stat); +protected: + virtual void do_deduplication() = 0; + +public: + static StringDedupThread* thread(); +}; + +template +class StringDedupThreadImpl : public StringDedupThread { +private: + StringDedupThreadImpl() { } + +protected: + void do_deduplication(); + +public: + static void create(); +}; + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP --- /dev/null 2018-05-23 08:05:51.220661352 -0400 +++ new/src/hotspot/share/gc/shared/stringdedup/stringDedupThread.inline.hpp 2018-05-28 11:50:33.528069752 -0400 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_INLINE_HPP +#define SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_INLINE_HPP + +#include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/shared/stringdedup/stringDedupQueue.inline.hpp" +#include "gc/shared/stringdedup/stringDedupThread.hpp" + +template +void StringDedupThreadImpl::do_deduplication() { + STAT total_stat; + + deduplicate_shared_strings(&total_stat); + + // Main loop + for (;;) { + STAT stat; + + stat.mark_idle(); + + // Wait for the queue to become non-empty + StringDedupQueue::wait(); + if (this->should_terminate()) { + break; + } + + { + // Include thread in safepoints + SuspendibleThreadSetJoiner sts_join; + + stat.mark_exec(); + StringDedupStat::print_start(&stat); + + // Process the queue + for (;;) { + oop java_string = StringDedupQueue::pop(); + if (java_string == NULL) { + break; + } + + StringDedupTable::deduplicate(java_string, &stat); + + // Safepoint this thread if needed + if (sts_join.should_yield()) { + stat.mark_block(); + sts_join.yield(); + stat.mark_unblock(); + } + } + + stat.mark_done(); + + total_stat.add(&stat); + print_end(&stat, &total_stat); + stat.reset(); + } + + StringDedupTable::clean_entry_cache(); + } +} + +template +void StringDedupThreadImpl::create() { + assert(_thread == NULL, "One string deduplication thread allowed"); + _thread = new StringDedupThreadImpl(); +} + +#endif // SHARE_VM_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_INLINE_HPP