8029075: String deduplication in G1
authorpliden
Tue, 18 Mar 2014 19:07:22 +0100
changeset 23472 35e93890ed88
parent 23471 ec9427262f0a
child 23473 d94be023b00d
8029075: String deduplication in G1 Summary: Implementation of JEP 192, http://openjdk.java.net/jeps/192 Reviewed-by: brutisso, tschatzl, coleenp
hotspot/make/excludeSrc.make
hotspot/src/share/vm/classfile/javaClasses.hpp
hotspot/src/share/vm/classfile/symbolTable.cpp
hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp
hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp
hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.hpp
hotspot/src/share/vm/gc_implementation/g1/g1MarkSweep.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.hpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.hpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.hpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.hpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupThread.cpp
hotspot/src/share/vm/gc_implementation/g1/g1StringDedupThread.hpp
hotspot/src/share/vm/gc_implementation/shared/markSweep.inline.hpp
hotspot/src/share/vm/runtime/arguments.cpp
hotspot/src/share/vm/runtime/globals.hpp
hotspot/src/share/vm/runtime/mutexLocker.cpp
hotspot/src/share/vm/runtime/mutexLocker.hpp
hotspot/test/gc/g1/TestGCLogMessages.java
hotspot/test/gc/g1/TestStringDeduplicationAgeThreshold.java
hotspot/test/gc/g1/TestStringDeduplicationFullGC.java
hotspot/test/gc/g1/TestStringDeduplicationInterned.java
hotspot/test/gc/g1/TestStringDeduplicationMemoryUsage.java
hotspot/test/gc/g1/TestStringDeduplicationPrintOptions.java
hotspot/test/gc/g1/TestStringDeduplicationTableRehash.java
hotspot/test/gc/g1/TestStringDeduplicationTableResize.java
hotspot/test/gc/g1/TestStringDeduplicationTools.java
hotspot/test/gc/g1/TestStringDeduplicationYoungGC.java
--- a/hotspot/make/excludeSrc.make	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/make/excludeSrc.make	Tue Mar 18 19:07:22 2014 +0100
@@ -87,7 +87,8 @@
 	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 \
--- a/hotspot/src/share/vm/classfile/javaClasses.hpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/classfile/javaClasses.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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 @@
 
   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 @@
     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");
--- a/hotspot/src/share/vm/classfile/symbolTable.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/classfile/symbolTable.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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 @@
     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);
--- a/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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 @@
   // values in the heap have been properly initialized.
   _g1mm = new G1MonitoringSupport(this);
 
+  G1StringDedup::initialize();
+
   return JNI_OK;
 }
 
@@ -3456,6 +3459,11 @@
     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 @@
     }
     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 @@
   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 @@
   }
   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 @@
       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 @@
                            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 @@
     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 @@
     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 @@
 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);
 
--- a/hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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 @@
   _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 @@
   _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 @@
     // 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 @@
   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);
--- a/hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.hpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -137,6 +137,10 @@
   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 @@
     _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;
   }
--- a/hotspot/src/share/vm/gc_implementation/g1/g1MarkSweep.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1MarkSweep.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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 @@
   // 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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedup.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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");
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupQueue.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupStat.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupTable.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupThread.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1StringDedupThread.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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
--- a/hotspot/src/share/vm/gc_implementation/shared/markSweep.inline.hpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/gc_implementation/shared/markSweep.inline.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
--- a/hotspot/src/share/vm/runtime/arguments.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/runtime/arguments.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -2246,6 +2246,8 @@
                                        "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");
--- a/hotspot/src/share/vm/runtime/globals.hpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/runtime/globals.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -3840,6 +3840,22 @@
   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")                      \
                                                                             \
--- a/hotspot/src/share/vm/runtime/mutexLocker.cpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/runtime/mutexLocker.cpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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*   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 @@
     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 );
--- a/hotspot/src/share/vm/runtime/mutexLocker.hpp	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/src/share/vm/runtime/mutexLocker.hpp	Tue Mar 18 19:07:22 2014 +0100
@@ -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*   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
--- a/hotspot/test/gc/g1/TestGCLogMessages.java	Fri Feb 28 15:27:09 2014 +0100
+++ b/hotspot/test/gc/g1/TestGCLogMessages.java	Tue Mar 18 19:07:22 2014 +0100
@@ -49,11 +49,13 @@
 
     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 @@
 
     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 @@
 
     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");
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationAgeThreshold.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationFullGC.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationInterned.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationMemoryUsage.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationPrintOptions.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationTableRehash.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationTableResize.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationTools.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/gc/g1/TestStringDeduplicationYoungGC.java	Tue Mar 18 19:07:22 2014 +0100
@@ -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();
+    }
+}