Compact Strings (byte[] + coder flag) & String Deduplication
- Last updated
- Prerequisites:
- Java String basics
- Understanding of JVM heap memory
- java
- compact-strings
- string
- deduplication
- memory
- gc
Read at your depth
The practical view
Before JDK 9, String stored char[] (2 bytes per char, UTF-16 always). Compact Strings (JDK 9+) store byte[] plus a coder flag (LATIN1 or UTF16): if all characters fit in Latin-1, the string uses 1 byte per char — halving memory for most strings. This is automatic; String.length() still returns the character count. String Deduplication (-XX:+UseStringDeduplication, G1) is a GC feature that identifies duplicate String objects (same content) and makes them share the same backing char[]/byte[], saving heap when many strings repeat (e.g. JSON keys, log fields).
The same idea in other frameworks
N/A — JVM runtime feature.N/A — JVM feature.N/A — JVM feature.N/A — JVM feature.Legacy vs modern
char[]-based Strings vs compact byte[]+coder Strings
The old layout costs 2 bytes per char always; compact strings use 1 byte per char for Latin-1 content, halving memory for typical data.
// ❌ OLD: String was backed by char[] — every char = 2 bytes, alwaysString s = "hello"; // ✅ NEW: byte[] with LATIN1 coder → 5 bytesInterview gotchas
Context
This tests the layout knowledge — interviewers want the byte[]+coder design and how the JVM keeps the public API stable while optimizing internals.
The mechanical answer
Compact strings store the characters in a byte[] (not char[]) plus a byte coder flag: LATIN1 (0) when every char fits in one byte, UTF16 (1) otherwise. The length() is computed from the byte[] length shifted by the coder (for LATIN1, /1; for UTF16, /2), so the public API is unchanged. Operations that depend on indexing — charAt, substring, indexOf, equals, hashCode, compareTo — consult the coder and use optimized Latin-1 paths (the C2 compiler emits intrinsics keyed on the coder). When a string transitions to UTF16 (a non-Latin char is appended), the array is expanded to 2 bytes/char. This halves memory for typical ASCII-heavy data and improves cache locality, with negligible complexity cost because the coder check is a single branch.
Trap
The naive answer is 'compact strings always use one byte per char'. Only Latin-1-compatible strings do; non-Latin content flips to UTF16. Another trap: assuming charAt is now O(1) byte indexing — it must decode based on the coder, but remains O(1); code that assumed a char[] layout or used reflection into the value field breaks. Candidates who describe the coder-aware dispatch show real understanding.
Context
This probes the GC-time optimization — interviewers want the dedup mechanism and the honest scope (only helps with many equal strings; has overhead).
The mechanical answer
String Deduplication is a GC feature (G1, and some other collectors) that runs during collection: the GC hashes the backing value array of candidate String objects, looks up a dedup table, and if an identical array already exists, it re-points the duplicate String's value reference to the existing array, making the duplicate's array garbage. It only triggers on strings that survive past a young-gen age threshold (default 3), avoiding the cost of deduping short-lived strings. The win is workload-dependent: apps with many repeated strings (JSON keys, enum names, log messages, identifier-heavy data) can see significant heap savings; apps with mostly unique strings gain nothing and pay the hash/table overhead. It's disabled by default and tuned via -XX:StringDeduplicationAgeThreshold and the dedup table size.
Trap
The naive answer is 'deduplication dedupes all equal strings automatically'. It is GC-time, age-thresholded, and opt-in; String interning (intern()) is a different, explicit mechanism. Another trap: assuming it saves memory for short-lived strings — dedup only considers older survivors. Candidates who mention the age threshold and the workload-dependence show real understanding.