8029075: String deduplication in G1
Implementation of JEP 192, http://openjdk.java.net/jeps/192 Reviewed-by: brutisso, tschatzl, coleenp
This commit is contained in:
parent
1e1ad7f132
commit
4a4c0fce93
@ -87,7 +87,8 @@ ifeq ($(INCLUDE_ALL_GCS), false)
|
||||
g1BlockOffsetTable.cpp g1CardCounts.cpp g1CollectedHeap.cpp g1CollectorPolicy.cpp \
|
||||
g1ErgoVerbose.cpp g1GCPhaseTimes.cpp g1HRPrinter.cpp g1HotCardCache.cpp g1Log.cpp \
|
||||
g1MMUTracker.cpp g1MarkSweep.cpp g1MemoryPool.cpp g1MonitoringSupport.cpp g1OopClosures.cpp \
|
||||
g1RemSet.cpp g1RemSetSummary.cpp g1SATBCardTableModRefBS.cpp g1_globals.cpp heapRegion.cpp \
|
||||
g1RemSet.cpp g1RemSetSummary.cpp g1SATBCardTableModRefBS.cpp g1StringDedup.cpp g1StringDedupStat.cpp \
|
||||
g1StringDedupTable.cpp g1StringDedupThread.cpp g1StringDedupQueue.cpp g1_globals.cpp heapRegion.cpp \
|
||||
g1BiasedArray.cpp heapRegionRemSet.cpp heapRegionSeq.cpp heapRegionSet.cpp heapRegionSets.cpp \
|
||||
ptrQueue.cpp satbQueue.cpp sparsePRT.cpp survRateGroup.cpp vm_operations_g1.cpp g1CodeCacheRemSet.cpp \
|
||||
adjoiningGenerations.cpp adjoiningVirtualSpaces.cpp asPSOldGen.cpp asPSYoungGen.cpp \
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2014, 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
|
||||
@ -61,10 +61,6 @@ class java_lang_String : AllStatic {
|
||||
|
||||
static Handle basic_create(int length, TRAPS);
|
||||
|
||||
static void set_value( oop string, typeArrayOop buffer) {
|
||||
assert(initialized, "Must be initialized");
|
||||
string->obj_field_put(value_offset, (oop)buffer);
|
||||
}
|
||||
static void set_offset(oop string, int offset) {
|
||||
assert(initialized, "Must be initialized");
|
||||
if (offset_offset > 0) {
|
||||
@ -122,12 +118,26 @@ class java_lang_String : AllStatic {
|
||||
return hash_offset;
|
||||
}
|
||||
|
||||
static void set_value(oop string, typeArrayOop buffer) {
|
||||
assert(initialized && (value_offset > 0), "Must be initialized");
|
||||
string->obj_field_put(value_offset, (oop)buffer);
|
||||
}
|
||||
static void set_hash(oop string, unsigned int hash) {
|
||||
assert(initialized && (hash_offset > 0), "Must be initialized");
|
||||
string->int_field_put(hash_offset, hash);
|
||||
}
|
||||
|
||||
// Accessors
|
||||
static typeArrayOop value(oop java_string) {
|
||||
assert(initialized && (value_offset > 0), "Must be initialized");
|
||||
assert(is_instance(java_string), "must be java_string");
|
||||
return (typeArrayOop) java_string->obj_field(value_offset);
|
||||
}
|
||||
static unsigned int hash(oop java_string) {
|
||||
assert(initialized && (hash_offset > 0), "Must be initialized");
|
||||
assert(is_instance(java_string), "must be java_string");
|
||||
return java_string->int_field(hash_offset);
|
||||
}
|
||||
static int offset(oop java_string) {
|
||||
assert(initialized, "Must be initialized");
|
||||
assert(is_instance(java_string), "must be java_string");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2014, 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
|
||||
@ -35,6 +35,9 @@
|
||||
#include "oops/oop.inline2.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "utilities/hashtable.inline.hpp"
|
||||
#if INCLUDE_ALL_GCS
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@ -728,6 +731,15 @@ oop StringTable::intern(Handle string_or_null, jchar* name,
|
||||
string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
|
||||
}
|
||||
|
||||
#if INCLUDE_ALL_GCS
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
// Deduplicate the string before it is interned. Note that we should never
|
||||
// deduplicate a string after it has been interned. Doing so will counteract
|
||||
// compiler optimizations done on e.g. interned string literals.
|
||||
G1StringDedup::deduplicate(string());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Grab the StringTable_lock before getting the_table() because it could
|
||||
// change at safepoint.
|
||||
MutexLocker ml(StringTable_lock, THREAD);
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "gc_implementation/g1/g1MarkSweep.hpp"
|
||||
#include "gc_implementation/g1/g1OopClosures.inline.hpp"
|
||||
#include "gc_implementation/g1/g1RemSet.inline.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#include "gc_implementation/g1/g1YCTypes.hpp"
|
||||
#include "gc_implementation/g1/heapRegion.inline.hpp"
|
||||
#include "gc_implementation/g1/heapRegionRemSet.hpp"
|
||||
@ -2172,6 +2173,8 @@ jint G1CollectedHeap::initialize() {
|
||||
// values in the heap have been properly initialized.
|
||||
_g1mm = new G1MonitoringSupport(this);
|
||||
|
||||
G1StringDedup::initialize();
|
||||
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
@ -3456,6 +3459,11 @@ void G1CollectedHeap::verify(bool silent, VerifyOption vo) {
|
||||
if (!silent) gclog_or_tty->print("RemSet ");
|
||||
rem_set()->verify();
|
||||
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
if (!silent) gclog_or_tty->print("StrDedup ");
|
||||
G1StringDedup::verify();
|
||||
}
|
||||
|
||||
if (failures) {
|
||||
gclog_or_tty->print_cr("Heap:");
|
||||
// It helps to have the per-region information in the output to
|
||||
@ -3473,8 +3481,13 @@ void G1CollectedHeap::verify(bool silent, VerifyOption vo) {
|
||||
}
|
||||
guarantee(!failures, "there should not have been any failures");
|
||||
} else {
|
||||
if (!silent)
|
||||
gclog_or_tty->print("(SKIPPING roots, heapRegionSets, heapRegions, remset) ");
|
||||
if (!silent) {
|
||||
gclog_or_tty->print("(SKIPPING Roots, HeapRegionSets, HeapRegions, RemSet");
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
gclog_or_tty->print(", StrDedup");
|
||||
}
|
||||
gclog_or_tty->print(") ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3567,6 +3580,9 @@ void G1CollectedHeap::print_gc_threads_on(outputStream* st) const {
|
||||
st->cr();
|
||||
_cm->print_worker_threads_on(st);
|
||||
_cg1r->print_worker_threads_on(st);
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::print_worker_threads_on(st);
|
||||
}
|
||||
}
|
||||
|
||||
void G1CollectedHeap::gc_threads_do(ThreadClosure* tc) const {
|
||||
@ -3575,6 +3591,9 @@ void G1CollectedHeap::gc_threads_do(ThreadClosure* tc) const {
|
||||
}
|
||||
tc->do_thread(_cmThread);
|
||||
_cg1r->threads_do(tc);
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::threads_do(tc);
|
||||
}
|
||||
}
|
||||
|
||||
void G1CollectedHeap::print_tracing_info() const {
|
||||
@ -4755,6 +4774,13 @@ oop G1ParScanThreadState::copy_to_survivor_space(oop const old) {
|
||||
obj->set_mark(m);
|
||||
}
|
||||
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::enqueue_from_evacuation(from_region->is_young(),
|
||||
to_region->is_young(),
|
||||
queue_num(),
|
||||
obj);
|
||||
}
|
||||
|
||||
size_t* surv_young_words = surviving_young_words();
|
||||
surv_young_words[young_index] += word_sz;
|
||||
|
||||
@ -5218,6 +5244,10 @@ void G1CollectedHeap::unlink_string_and_symbol_table(BoolObjectClosure* is_alive
|
||||
g1_unlink_task.strings_processed(), g1_unlink_task.strings_removed(),
|
||||
g1_unlink_task.symbols_processed(), g1_unlink_task.symbols_removed());
|
||||
}
|
||||
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::unlink(is_alive);
|
||||
}
|
||||
}
|
||||
|
||||
class RedirtyLoggedCardTableEntryFastClosure : public CardTableEntryClosure {
|
||||
@ -5841,6 +5871,9 @@ void G1CollectedHeap::evacuate_collection_set(EvacuationInfo& evacuation_info) {
|
||||
G1STWIsAliveClosure is_alive(this);
|
||||
G1KeepAliveClosure keep_alive(this);
|
||||
JNIHandles::weak_oops_do(&is_alive, &keep_alive);
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::unlink_or_oops_do(&is_alive, &keep_alive);
|
||||
}
|
||||
}
|
||||
|
||||
release_gc_alloc_regions(n_workers, evacuation_info);
|
||||
@ -6321,9 +6354,10 @@ void G1CollectedHeap::tear_down_region_sets(bool free_list_only) {
|
||||
TearDownRegionSetsClosure cl(&_old_set);
|
||||
heap_region_iterate(&cl);
|
||||
|
||||
// Need to do this after the heap iteration to be able to
|
||||
// recognize the young regions and ignore them during the iteration.
|
||||
_young_list->empty_list();
|
||||
// Note that emptying the _young_list is postponed and instead done as
|
||||
// the first step when rebuilding the regions sets again. The reason for
|
||||
// this is that during a full GC string deduplication needs to know if
|
||||
// a collected region was young or old when the full GC was initiated.
|
||||
}
|
||||
_free_list.remove_all();
|
||||
}
|
||||
@ -6377,6 +6411,10 @@ public:
|
||||
void G1CollectedHeap::rebuild_region_sets(bool free_list_only) {
|
||||
assert_at_safepoint(true /* should_be_vm_thread */);
|
||||
|
||||
if (!free_list_only) {
|
||||
_young_list->empty_list();
|
||||
}
|
||||
|
||||
RebuildRegionSetsClosure cl(free_list_only, &_old_set, &_free_list);
|
||||
heap_region_iterate(&cl);
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "gc_implementation/g1/g1CollectedHeap.inline.hpp"
|
||||
#include "gc_implementation/g1/g1GCPhaseTimes.hpp"
|
||||
#include "gc_implementation/g1/g1Log.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
|
||||
// Helper class for avoiding interleaved logging
|
||||
class LineBuffer: public StackObj {
|
||||
@ -168,7 +169,9 @@ G1GCPhaseTimes::G1GCPhaseTimes(uint max_gc_threads) :
|
||||
_last_termination_attempts(_max_gc_threads, SIZE_FORMAT),
|
||||
_last_gc_worker_end_times_ms(_max_gc_threads, "%.1lf", false),
|
||||
_last_gc_worker_times_ms(_max_gc_threads, "%.1lf"),
|
||||
_last_gc_worker_other_times_ms(_max_gc_threads, "%.1lf")
|
||||
_last_gc_worker_other_times_ms(_max_gc_threads, "%.1lf"),
|
||||
_cur_string_dedup_queue_fixup_worker_times_ms(_max_gc_threads, "%.1lf"),
|
||||
_cur_string_dedup_table_fixup_worker_times_ms(_max_gc_threads, "%.1lf")
|
||||
{
|
||||
assert(max_gc_threads > 0, "Must have some GC threads");
|
||||
}
|
||||
@ -229,6 +232,16 @@ void G1GCPhaseTimes::note_gc_end() {
|
||||
_last_gc_worker_other_times_ms.verify();
|
||||
}
|
||||
|
||||
void G1GCPhaseTimes::note_string_dedup_fixup_start() {
|
||||
_cur_string_dedup_queue_fixup_worker_times_ms.reset();
|
||||
_cur_string_dedup_table_fixup_worker_times_ms.reset();
|
||||
}
|
||||
|
||||
void G1GCPhaseTimes::note_string_dedup_fixup_end() {
|
||||
_cur_string_dedup_queue_fixup_worker_times_ms.verify();
|
||||
_cur_string_dedup_table_fixup_worker_times_ms.verify();
|
||||
}
|
||||
|
||||
void G1GCPhaseTimes::print_stats(int level, const char* str, double value) {
|
||||
LineBuffer(level).append_and_print_cr("[%s: %.1lf ms]", str, value);
|
||||
}
|
||||
@ -253,6 +266,11 @@ double G1GCPhaseTimes::accounted_time_ms() {
|
||||
// Strong code root purge time
|
||||
misc_time_ms += _cur_strong_code_root_purge_time_ms;
|
||||
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
// String dedup fixup time
|
||||
misc_time_ms += _cur_string_dedup_fixup_time_ms;
|
||||
}
|
||||
|
||||
// Subtract the time taken to clean the card table from the
|
||||
// current value of "other time"
|
||||
misc_time_ms += _cur_clear_ct_time_ms;
|
||||
@ -303,6 +321,11 @@ void G1GCPhaseTimes::print(double pause_time_sec) {
|
||||
print_stats(1, "Code Root Fixup", _cur_collection_code_root_fixup_time_ms);
|
||||
print_stats(1, "Code Root Migration", _cur_strong_code_root_migration_time_ms);
|
||||
print_stats(1, "Code Root Purge", _cur_strong_code_root_purge_time_ms);
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
print_stats(1, "String Dedup Fixup", _cur_string_dedup_fixup_time_ms, _active_gc_threads);
|
||||
_cur_string_dedup_queue_fixup_worker_times_ms.print(2, "Queue Fixup (ms)");
|
||||
_cur_string_dedup_table_fixup_worker_times_ms.print(2, "Table Fixup (ms)");
|
||||
}
|
||||
print_stats(1, "Clear CT", _cur_clear_ct_time_ms);
|
||||
double misc_time_ms = pause_time_sec * MILLIUNITS - accounted_time_ms();
|
||||
print_stats(1, "Other", misc_time_ms);
|
||||
|
@ -137,6 +137,10 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
|
||||
double _cur_evac_fail_restore_remsets;
|
||||
double _cur_evac_fail_remove_self_forwards;
|
||||
|
||||
double _cur_string_dedup_fixup_time_ms;
|
||||
WorkerDataArray<double> _cur_string_dedup_queue_fixup_worker_times_ms;
|
||||
WorkerDataArray<double> _cur_string_dedup_table_fixup_worker_times_ms;
|
||||
|
||||
double _cur_clear_ct_time_ms;
|
||||
double _cur_ref_proc_time_ms;
|
||||
double _cur_ref_enq_time_ms;
|
||||
@ -246,6 +250,21 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
|
||||
_cur_evac_fail_remove_self_forwards = ms;
|
||||
}
|
||||
|
||||
void note_string_dedup_fixup_start();
|
||||
void note_string_dedup_fixup_end();
|
||||
|
||||
void record_string_dedup_fixup_time(double ms) {
|
||||
_cur_string_dedup_fixup_time_ms = ms;
|
||||
}
|
||||
|
||||
void record_string_dedup_queue_fixup_worker_time(uint worker_id, double ms) {
|
||||
_cur_string_dedup_queue_fixup_worker_times_ms.set(worker_id, ms);
|
||||
}
|
||||
|
||||
void record_string_dedup_table_fixup_worker_time(uint worker_id, double ms) {
|
||||
_cur_string_dedup_table_fixup_worker_times_ms.set(worker_id, ms);
|
||||
}
|
||||
|
||||
void record_ref_proc_time(double ms) {
|
||||
_cur_ref_proc_time_ms = ms;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2014, 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
|
||||
@ -31,6 +31,7 @@
|
||||
#include "code/icBuffer.hpp"
|
||||
#include "gc_implementation/g1/g1Log.hpp"
|
||||
#include "gc_implementation/g1/g1MarkSweep.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#include "gc_implementation/shared/gcHeapSummary.hpp"
|
||||
#include "gc_implementation/shared/gcTimer.hpp"
|
||||
#include "gc_implementation/shared/gcTrace.hpp"
|
||||
@ -316,6 +317,10 @@ void G1MarkSweep::mark_sweep_phase3() {
|
||||
// have been cleared if they pointed to non-surviving objects.)
|
||||
sh->process_weak_roots(&GenMarkSweep::adjust_pointer_closure);
|
||||
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
G1StringDedup::oops_do(&GenMarkSweep::adjust_pointer_closure);
|
||||
}
|
||||
|
||||
GenMarkSweep::adjust_marks();
|
||||
|
||||
G1AdjustPointersClosure blk;
|
||||
|
208
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.cpp
Normal file
208
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.hpp"
|
||||
#include "gc_implementation/g1/g1CollectedHeap.inline.hpp"
|
||||
#include "gc_implementation/g1/g1GCPhaseTimes.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupQueue.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupStat.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupTable.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupThread.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();
|
||||
}
|
||||
}
|
||||
|
||||
bool G1StringDedup::is_candidate_from_mark(oop obj) {
|
||||
if (java_lang_String::is_instance(obj)) {
|
||||
bool from_young = G1CollectedHeap::heap()->heap_region_containing_raw(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) {
|
||||
assert(is_enabled(), "String deduplication not enabled");
|
||||
if (is_candidate_from_mark(java_string)) {
|
||||
G1StringDedupQueue::push(0 /* 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(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);
|
||||
}
|
||||
|
||||
void G1StringDedup::unlink(BoolObjectClosure* is_alive) {
|
||||
assert(is_enabled(), "String deduplication not enabled");
|
||||
// Don't allow a potential resize or rehash during unlink, as the unlink
|
||||
// operation itself might remove enough entries to invalidate such a decision.
|
||||
unlink_or_oops_do(is_alive, NULL, false /* allow_resize_and_rehash */);
|
||||
}
|
||||
|
||||
//
|
||||
// Task for parallel unlink_or_oops_do() operation on the deduplication queue
|
||||
// and table.
|
||||
//
|
||||
class G1StringDedupUnlinkOrOopsDoTask : public AbstractGangTask {
|
||||
private:
|
||||
G1StringDedupUnlinkOrOopsDoClosure _cl;
|
||||
|
||||
public:
|
||||
G1StringDedupUnlinkOrOopsDoTask(BoolObjectClosure* is_alive,
|
||||
OopClosure* keep_alive,
|
||||
bool allow_resize_and_rehash) :
|
||||
AbstractGangTask("G1StringDedupUnlinkOrOopsDoTask"),
|
||||
_cl(is_alive, keep_alive, allow_resize_and_rehash) {
|
||||
}
|
||||
|
||||
virtual void work(uint worker_id) {
|
||||
double queue_fixup_start = os::elapsedTime();
|
||||
G1StringDedupQueue::unlink_or_oops_do(&_cl);
|
||||
|
||||
double table_fixup_start = os::elapsedTime();
|
||||
G1StringDedupTable::unlink_or_oops_do(&_cl, worker_id);
|
||||
|
||||
double queue_fixup_time_ms = (table_fixup_start - queue_fixup_start) * 1000.0;
|
||||
double table_fixup_time_ms = (os::elapsedTime() - table_fixup_start) * 1000.0;
|
||||
G1CollectorPolicy* g1p = G1CollectedHeap::heap()->g1_policy();
|
||||
g1p->phase_times()->record_string_dedup_queue_fixup_worker_time(worker_id, queue_fixup_time_ms);
|
||||
g1p->phase_times()->record_string_dedup_table_fixup_worker_time(worker_id, table_fixup_time_ms);
|
||||
}
|
||||
};
|
||||
|
||||
void G1StringDedup::unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive, bool allow_resize_and_rehash) {
|
||||
assert(is_enabled(), "String deduplication not enabled");
|
||||
G1CollectorPolicy* g1p = G1CollectedHeap::heap()->g1_policy();
|
||||
g1p->phase_times()->note_string_dedup_fixup_start();
|
||||
double fixup_start = os::elapsedTime();
|
||||
|
||||
G1StringDedupUnlinkOrOopsDoTask task(is_alive, keep_alive, allow_resize_and_rehash);
|
||||
if (G1CollectedHeap::use_parallel_gc_threads()) {
|
||||
G1CollectedHeap* g1h = G1CollectedHeap::heap();
|
||||
g1h->set_par_threads();
|
||||
g1h->workers()->run_task(&task);
|
||||
g1h->set_par_threads(0);
|
||||
} else {
|
||||
task.work(0);
|
||||
}
|
||||
|
||||
double fixup_time_ms = (os::elapsedTime() - fixup_start) * 1000.0;
|
||||
g1p->phase_times()->record_string_dedup_fixup_time(fixup_time_ms);
|
||||
g1p->phase_times()->note_string_dedup_fixup_end();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
202
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.hpp
Normal file
202
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.hpp
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_IMPLEMENTATION_G1_G1STRINGDEDUP_HPP
|
||||
#define SHARE_VM_GC_IMPLEMENTATION_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;
|
||||
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void initialize();
|
||||
|
||||
// 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);
|
||||
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 unlink(BoolObjectClosure* is_alive);
|
||||
static void unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive,
|
||||
bool allow_resize_and_rehash = true);
|
||||
|
||||
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() {
|
||||
return (size_t)Atomic::add_ptr(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 claim_table_partition(size_t partition_size) {
|
||||
return (size_t)Atomic::add_ptr(partition_size, &_next_bucket) - 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_IMPLEMENTATION_G1_G1STRINGDEDUP_HPP
|
162
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.cpp
Normal file
162
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupQueue.hpp"
|
||||
#include "memory/gcLocker.hpp"
|
||||
#include "runtime/mutexLocker.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
|
||||
|
||||
G1StringDedupQueue::G1StringDedupQueue() :
|
||||
_cursor(0),
|
||||
_empty(true),
|
||||
_dropped(0) {
|
||||
_nqueues = MAX2(ParallelGCThreads, (size_t)1);
|
||||
_queues = NEW_C_HEAP_ARRAY(G1StringDedupWorkerQueue, _nqueues, mtGC);
|
||||
for (size_t i = 0; i < _nqueues; i++) {
|
||||
new (_queues + i) G1StringDedupWorkerQueue(G1StringDedupWorkerQueue::default_segment_size(), _max_cache_size, _max_size);
|
||||
}
|
||||
}
|
||||
|
||||
G1StringDedupQueue::~G1StringDedupQueue() {
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
void G1StringDedupQueue::create() {
|
||||
assert(_queue == NULL, "One string deduplication queue allowed");
|
||||
_queue = new G1StringDedupQueue();
|
||||
}
|
||||
|
||||
void G1StringDedupQueue::wait() {
|
||||
MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
|
||||
while (_queue->_empty) {
|
||||
ml.wait(Mutex::_no_safepoint_check_flag);
|
||||
}
|
||||
}
|
||||
|
||||
void G1StringDedupQueue::push(uint worker_id, oop java_string) {
|
||||
assert(SafepointSynchronize::is_at_safepoint(), "Must be at safepoint");
|
||||
assert(worker_id < _queue->_nqueues, "Invalid queue");
|
||||
|
||||
// Push and notify waiter
|
||||
G1StringDedupWorkerQueue& worker_queue = _queue->_queues[worker_id];
|
||||
if (!worker_queue.is_full()) {
|
||||
worker_queue.push(java_string);
|
||||
if (_queue->_empty) {
|
||||
MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (_queue->_empty) {
|
||||
// Mark non-empty and notify waiter
|
||||
_queue->_empty = false;
|
||||
ml.notify();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Queue is full, drop the string and update the statistics
|
||||
Atomic::inc_ptr(&_queue->_dropped);
|
||||
}
|
||||
}
|
||||
|
||||
oop G1StringDedupQueue::pop() {
|
||||
assert(!SafepointSynchronize::is_at_safepoint(), "Must not be at safepoint");
|
||||
No_Safepoint_Verifier nsv;
|
||||
|
||||
// Try all queues before giving up
|
||||
for (size_t tries = 0; tries < _queue->_nqueues; tries++) {
|
||||
// The cursor indicates where we left of last time
|
||||
G1StringDedupWorkerQueue* queue = &_queue->_queues[_queue->_cursor];
|
||||
while (!queue->is_empty()) {
|
||||
oop obj = queue->pop();
|
||||
// The oop we pop can be NULL if it was marked
|
||||
// dead. Just ignore those and pop the next oop.
|
||||
if (obj != NULL) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
// Try next queue
|
||||
_queue->_cursor = (_queue->_cursor + 1) % _queue->_nqueues;
|
||||
}
|
||||
|
||||
// Mark empty
|
||||
_queue->_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<oop, mtGC> iter(_queue->_queues[queue]);
|
||||
while (!iter.is_empty()) {
|
||||
oop* p = iter.next_addr();
|
||||
if (*p != NULL) {
|
||||
if (cl->is_alive(*p)) {
|
||||
cl->keep_alive(p);
|
||||
} else {
|
||||
// Clear dead reference
|
||||
*p = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void G1StringDedupQueue::print_statistics(outputStream* st) {
|
||||
st->print_cr(
|
||||
" [Queue]\n"
|
||||
" [Dropped: "UINTX_FORMAT"]", _queue->_dropped);
|
||||
}
|
||||
|
||||
void G1StringDedupQueue::verify() {
|
||||
for (size_t i = 0; i < _queue->_nqueues; i++) {
|
||||
StackIterator<oop, mtGC> iter(_queue->_queues[i]);
|
||||
while (!iter.is_empty()) {
|
||||
oop obj = iter.next();
|
||||
if (obj != NULL) {
|
||||
guarantee(Universe::heap()->is_in_reserved(obj), "Object must be on the heap");
|
||||
guarantee(!obj->is_forwarded(), "Object must not be forwarded");
|
||||
guarantee(java_lang_String::is_instance(obj), "Object must be a String");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_IMPLEMENTATION_G1_G1STRINGDEDUPQUEUE_HPP
|
||||
#define SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPQUEUE_HPP
|
||||
|
||||
#include "memory/allocation.hpp"
|
||||
#include "oops/oop.hpp"
|
||||
#include "utilities/stack.hpp"
|
||||
|
||||
class G1StringDedupUnlinkOrOopsDoClosure;
|
||||
|
||||
//
|
||||
// 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).
|
||||
//
|
||||
// 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<mtGC> {
|
||||
private:
|
||||
typedef Stack<oop, mtGC> G1StringDedupWorkerQueue;
|
||||
|
||||
static G1StringDedupQueue* _queue;
|
||||
static const size_t _max_size;
|
||||
static const size_t _max_cache_size;
|
||||
|
||||
G1StringDedupWorkerQueue* _queues;
|
||||
size_t _nqueues;
|
||||
size_t _cursor;
|
||||
volatile bool _empty;
|
||||
|
||||
// Statistics counter, only used for logging.
|
||||
uintx _dropped;
|
||||
|
||||
G1StringDedupQueue();
|
||||
~G1StringDedupQueue();
|
||||
|
||||
static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, size_t queue);
|
||||
|
||||
public:
|
||||
static void create();
|
||||
|
||||
// Blocks and waits for the queue to become non-empty.
|
||||
static void wait();
|
||||
|
||||
// Pushes a deduplication candidate onto a specific GC worker queue.
|
||||
static void push(uint worker_id, oop java_string);
|
||||
|
||||
// Pops a deduplication candidate from any queue, returns NULL if
|
||||
// all queues are empty.
|
||||
static oop pop();
|
||||
|
||||
static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl);
|
||||
|
||||
static void print_statistics(outputStream* st);
|
||||
static void verify();
|
||||
};
|
||||
|
||||
#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPQUEUE_HPP
|
162
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.cpp
Normal file
162
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_implementation/g1/g1StringDedupStat.hpp"
|
||||
|
||||
G1StringDedupStat::G1StringDedupStat() :
|
||||
_inspected(0),
|
||||
_skipped(0),
|
||||
_hashed(0),
|
||||
_known(0),
|
||||
_new(0),
|
||||
_new_bytes(0),
|
||||
_deduped(0),
|
||||
_deduped_bytes(0),
|
||||
_deduped_young(0),
|
||||
_deduped_young_bytes(0),
|
||||
_deduped_old(0),
|
||||
_deduped_old_bytes(0),
|
||||
_idle(0),
|
||||
_exec(0),
|
||||
_block(0),
|
||||
_start(0.0),
|
||||
_idle_elapsed(0.0),
|
||||
_exec_elapsed(0.0),
|
||||
_block_elapsed(0.0) {
|
||||
}
|
||||
|
||||
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::print_summary(outputStream* st, const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat) {
|
||||
double total_deduped_bytes_percent = 0.0;
|
||||
|
||||
if (total_stat._new_bytes > 0) {
|
||||
// Avoid division by zero
|
||||
total_deduped_bytes_percent = (double)total_stat._deduped_bytes / (double)total_stat._new_bytes * 100.0;
|
||||
}
|
||||
|
||||
st->date_stamp(PrintGCDateStamps);
|
||||
st->stamp(PrintGCTimeStamps);
|
||||
st->print_cr(
|
||||
"[GC 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_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,
|
||||
last_stat._exec_elapsed);
|
||||
}
|
||||
|
||||
void G1StringDedupStat::print_statistics(outputStream* st, const G1StringDedupStat& stat, bool total) {
|
||||
double young_percent = 0.0;
|
||||
double old_percent = 0.0;
|
||||
double skipped_percent = 0.0;
|
||||
double hashed_percent = 0.0;
|
||||
double known_percent = 0.0;
|
||||
double new_percent = 0.0;
|
||||
double deduped_percent = 0.0;
|
||||
double deduped_bytes_percent = 0.0;
|
||||
double deduped_young_percent = 0.0;
|
||||
double deduped_young_bytes_percent = 0.0;
|
||||
double deduped_old_percent = 0.0;
|
||||
double deduped_old_bytes_percent = 0.0;
|
||||
|
||||
if (stat._inspected > 0) {
|
||||
// Avoid division by zero
|
||||
skipped_percent = (double)stat._skipped / (double)stat._inspected * 100.0;
|
||||
hashed_percent = (double)stat._hashed / (double)stat._inspected * 100.0;
|
||||
known_percent = (double)stat._known / (double)stat._inspected * 100.0;
|
||||
new_percent = (double)stat._new / (double)stat._inspected * 100.0;
|
||||
}
|
||||
|
||||
if (stat._new > 0) {
|
||||
// Avoid division by zero
|
||||
deduped_percent = (double)stat._deduped / (double)stat._new * 100.0;
|
||||
}
|
||||
|
||||
if (stat._deduped > 0) {
|
||||
// Avoid division by zero
|
||||
deduped_young_percent = (double)stat._deduped_young / (double)stat._deduped * 100.0;
|
||||
deduped_old_percent = (double)stat._deduped_old / (double)stat._deduped * 100.0;
|
||||
}
|
||||
|
||||
if (stat._new_bytes > 0) {
|
||||
// Avoid division by zero
|
||||
deduped_bytes_percent = (double)stat._deduped_bytes / (double)stat._new_bytes * 100.0;
|
||||
}
|
||||
|
||||
if (stat._deduped_bytes > 0) {
|
||||
// Avoid division by zero
|
||||
deduped_young_bytes_percent = (double)stat._deduped_young_bytes / (double)stat._deduped_bytes * 100.0;
|
||||
deduped_old_bytes_percent = (double)stat._deduped_old_bytes / (double)stat._deduped_bytes * 100.0;
|
||||
}
|
||||
|
||||
if (total) {
|
||||
st->print_cr(
|
||||
" [Total Exec: "UINTX_FORMAT"/"G1_STRDEDUP_TIME_FORMAT", Idle: "UINTX_FORMAT"/"G1_STRDEDUP_TIME_FORMAT", Blocked: "UINTX_FORMAT"/"G1_STRDEDUP_TIME_FORMAT"]",
|
||||
stat._exec, stat._exec_elapsed, stat._idle, stat._idle_elapsed, stat._block, stat._block_elapsed);
|
||||
} else {
|
||||
st->print_cr(
|
||||
" [Last Exec: "G1_STRDEDUP_TIME_FORMAT", Idle: "G1_STRDEDUP_TIME_FORMAT", Blocked: "UINTX_FORMAT"/"G1_STRDEDUP_TIME_FORMAT"]",
|
||||
stat._exec_elapsed, stat._idle_elapsed, stat._block, stat._block_elapsed);
|
||||
}
|
||||
st->print_cr(
|
||||
" [Inspected: "G1_STRDEDUP_OBJECTS_FORMAT"]\n"
|
||||
" [Skipped: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]\n"
|
||||
" [Hashed: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]\n"
|
||||
" [Known: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]\n"
|
||||
" [New: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT") "G1_STRDEDUP_BYTES_FORMAT"]\n"
|
||||
" [Deduplicated: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT") "G1_STRDEDUP_BYTES_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]\n"
|
||||
" [Young: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT") "G1_STRDEDUP_BYTES_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]\n"
|
||||
" [Old: "G1_STRDEDUP_OBJECTS_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT") "G1_STRDEDUP_BYTES_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT")]",
|
||||
stat._inspected,
|
||||
stat._skipped, skipped_percent,
|
||||
stat._hashed, hashed_percent,
|
||||
stat._known, known_percent,
|
||||
stat._new, new_percent, G1_STRDEDUP_BYTES_PARAM(stat._new_bytes),
|
||||
stat._deduped, deduped_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_bytes), deduped_bytes_percent,
|
||||
stat._deduped_young, deduped_young_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_young_bytes), deduped_young_bytes_percent,
|
||||
stat._deduped_old, deduped_old_percent, G1_STRDEDUP_BYTES_PARAM(stat._deduped_old_bytes), deduped_old_bytes_percent);
|
||||
}
|
142
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.hpp
Normal file
142
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.hpp
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_IMPLEMENTATION_G1_G1STRINGDEDUPSTAT_HPP
|
||||
#define SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPSTAT_HPP
|
||||
|
||||
#include "memory/allocation.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
|
||||
// Macros for GC log output formating
|
||||
#define G1_STRDEDUP_OBJECTS_FORMAT UINTX_FORMAT_W(12)
|
||||
#define G1_STRDEDUP_TIME_FORMAT "%1.7lf secs"
|
||||
#define G1_STRDEDUP_PERCENT_FORMAT "%5.1lf%%"
|
||||
#define G1_STRDEDUP_PERCENT_FORMAT_NS "%.1lf%%"
|
||||
#define G1_STRDEDUP_BYTES_FORMAT "%8.1lf%s"
|
||||
#define G1_STRDEDUP_BYTES_FORMAT_NS "%.1lf%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 {
|
||||
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;
|
||||
double _idle_elapsed;
|
||||
double _exec_elapsed;
|
||||
double _block_elapsed;
|
||||
|
||||
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 = os::elapsedTime();
|
||||
_idle++;
|
||||
}
|
||||
|
||||
void mark_exec() {
|
||||
double now = os::elapsedTime();
|
||||
_idle_elapsed = now - _start;
|
||||
_start = now;
|
||||
_exec++;
|
||||
}
|
||||
|
||||
void mark_block() {
|
||||
double now = os::elapsedTime();
|
||||
_exec_elapsed += now - _start;
|
||||
_start = now;
|
||||
_block++;
|
||||
}
|
||||
|
||||
void mark_unblock() {
|
||||
double now = os::elapsedTime();
|
||||
_block_elapsed += now - _start;
|
||||
_start = now;
|
||||
}
|
||||
|
||||
void mark_done() {
|
||||
double now = os::elapsedTime();
|
||||
_exec_elapsed += now - _start;
|
||||
}
|
||||
|
||||
void add(const G1StringDedupStat& stat);
|
||||
|
||||
static void print_summary(outputStream* st, const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat);
|
||||
static void print_statistics(outputStream* st, const G1StringDedupStat& stat, bool total);
|
||||
};
|
||||
|
||||
#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPSTAT_HPP
|
569
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.cpp
Normal file
569
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.cpp
Normal file
@ -0,0 +1,569 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.hpp"
|
||||
#include "gc_implementation/g1/g1CollectedHeap.inline.hpp"
|
||||
#include "gc_implementation/g1/g1SATBCardTableModRefBS.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupTable.hpp"
|
||||
#include "memory/gcLocker.hpp"
|
||||
#include "memory/padded.inline.hpp"
|
||||
#include "oops/typeArrayOop.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
|
||||
//
|
||||
// Freelist in the deduplication table entry cache. Links table
|
||||
// entries together using their _next fields.
|
||||
//
|
||||
class G1StringDedupEntryFreeList : public CHeapObj<mtGC> {
|
||||
private:
|
||||
G1StringDedupEntry* _list;
|
||||
size_t _length;
|
||||
|
||||
public:
|
||||
G1StringDedupEntryFreeList() :
|
||||
_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;
|
||||
}
|
||||
|
||||
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<mtGC> {
|
||||
private:
|
||||
// One freelist per GC worker to allow lock less freeing of
|
||||
// entries while doing a parallel scan of the table. Using
|
||||
// PaddedEnd to avoid false sharing.
|
||||
PaddedEnd<G1StringDedupEntryFreeList>* _lists;
|
||||
size_t _nlists;
|
||||
|
||||
public:
|
||||
G1StringDedupEntryCache();
|
||||
~G1StringDedupEntryCache();
|
||||
|
||||
// Get a table entry from the cache freelist, or allocate a new
|
||||
// entry if the cache is empty.
|
||||
G1StringDedupEntry* alloc();
|
||||
|
||||
// Insert a table entry into the cache freelist.
|
||||
void free(G1StringDedupEntry* entry, uint worker_id);
|
||||
|
||||
// Returns current number of entries in the cache.
|
||||
size_t size();
|
||||
|
||||
// If the cache has grown above the given max size, trim it down
|
||||
// and deallocate the memory occupied by trimmed of entries.
|
||||
void trim(size_t max_size);
|
||||
};
|
||||
|
||||
G1StringDedupEntryCache::G1StringDedupEntryCache() {
|
||||
_nlists = MAX2(ParallelGCThreads, (size_t)1);
|
||||
_lists = PaddedArray<G1StringDedupEntryFreeList, mtGC>::create_unfreeable((uint)_nlists);
|
||||
}
|
||||
|
||||
G1StringDedupEntryCache::~G1StringDedupEntryCache() {
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
G1StringDedupEntry* G1StringDedupEntryCache::alloc() {
|
||||
for (size_t i = 0; i < _nlists; i++) {
|
||||
G1StringDedupEntry* entry = _lists[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);
|
||||
_lists[worker_id].add(entry);
|
||||
}
|
||||
|
||||
size_t G1StringDedupEntryCache::size() {
|
||||
size_t size = 0;
|
||||
for (size_t i = 0; i < _nlists; i++) {
|
||||
size += _lists[i].length();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void G1StringDedupEntryCache::trim(size_t max_size) {
|
||||
size_t cache_size = 0;
|
||||
for (size_t i = 0; i < _nlists; i++) {
|
||||
G1StringDedupEntryFreeList* list = &_lists[i];
|
||||
cache_size += list->length();
|
||||
while (cache_size > max_size) {
|
||||
G1StringDedupEntry* entry = list->remove();
|
||||
assert(entry != NULL, "Should not be null");
|
||||
cache_size--;
|
||||
delete entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, mtGC);
|
||||
}
|
||||
|
||||
void G1StringDedupTable::create() {
|
||||
assert(_table == NULL, "One string deduplication table allowed");
|
||||
_entry_cache = new G1StringDedupEntryCache();
|
||||
_table = new G1StringDedupTable(_min_size);
|
||||
}
|
||||
|
||||
void G1StringDedupTable::add(typeArrayOop value, unsigned int hash, G1StringDedupEntry** list) {
|
||||
G1StringDedupEntry* entry = _entry_cache->alloc();
|
||||
entry->set_obj(value);
|
||||
entry->set_hash(hash);
|
||||
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_CHAR),
|
||||
value2->base(T_CHAR),
|
||||
value1->length() * sizeof(jchar)))));
|
||||
}
|
||||
|
||||
typeArrayOop G1StringDedupTable::lookup(typeArrayOop value, unsigned int hash,
|
||||
G1StringDedupEntry** list, uintx &count) {
|
||||
for (G1StringDedupEntry* entry = *list; entry != NULL; entry = entry->next()) {
|
||||
if (entry->hash() == hash) {
|
||||
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, 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, 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, hash, list);
|
||||
|
||||
// Update statistics
|
||||
_entries_added++;
|
||||
}
|
||||
|
||||
return existing_value;
|
||||
}
|
||||
|
||||
unsigned int G1StringDedupTable::hash_code(typeArrayOop value) {
|
||||
unsigned int hash;
|
||||
int length = value->length();
|
||||
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");
|
||||
No_Safepoint_Verifier nsv;
|
||||
|
||||
stat.inc_inspected();
|
||||
|
||||
typeArrayOop value = java_lang_String::value(java_string);
|
||||
if (value == NULL) {
|
||||
// String has no value
|
||||
stat.inc_skipped();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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, 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.
|
||||
G1SATBCardTableModRefBS::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++;
|
||||
|
||||
// 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 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;
|
||||
unsigned int hash = hash_code(value);
|
||||
(*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(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");
|
||||
unsigned int hash = hash_code(value);
|
||||
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();
|
||||
G1StringDedupEntry** entry2 = (*entry1)->next_addr();
|
||||
while (*entry2 != NULL) {
|
||||
typeArrayOop value2 = (*entry2)->obj();
|
||||
guarantee(!equals(value1, value2), "Table entries must not have identical arrays");
|
||||
entry2 = (*entry2)->next_addr();
|
||||
}
|
||||
entry1 = (*entry1)->next_addr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void G1StringDedupTable::trim_entry_cache() {
|
||||
MutexLockerEx ml(StringDedupTable_lock, Mutex::_no_safepoint_check_flag);
|
||||
size_t max_cache_size = (size_t)(_table->_size * _max_cache_factor);
|
||||
_entry_cache->trim(max_cache_size);
|
||||
}
|
||||
|
||||
void G1StringDedupTable::print_statistics(outputStream* st) {
|
||||
st->print_cr(
|
||||
" [Table]\n"
|
||||
" [Memory Usage: "G1_STRDEDUP_BYTES_FORMAT_NS"]\n"
|
||||
" [Size: "SIZE_FORMAT", Min: "SIZE_FORMAT", Max: "SIZE_FORMAT"]\n"
|
||||
" [Entries: "UINTX_FORMAT", Load: "G1_STRDEDUP_PERCENT_FORMAT_NS", Cached: " UINTX_FORMAT ", Added: "UINTX_FORMAT", Removed: "UINTX_FORMAT"]\n"
|
||||
" [Resize Count: "UINTX_FORMAT", Shrink Threshold: "UINTX_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT_NS"), Grow Threshold: "UINTX_FORMAT"("G1_STRDEDUP_PERCENT_FORMAT_NS")]\n"
|
||||
" [Rehash Count: "UINTX_FORMAT", Rehash Threshold: "UINTX_FORMAT", Hash Seed: 0x%x]\n"
|
||||
" [Age Threshold: "UINTX_FORMAT"]",
|
||||
G1_STRDEDUP_BYTES_PARAM(_table->_size * sizeof(G1StringDedupEntry*) + (_table->_entries + _entry_cache->size()) * sizeof(G1StringDedupEntry)),
|
||||
_table->_size, _min_size, _max_size,
|
||||
_table->_entries, (double)_table->_entries / (double)_table->_size * 100.0, _entry_cache->size(), _entries_added, _entries_removed,
|
||||
_resize_count, _table->_shrink_threshold, _shrink_load_factor * 100.0, _table->_grow_threshold, _grow_load_factor * 100.0,
|
||||
_rehash_count, _rehash_threshold, _table->_hash_seed,
|
||||
StringDeduplicationAgeThreshold);
|
||||
}
|
230
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.hpp
Normal file
230
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.hpp
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_IMPLEMENTATION_G1_G1STRINGDEDUPTABLE_HPP
|
||||
#define SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPTABLE_HPP
|
||||
|
||||
#include "gc_implementation/g1/g1StringDedupStat.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
|
||||
class G1StringDedupEntryCache;
|
||||
|
||||
//
|
||||
// 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<mtGC> {
|
||||
private:
|
||||
G1StringDedupEntry* _next;
|
||||
unsigned int _hash;
|
||||
typeArrayOop _obj;
|
||||
|
||||
public:
|
||||
G1StringDedupEntry() :
|
||||
_next(NULL),
|
||||
_hash(0),
|
||||
_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;
|
||||
}
|
||||
|
||||
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<mtGC> {
|
||||
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, 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, 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, unsigned int hash);
|
||||
|
||||
// Thread safe lookup or add of table entry
|
||||
static typeArrayOop lookup_or_add(typeArrayOop value, 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, 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);
|
||||
|
||||
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, trim it down according to policy
|
||||
static void trim_entry_cache();
|
||||
|
||||
static void unlink_or_oops_do(G1StringDedupUnlinkOrOopsDoClosure* cl, uint worker_id);
|
||||
|
||||
static void print_statistics(outputStream* st);
|
||||
static void verify();
|
||||
};
|
||||
|
||||
#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPTABLE_HPP
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_implementation/g1/g1Log.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupTable.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupThread.hpp"
|
||||
#include "gc_implementation/g1/g1StringDedupQueue.hpp"
|
||||
|
||||
G1StringDedupThread* G1StringDedupThread::_thread = NULL;
|
||||
|
||||
G1StringDedupThread::G1StringDedupThread() :
|
||||
ConcurrentGCThread() {
|
||||
set_name("String Deduplication Thread");
|
||||
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;
|
||||
}
|
||||
|
||||
void G1StringDedupThread::print_on(outputStream* st) const {
|
||||
st->print("\"%s\" ", name());
|
||||
Thread::print_on(st);
|
||||
st->cr();
|
||||
}
|
||||
|
||||
void G1StringDedupThread::run() {
|
||||
G1StringDedupStat total_stat;
|
||||
|
||||
initialize_in_thread();
|
||||
wait_for_universe_init();
|
||||
|
||||
// Main loop
|
||||
for (;;) {
|
||||
G1StringDedupStat stat;
|
||||
|
||||
stat.mark_idle();
|
||||
|
||||
// Wait for the queue to become non-empty
|
||||
G1StringDedupQueue::wait();
|
||||
|
||||
// Include this thread in safepoints
|
||||
stsJoin();
|
||||
|
||||
stat.mark_exec();
|
||||
|
||||
// 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 (stsShouldYield()) {
|
||||
stat.mark_block();
|
||||
stsYield(NULL);
|
||||
stat.mark_unblock();
|
||||
}
|
||||
}
|
||||
|
||||
G1StringDedupTable::trim_entry_cache();
|
||||
|
||||
stat.mark_done();
|
||||
|
||||
// Print statistics
|
||||
total_stat.add(stat);
|
||||
print(gclog_or_tty, stat, total_stat);
|
||||
|
||||
// Exclude this thread from safepoints
|
||||
stsLeave();
|
||||
}
|
||||
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
void G1StringDedupThread::print(outputStream* st, const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat) {
|
||||
if (G1Log::fine() || PrintStringDeduplicationStatistics) {
|
||||
G1StringDedupStat::print_summary(st, last_stat, total_stat);
|
||||
if (PrintStringDeduplicationStatistics) {
|
||||
G1StringDedupStat::print_statistics(st, last_stat, false);
|
||||
G1StringDedupStat::print_statistics(st, total_stat, true);
|
||||
G1StringDedupTable::print_statistics(st);
|
||||
G1StringDedupQueue::print_statistics(st);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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_IMPLEMENTATION_G1_G1STRINGDEDUPTHREAD_HPP
|
||||
#define SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPTHREAD_HPP
|
||||
|
||||
#include "gc_implementation/g1/g1StringDedupStat.hpp"
|
||||
#include "gc_implementation/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(outputStream* st, const G1StringDedupStat& last_stat, const G1StringDedupStat& total_stat);
|
||||
|
||||
public:
|
||||
static void create();
|
||||
static G1StringDedupThread* thread();
|
||||
|
||||
virtual void run();
|
||||
virtual void print_on(outputStream* st) const;
|
||||
};
|
||||
|
||||
#endif // SHARE_VM_GC_IMPLEMENTATION_G1_G1STRINGDEDUPTHREAD_HPP
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2014, 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
|
||||
@ -30,10 +30,18 @@
|
||||
#include "utilities/stack.inline.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
#if INCLUDE_ALL_GCS
|
||||
#include "gc_implementation/g1/g1StringDedup.hpp"
|
||||
#include "gc_implementation/parallelScavenge/psParallelCompact.hpp"
|
||||
#endif // INCLUDE_ALL_GCS
|
||||
|
||||
inline void MarkSweep::mark_object(oop obj) {
|
||||
#if INCLUDE_ALL_GCS
|
||||
if (G1StringDedup::is_enabled()) {
|
||||
// We must enqueue the object before it is marked
|
||||
// as we otherwise can't read the object's age.
|
||||
G1StringDedup::enqueue_from_mark(obj);
|
||||
}
|
||||
#endif
|
||||
// some marks may contain information we need to preserve so we store them away
|
||||
// and overwrite the mark. We'll restore it at the end of markSweep.
|
||||
markOop mark = obj->mark();
|
||||
|
@ -2246,6 +2246,8 @@ bool Arguments::check_vm_args_consistency() {
|
||||
"G1ConcRSHotCardLimit");
|
||||
status = status && verify_interval(G1ConcRSLogCacheSize, 0, 31,
|
||||
"G1ConcRSLogCacheSize");
|
||||
status = status && verify_interval(StringDeduplicationAgeThreshold, 1, markOopDesc::max_age,
|
||||
"StringDeduplicationAgeThreshold");
|
||||
}
|
||||
if (UseConcMarkSweepGC) {
|
||||
status = status && verify_min_value(CMSOldPLABNumRefills, 1, "CMSOldPLABNumRefills");
|
||||
|
@ -3840,6 +3840,22 @@ class CommandLineFlags {
|
||||
experimental(uintx, SymbolTableSize, defaultSymbolTableSize, \
|
||||
"Number of buckets in the JVM internal Symbol table") \
|
||||
\
|
||||
product(bool, UseStringDeduplication, false, \
|
||||
"Use string deduplication") \
|
||||
\
|
||||
product(bool, PrintStringDeduplicationStatistics, false, \
|
||||
"Print string deduplication statistics") \
|
||||
\
|
||||
product(uintx, StringDeduplicationAgeThreshold, 3, \
|
||||
"A string must reach this age (or be promoted to an old region) " \
|
||||
"to be considered for deduplication") \
|
||||
\
|
||||
diagnostic(bool, StringDeduplicationResizeALot, false, \
|
||||
"Force table resize every time the table is scanned") \
|
||||
\
|
||||
diagnostic(bool, StringDeduplicationRehashALot, false, \
|
||||
"Force table rehash every time the table is scanned") \
|
||||
\
|
||||
develop(bool, TraceDefaultMethods, false, \
|
||||
"Trace the default method processing steps") \
|
||||
\
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2014, 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
|
||||
@ -58,6 +58,8 @@ Mutex* SignatureHandlerLibrary_lock = NULL;
|
||||
Mutex* VtableStubs_lock = NULL;
|
||||
Mutex* SymbolTable_lock = NULL;
|
||||
Mutex* StringTable_lock = NULL;
|
||||
Monitor* StringDedupQueue_lock = NULL;
|
||||
Mutex* StringDedupTable_lock = NULL;
|
||||
Mutex* CodeCache_lock = NULL;
|
||||
Mutex* MethodData_lock = NULL;
|
||||
Mutex* RetData_lock = NULL;
|
||||
@ -196,6 +198,9 @@ void mutex_init() {
|
||||
def(MMUTracker_lock , Mutex , leaf , true );
|
||||
def(HotCardCache_lock , Mutex , special , true );
|
||||
def(EvacFailureStack_lock , Mutex , nonleaf , true );
|
||||
|
||||
def(StringDedupQueue_lock , Monitor, leaf, true );
|
||||
def(StringDedupTable_lock , Mutex , leaf, true );
|
||||
}
|
||||
def(ParGCRareEvent_lock , Mutex , leaf , true );
|
||||
def(DerivedPointerTableGC_lock , Mutex, leaf, true );
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2014, 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
|
||||
@ -66,6 +66,8 @@ extern Mutex* SignatureHandlerLibrary_lock; // a lock on the SignatureHandl
|
||||
extern Mutex* VtableStubs_lock; // a lock on the VtableStubs
|
||||
extern Mutex* SymbolTable_lock; // a lock on the symbol table
|
||||
extern Mutex* StringTable_lock; // a lock on the interned string table
|
||||
extern Monitor* StringDedupQueue_lock; // a lock on the string deduplication queue
|
||||
extern Mutex* StringDedupTable_lock; // a lock on the string deduplication table
|
||||
extern Mutex* CodeCache_lock; // a lock on the CodeCache, rank is special, use MutexLockerEx
|
||||
extern Mutex* MethodData_lock; // a lock on installation of method data
|
||||
extern Mutex* RetData_lock; // a lock on installation of RetData inside method data
|
||||
|
@ -49,11 +49,13 @@ public class TestGCLogMessages {
|
||||
|
||||
output.shouldNotContain("[Redirty Cards");
|
||||
output.shouldNotContain("[Code Root Purge");
|
||||
output.shouldNotContain("[String Dedup Fixup");
|
||||
output.shouldNotContain("[Young Free CSet");
|
||||
output.shouldNotContain("[Non-Young Free CSet");
|
||||
output.shouldHaveExitValue(0);
|
||||
|
||||
pb = ProcessTools.createJavaProcessBuilder("-XX:+UseG1GC",
|
||||
"-XX:+UseStringDeduplication",
|
||||
"-Xmx10M",
|
||||
"-XX:+PrintGCDetails",
|
||||
GCTest.class.getName());
|
||||
@ -62,11 +64,13 @@ public class TestGCLogMessages {
|
||||
|
||||
output.shouldContain("[Redirty Cards");
|
||||
output.shouldContain("[Code Root Purge");
|
||||
output.shouldContain("[String Dedup Fixup");
|
||||
output.shouldNotContain("[Young Free CSet");
|
||||
output.shouldNotContain("[Non-Young Free CSet");
|
||||
output.shouldHaveExitValue(0);
|
||||
|
||||
pb = ProcessTools.createJavaProcessBuilder("-XX:+UseG1GC",
|
||||
"-XX:+UseStringDeduplication",
|
||||
"-Xmx10M",
|
||||
"-XX:+PrintGCDetails",
|
||||
"-XX:+UnlockExperimentalVMOptions",
|
||||
@ -77,6 +81,7 @@ public class TestGCLogMessages {
|
||||
|
||||
output.shouldContain("[Redirty Cards");
|
||||
output.shouldContain("[Code Root Purge");
|
||||
output.shouldContain("[String Dedup Fixup");
|
||||
output.shouldContain("[Young Free CSet");
|
||||
output.shouldContain("[Non-Young Free CSet");
|
||||
|
||||
|
36
hotspot/test/gc/g1/TestStringDeduplicationAgeThreshold.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationAgeThreshold.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationAgeThreshold
|
||||
* @summary Test string deduplication age threshold
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationAgeThreshold {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testAgeThreshold();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationFullGC.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationFullGC.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationFullGC
|
||||
* @summary Test string deduplication during full GC
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationFullGC {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testFullGC();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationInterned.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationInterned.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationInterned
|
||||
* @summary Test string deduplication of interned strings
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationInterned {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testInterned();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationMemoryUsage.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationMemoryUsage.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationMemoryUsage
|
||||
* @summary Test string deduplication memory usage
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationMemoryUsage {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testMemoryUsage();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationPrintOptions.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationPrintOptions.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationPrintOptions
|
||||
* @summary Test string deduplication print options
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationPrintOptions {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testPrintOptions();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationTableRehash.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationTableRehash.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationTableRehash
|
||||
* @summary Test string deduplication table rehash
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationTableRehash {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testTableRehash();
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationTableResize.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationTableResize.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationTableResize
|
||||
* @summary Test string deduplication table resize
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationTableResize {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testTableResize();
|
||||
}
|
||||
}
|
512
hotspot/test/gc/g1/TestStringDeduplicationTools.java
Normal file
512
hotspot/test/gc/g1/TestStringDeduplicationTools.java
Normal file
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common code for string deduplication tests
|
||||
*/
|
||||
|
||||
import java.lang.management.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import com.oracle.java.testlibrary.*;
|
||||
import sun.misc.*;
|
||||
|
||||
class TestStringDeduplicationTools {
|
||||
private static final String YoungGC = "YoungGC";
|
||||
private static final String FullGC = "FullGC";
|
||||
|
||||
private static final int Xmn = 50; // MB
|
||||
private static final int Xms = 100; // MB
|
||||
private static final int Xmx = 100; // MB
|
||||
private static final int MB = 1024 * 1024;
|
||||
private static final int StringLength = 50;
|
||||
|
||||
private static Field valueField;
|
||||
private static Unsafe unsafe;
|
||||
private static byte[] dummy;
|
||||
|
||||
static {
|
||||
try {
|
||||
Field field = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
field.setAccessible(true);
|
||||
unsafe = (Unsafe)field.get(null);
|
||||
|
||||
valueField = String.class.getDeclaredField("value");
|
||||
valueField.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getValue(String string) {
|
||||
try {
|
||||
return valueField.get(string);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doFullGc(int numberOfTimes) {
|
||||
for (int i = 0; i < numberOfTimes; i++) {
|
||||
System.out.println("Begin: Full GC " + (i + 1) + "/" + numberOfTimes);
|
||||
System.gc();
|
||||
System.out.println("End: Full GC " + (i + 1) + "/" + numberOfTimes);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doYoungGc(int numberOfTimes) {
|
||||
// Provoke at least numberOfTimes young GCs
|
||||
final int objectSize = 128;
|
||||
final int maxObjectInYoung = (Xmn * MB) / objectSize;
|
||||
for (int i = 0; i < numberOfTimes; i++) {
|
||||
System.out.println("Begin: Young GC " + (i + 1) + "/" + numberOfTimes);
|
||||
for (int j = 0; j < maxObjectInYoung + 1; j++) {
|
||||
dummy = new byte[objectSize];
|
||||
}
|
||||
System.out.println("End: Young GC " + (i + 1) + "/" + numberOfTimes);
|
||||
}
|
||||
}
|
||||
|
||||
private static void forceDeduplication(int ageThreshold, String gcType) {
|
||||
// Force deduplication to happen by either causing a FullGC or a YoungGC.
|
||||
// We do several collections to also provoke a situation where the the
|
||||
// deduplication thread needs to yield while processing the queue. This
|
||||
// also tests that the references in the deduplication queue are adjusted
|
||||
// accordingly.
|
||||
if (gcType.equals(FullGC)) {
|
||||
doFullGc(3);
|
||||
} else {
|
||||
doYoungGc(ageThreshold + 3);
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateString(int id) {
|
||||
StringBuilder builder = new StringBuilder(StringLength);
|
||||
|
||||
builder.append("DeduplicationTestString:" + id + ":");
|
||||
|
||||
while (builder.length() < StringLength) {
|
||||
builder.append('X');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static ArrayList<String> createStrings(int total, int unique) {
|
||||
System.out.println("Creating strings: total=" + total + ", unique=" + unique);
|
||||
if (total % unique != 0) {
|
||||
throw new RuntimeException("Total must be divisible by unique");
|
||||
}
|
||||
|
||||
ArrayList<String> list = new ArrayList<String>(total);
|
||||
for (int j = 0; j < total / unique; j++) {
|
||||
for (int i = 0; i < unique; i++) {
|
||||
list.add(generateString(i));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void verifyStrings(ArrayList<String> list, int uniqueExpected) {
|
||||
for (;;) {
|
||||
// Check number of deduplicated strings
|
||||
ArrayList<Object> unique = new ArrayList<Object>(uniqueExpected);
|
||||
for (String string: list) {
|
||||
Object value = getValue(string);
|
||||
boolean uniqueValue = true;
|
||||
for (Object obj: unique) {
|
||||
if (obj == value) {
|
||||
uniqueValue = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueValue) {
|
||||
unique.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Verifying strings: total=" + list.size() +
|
||||
", uniqueFound=" + unique.size() +
|
||||
", uniqueExpected=" + uniqueExpected);
|
||||
|
||||
if (unique.size() == uniqueExpected) {
|
||||
System.out.println("Deduplication completed");
|
||||
break;
|
||||
} else {
|
||||
System.out.println("Deduplication not completed, waiting...");
|
||||
|
||||
// Give the deduplication thread time to complete
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static OutputAnalyzer runTest(String... extraArgs) throws Exception {
|
||||
String[] defaultArgs = new String[] {
|
||||
"-Xmn" + Xmn + "m",
|
||||
"-Xms" + Xms + "m",
|
||||
"-Xmx" + Xmx + "m",
|
||||
"-XX:+UseG1GC",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-XX:+VerifyAfterGC" // Always verify after GC
|
||||
};
|
||||
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
args.addAll(Arrays.asList(defaultArgs));
|
||||
args.addAll(Arrays.asList(extraArgs));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(args.toArray(new String[args.size()]));
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
System.err.println(output.getStderr());
|
||||
System.out.println(output.getStdout());
|
||||
return output;
|
||||
}
|
||||
|
||||
private static class DeduplicationTest {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Begin: DeduplicationTest");
|
||||
|
||||
final int numberOfStrings = Integer.parseUnsignedInt(args[0]);
|
||||
final int numberOfUniqueStrings = Integer.parseUnsignedInt(args[1]);
|
||||
final int ageThreshold = Integer.parseUnsignedInt(args[2]);
|
||||
final String gcType = args[3];
|
||||
|
||||
ArrayList<String> list = createStrings(numberOfStrings, numberOfUniqueStrings);
|
||||
forceDeduplication(ageThreshold, gcType);
|
||||
verifyStrings(list, numberOfUniqueStrings);
|
||||
|
||||
System.out.println("End: DeduplicationTest");
|
||||
}
|
||||
|
||||
public static OutputAnalyzer run(int numberOfStrings, int ageThreshold, String gcType, String... extraArgs) throws Exception {
|
||||
String[] defaultArgs = new String[] {
|
||||
"-XX:+UseStringDeduplication",
|
||||
"-XX:StringDeduplicationAgeThreshold=" + ageThreshold,
|
||||
DeduplicationTest.class.getName(),
|
||||
"" + numberOfStrings,
|
||||
"" + numberOfStrings / 2,
|
||||
"" + ageThreshold,
|
||||
gcType
|
||||
};
|
||||
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
args.addAll(Arrays.asList(extraArgs));
|
||||
args.addAll(Arrays.asList(defaultArgs));
|
||||
|
||||
return runTest(args.toArray(new String[args.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
private static class InternedTest {
|
||||
public static void main(String[] args) {
|
||||
// This test verifies that interned strings are always
|
||||
// deduplicated when being interned, and never after
|
||||
// being interned.
|
||||
|
||||
System.out.println("Begin: InternedTest");
|
||||
|
||||
final int ageThreshold = Integer.parseUnsignedInt(args[0]);
|
||||
final String baseString = "DeduplicationTestString:" + InternedTest.class.getName();
|
||||
|
||||
// Create duplicate of baseString
|
||||
StringBuilder sb1 = new StringBuilder(baseString);
|
||||
String dupString1 = sb1.toString();
|
||||
if (getValue(dupString1) == getValue(baseString)) {
|
||||
throw new RuntimeException("Values should not match");
|
||||
}
|
||||
|
||||
// Force baseString to be inspected for deduplication
|
||||
// and be inserted into the deduplication hashtable.
|
||||
forceDeduplication(ageThreshold, FullGC);
|
||||
|
||||
// Wait for deduplication to occur
|
||||
while (getValue(dupString1) != getValue(baseString)) {
|
||||
System.out.println("Waiting...");
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new duplicate of baseString
|
||||
StringBuilder sb2 = new StringBuilder(baseString);
|
||||
String dupString2 = sb2.toString();
|
||||
if (getValue(dupString2) == getValue(baseString)) {
|
||||
throw new RuntimeException("Values should not match");
|
||||
}
|
||||
|
||||
// Intern the new duplicate
|
||||
Object beforeInternedValue = getValue(dupString2);
|
||||
String internedString = dupString2.intern();
|
||||
if (internedString != dupString2) {
|
||||
throw new RuntimeException("String should match");
|
||||
}
|
||||
if (getValue(internedString) != getValue(baseString)) {
|
||||
throw new RuntimeException("Values should match");
|
||||
}
|
||||
|
||||
// Check original value of interned string, to make sure
|
||||
// deduplication happened on the interned string and not
|
||||
// on the base string
|
||||
if (beforeInternedValue == getValue(baseString)) {
|
||||
throw new RuntimeException("Values should not match");
|
||||
}
|
||||
|
||||
System.out.println("End: InternedTest");
|
||||
}
|
||||
|
||||
public static OutputAnalyzer run() throws Exception {
|
||||
return runTest("-XX:+PrintGC",
|
||||
"-XX:+PrintGCDetails",
|
||||
"-XX:+UseStringDeduplication",
|
||||
"-XX:+PrintStringDeduplicationStatistics",
|
||||
"-XX:StringDeduplicationAgeThreshold=" + DefaultAgeThreshold,
|
||||
InternedTest.class.getName(),
|
||||
"" + DefaultAgeThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MemoryUsageTest {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Begin: MemoryUsageTest");
|
||||
|
||||
final boolean useStringDeduplication = Boolean.parseBoolean(args[0]);
|
||||
final int numberOfStrings = LargeNumberOfStrings;
|
||||
final int numberOfUniqueStrings = 1;
|
||||
|
||||
ArrayList<String> list = createStrings(numberOfStrings, numberOfUniqueStrings);
|
||||
forceDeduplication(DefaultAgeThreshold, FullGC);
|
||||
|
||||
if (useStringDeduplication) {
|
||||
verifyStrings(list, numberOfUniqueStrings);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
System.out.println("Heap Memory Usage: " + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed());
|
||||
|
||||
System.out.println("End: MemoryUsageTest");
|
||||
}
|
||||
|
||||
public static OutputAnalyzer run(boolean useStringDeduplication) throws Exception {
|
||||
String[] extraArgs = new String[0];
|
||||
|
||||
if (useStringDeduplication) {
|
||||
extraArgs = new String[] {
|
||||
"-XX:+UseStringDeduplication",
|
||||
"-XX:+PrintStringDeduplicationStatistics",
|
||||
"-XX:StringDeduplicationAgeThreshold=" + DefaultAgeThreshold
|
||||
};
|
||||
}
|
||||
|
||||
String[] defaultArgs = new String[] {
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintGCDetails",
|
||||
MemoryUsageTest.class.getName(),
|
||||
"" + useStringDeduplication
|
||||
};
|
||||
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
args.addAll(Arrays.asList(extraArgs));
|
||||
args.addAll(Arrays.asList(defaultArgs));
|
||||
|
||||
return runTest(args.toArray(new String[args.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests
|
||||
*/
|
||||
|
||||
private static final int LargeNumberOfStrings = 10000;
|
||||
private static final int SmallNumberOfStrings = 10;
|
||||
|
||||
private static final int MaxAgeThreshold = 15;
|
||||
private static final int DefaultAgeThreshold = 3;
|
||||
private static final int MinAgeThreshold = 1;
|
||||
|
||||
private static final int TooLowAgeThreshold = MinAgeThreshold - 1;
|
||||
private static final int TooHighAgeThreshold = MaxAgeThreshold + 1;
|
||||
|
||||
public static void testYoungGC() throws Exception {
|
||||
// Do young GC to age strings to provoke deduplication
|
||||
OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics");
|
||||
output.shouldNotContain("Full GC");
|
||||
output.shouldContain("GC pause (G1 Evacuation Pause) (young)");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testFullGC() throws Exception {
|
||||
// Do full GC to age strings to provoke deduplication
|
||||
OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
FullGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics");
|
||||
output.shouldNotContain("GC pause (G1 Evacuation Pause) (young)");
|
||||
output.shouldContain("Full GC");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testTableResize() throws Exception {
|
||||
// Test with StringDeduplicationResizeALot
|
||||
OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics",
|
||||
"-XX:+StringDeduplicationResizeALot");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldNotContain("Resize Count: 0");
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testTableRehash() throws Exception {
|
||||
// Test with StringDeduplicationRehashALot
|
||||
OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics",
|
||||
"-XX:+StringDeduplicationRehashALot");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldNotContain("Rehash Count: 0");
|
||||
output.shouldNotContain("Hash Seed: 0x0");
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testAgeThreshold() throws Exception {
|
||||
OutputAnalyzer output;
|
||||
|
||||
// Test with max age theshold
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
MaxAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
|
||||
// Test with min age theshold
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
MinAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC",
|
||||
"-XX:+PrintStringDeduplicationStatistics");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
|
||||
// Test with too low age threshold
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
TooLowAgeThreshold,
|
||||
YoungGC);
|
||||
output.shouldContain("StringDeduplicationAgeThreshold of " + TooLowAgeThreshold +
|
||||
" is invalid; must be between " + MinAgeThreshold + " and " + MaxAgeThreshold);
|
||||
output.shouldHaveExitValue(1);
|
||||
|
||||
// Test with too high age threshold
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
TooHighAgeThreshold,
|
||||
YoungGC);
|
||||
output.shouldContain("StringDeduplicationAgeThreshold of " + TooHighAgeThreshold +
|
||||
" is invalid; must be between " + MinAgeThreshold + " and " + MaxAgeThreshold);
|
||||
output.shouldHaveExitValue(1);
|
||||
}
|
||||
|
||||
public static void testPrintOptions() throws Exception {
|
||||
OutputAnalyzer output;
|
||||
|
||||
// Test without PrintGC and without PrintStringDeduplicationStatistics
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
YoungGC);
|
||||
output.shouldNotContain("GC concurrent-string-deduplication");
|
||||
output.shouldNotContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
|
||||
// Test with PrintGC but without PrintStringDeduplicationStatistics
|
||||
output = DeduplicationTest.run(SmallNumberOfStrings,
|
||||
DefaultAgeThreshold,
|
||||
YoungGC,
|
||||
"-XX:+PrintGC");
|
||||
output.shouldContain("GC concurrent-string-deduplication");
|
||||
output.shouldNotContain("Deduplicated:");
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testInterned() throws Exception {
|
||||
// Test that interned strings are deduplicated before being interned
|
||||
OutputAnalyzer output = InternedTest.run();
|
||||
output.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
public static void testMemoryUsage() throws Exception {
|
||||
// Test that memory usage is reduced after deduplication
|
||||
OutputAnalyzer output;
|
||||
final String usagePattern = "Heap Memory Usage: (\\d+)";
|
||||
|
||||
// Run without deduplication
|
||||
output = MemoryUsageTest.run(false);
|
||||
output.shouldHaveExitValue(0);
|
||||
final long memoryUsageWithoutDedup = Long.parseLong(output.firstMatch(usagePattern, 1));
|
||||
|
||||
// Run with deduplication
|
||||
output = MemoryUsageTest.run(true);
|
||||
output.shouldHaveExitValue(0);
|
||||
final long memoryUsageWithDedup = Long.parseLong(output.firstMatch(usagePattern, 1));
|
||||
|
||||
// Calculate expected memory usage with deduplication enabled. This calculation does
|
||||
// not take alignment and padding into account, so it's a conservative estimate.
|
||||
final long sizeOfChar = 2; // bytes
|
||||
final long bytesSaved = (LargeNumberOfStrings - 1) * (StringLength * sizeOfChar + unsafe.ARRAY_CHAR_BASE_OFFSET);
|
||||
final long memoryUsageWithDedupExpected = memoryUsageWithoutDedup - bytesSaved;
|
||||
|
||||
System.out.println("Memory usage summary:");
|
||||
System.out.println(" memoryUsageWithoutDedup: " + memoryUsageWithoutDedup);
|
||||
System.out.println(" memoryUsageWithDedup: " + memoryUsageWithDedup);
|
||||
System.out.println(" memoryUsageWithDedupExpected: " + memoryUsageWithDedupExpected);
|
||||
|
||||
if (memoryUsageWithDedup > memoryUsageWithDedupExpected) {
|
||||
throw new Exception("Unexpected memory usage, memoryUsageWithDedup should less or equal to memoryUsageWithDedupExpected");
|
||||
}
|
||||
}
|
||||
}
|
36
hotspot/test/gc/g1/TestStringDeduplicationYoungGC.java
Normal file
36
hotspot/test/gc/g1/TestStringDeduplicationYoungGC.java
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test TestStringDeduplicationYoungGC
|
||||
* @summary Test string deduplication during young GC
|
||||
* @bug 8029075
|
||||
* @key gc
|
||||
* @library /testlibrary
|
||||
*/
|
||||
|
||||
public class TestStringDeduplicationYoungGC {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TestStringDeduplicationTools.testYoungGC();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user