What is String Deduplication in G1 GC
In typical enterprise applications, millions of duplicate strings reside in memory (e.g., status flags "ACTIVE", "SUCCESS", user roles, JSON keys, HTTP headers).
🟢 Junior Level
String Deduplication is an automated background garbage collector optimization that identifies String objects with identical character sequences in the Java heap and redirects their internal references to a single shared backing byte array.
In a Nutshell (30 Seconds)
In typical enterprise applications, millions of duplicate strings reside in memory (e.g., status flags "ACTIVE", "SUCCESS", user roles, JSON keys, HTTP headers).
- Without deduplication, every
Stringinstance retains its own distinct copy of thebyte[] valuearray. - With deduplication enabled, the JVM locates duplicates and frees redundant byte arrays, leaving a single shared array instance for all identical strings.
- This typically yields 10% to 25% heap memory savings without modifying a single line of Java application code.
# Enabling String Deduplication on JVM launch:
java -XX:+UseG1GC -XX:+UseStringDeduplication -jar app.jar
Key Differences: String Deduplication vs. String Pool (intern())
| Parameter | String Pool (intern()) |
String Deduplication (-XX:+UseStringDeduplication) |
|---|---|---|
| What is Unified | Entire java.lang.String object instances |
Only the internal backing byte[] value arrays |
Result of s1 == s2 |
true (object references become identical) |
false (object references remain distinct!) |
| Management | Manual invocation via str.intern() in application code |
Fully automated by GC background worker threads via JVM flag |
| OutOfMemoryError Risk | ⚠️ High risk if interning unbounded dynamic values | ❌ Zero risk (GC manages lifecycle and weak references) |
🟡 Middle Level
Lifecycle of String Deduplication
Deduplication does not execute synchronously upon string creation (which would paralyze CPU throughput). The process is multi-staged:
- Allocation in Eden: The string is created with its own private
byte[] valuearray. - Filtering by Age (
Age Threshold): The vast majority of strings die in the Eden space within milliseconds (Weak Generational Hypothesis). The deduplicator ignores young strings until they survive multiple garbage collection cycles (default threshold: 3 GC cycles, controlled via-XX:StringDeduplicationAgeThreshold=3). - Queue Registration: Candidate strings surviving Young GC evacuation passes are registered into a native Deduplication Queue.
- Asynchronous Processing by
StringDedupThread:- Computes a 32-bit hash of the
byte[] valuearray. - Performs a lookup in the native off-heap hash table (
Deduplication Tablein C-Heap). - If a matching hash is found, executes a byte-by-byte comparison to verify character identity and rule out hash collisions.
- If identical, performs an atomic CAS (Compare-And-Swap) operation to redirect the second string’s
valuepointer to the first string’s array. - The unreferenced duplicate array is reclaimed by the garbage collector in the subsequent GC cycle.
- Computes a 32-bit hash of the
Before Deduplication:
String A [Header] ──value──► byte[] [ 'H', 'e', 'l', 'l', 'o' ] (24 bytes)
String B [Header] ──value──► byte[] [ 'H', 'e', 'l', 'l', 'o' ] (24 bytes)
After Deduplication:
String A [Header] ──value──► byte[] [ 'H', 'e', 'l', 'l', 'o' ] (24 bytes)
▲
String B [Header] ──value──────────┘
(The second byte[] array is reclaimed during the next GC cycle!)
Garbage Collector Support: Evolution to Java 21
- Java 8 Update 20 through Java 17: String Deduplication was historically implemented exclusively within G1 GC (and Shenandoah GC). Parallel GC, Serial GC, and ZGC did not support it.
- Java 18 and Later (including Java 21 LTS): Under JDK-8267186, OpenJDK unified String Deduplication across the entire JVM runtime. The
-XX:+UseStringDeduplicationflag is now fully supported across G1, ZGC, Parallel GC, and Serial GC.
🔴 Senior Level
HotSpot Architecture: Native Table and CAS Concurrency
In OpenJDK HotSpot, String Deduplication is implemented in C++:
- Native Off-Heap Deduplication Table (
StringDedupTable): Resides in C-Heap memory outside the Java Heap, consuming roughly 15 to 50 MB of native memory. Entries are referenced via weak handles (OopHandle), ensuring that table entries never prevent objects from being garbage-collected. - Atomic CAS Pointer Swap:
Although
String.valueis declared@Stable final, the JVM swaps array references using nativeAtomic::cmpxchg(Compare-And-Swap) instructions with StoreLoad/Release memory fences. Any application thread concurrently invokingcharAt()orgetBytes()observes either the previous array or the new array. Because the arrays contain identical bytes, the operation is 100% thread-safe and completely transparent to application logic. - Encoding Constraints (
codercheck): Deduplication merges arrays only if they share identical lengths and the samecoderflag. If one"Hello"string is Latin-1 (5 bytes) and another"Hello"was constructed as UTF-16 (10 bytes), they cannot be deduplicated.
Performance Analysis: CPU vs. Memory Trade-Off
Enabling -XX:+UseStringDeduplication carries an operational cost:
- CPU Overhead: The background deduplication worker thread consumes between 1.5% and 4% of total CPU capacity for hashing and byte comparisons.
- When Deduplication Degrades Performance: In systems dominated by unique strings (UUIDs, cryptographic tokens, high-cardinality timestamps), the deduplicator burns CPU cycles computing hashes without finding duplicates, resulting in 0% memory savings.
- When Deduplication Shines: REST API gateways, log aggregators, message brokers, and ETL pipelines where thousands of JSON payloads contain repetitive dictionary keys, status enums, and country codes.
4 Tricky Questions
1. Does the expression s1 == s2 evaluate to true after G1 GC successfully deduplicates two identical strings?
Answer:
No, s1 == s2 remains false.
String Deduplication operates exclusively on the internal byte[] value array. The garbage collector shares only the backing byte buffer. The java.lang.String object wrappers (including their object headers, identity hashcodes, and distinct heap memory addresses) remain completely separate instances.
The reference equality check s1 == s2 compares the memory addresses of the String wrapper objects, evaluating to false. Value equality s1.equals(s2) evaluates to true.
2. Is String Deduplication supported in ZGC in modern Java (Java 18–21)?
Answer:
Yes, it is fully supported.
While String Deduplication was originally restricted to G1 GC (and Shenandoah) in Java 8u20 through Java 17, OpenJDK refactored the mechanism in Java 18 (JDK-8267186) into a GC-agnostic platform feature. In Java 18, 21 LTS, and later releases, passing -XX:+UseStringDeduplication works seamlessly with ZGC, G1 GC, Parallel GC, and Serial GC.
3. Why does -XX:StringDeduplicationAgeThreshold=3 exist, and why not deduplicate strings immediately in Eden?
Answer: Under the Weak Generational Hypothesis, over 95% of strings in Java enterprise applications are transient: they are allocated for JSON parsing, logging, or method parameters, and collected within milliseconds during Minor GC. If the JVM attempted to deduplicate strings immediately in Eden:
- The CPU would waste up to 30–40% of its processing power hashing and checking objects that will disappear moments later.
- Setting an Age Threshold of 3 GC cycles ensures that only tenured, long-lived strings surviving multiple collection passes are considered for deduplication, maximizing memory reclamation while keeping CPU overhead below 3%.
4. How can you verify String Deduplication efficiency in a live production environment without restarting the JVM?
Answer:
- Via
jcmdDiagnostic Command:jcmd <pid> GC.string_deduplication -dumpThis outputs comprehensive operational statistics: total strings inspected, number of duplicates detected, megabytes of heap reclaimed, and the deduplication success percentage.
- Via Unified GC Logging:
Enable logging at JVM startup:
-Xlog:gc+stringdedup*=info:stdout:time,uptime,level,tagsEvery deduplication run will output an event:[GC concurrent-string-deduplication, 12.4ms, 150000 strings, 4.2MB (25.3%) saved].
🎯 Interview Cheat Sheet
30-Second Summary
“String Deduplication (
-XX:+UseStringDeduplication) is a GC optimization that runs in a background thread, identifying strings with identical text that have survived multiple GC passes and merging their internalbyte[] valuearrays. UnlikeString.intern(), it does not merge theStringobjects themselves (==remainsfalse), requires zero application code changes, and eliminates OutOfMemoryError risks. In enterprise services, it typically saves 10–25% of heap memory for a 2–4% CPU trade-off. Since Java 18, it is supported across G1, ZGC, Parallel, and Serial GC.”
Key Takeaways for Senior Interviews
- Array-Level Optimization: Merges backing
byte[] valuearrays;Stringwrappers remain distinct (==isfalse). - Age Threshold: Defaults to 3 GC cycles (
-XX:StringDeduplicationAgeThreshold=3), protecting Eden from wasteful hashing. - Atomic CAS Update: Thread-safe pointer replacement invisible to concurrent reading threads.
- Java 18+ Unification: Supported across G1, ZGC, Parallel, and Serial collectors (JDK-8267186).
- CPU Trade-off: Consumes 2–4% CPU; avoid in services dominated by unique strings (UUIDs, cryptographic tokens).
- Production Observability: Inspect using
jcmd <pid> GC.string_deduplicationand-Xlog:gc+stringdedup*=info.
Red Flags to Avoid
- ❌ Claiming that String Deduplication makes
s1 == s2returntrue(classic mistake; objects remain distinct). - ❌ Confusing String Deduplication with
String.intern()(interning unifies objects and mutates StringTable). - ❌ Assuming deduplication runs immediately on every new string in Eden.
- ❌ Claiming that String Deduplication only works in G1 GC (it works with ZGC and Parallel GC in Java 18+).
Related Questions
- How String Pool Works — String pool comparison
- When to Use intern() — Manual interning tradeoffs
- What are Compact Strings in Java 9+ — Internal
byte[]architecture - How to Find Out How Much Memory a String Occupies — Heap sizing calculations