StringTable (String Constant Pool)
String Fundamentals
Strings in Java can be created in two primary ways:
String text1 = "java"; // Literal declaration
String text2 = new String("code"); // Object instantiation
Key characteristics of String class:
- Declared as final, preventing inheritance
- Implements Serializable for object serialization
- Implements Comparable for string comparison
- JDK 8 and earlier used
final char value[]for storage - JDK 9+ uses
byte[]with encoding flag for storage optimization
JDK 9 Storage Optimization
Rationale for byte[] storage:
- Previous char[] implemantation used 2 bytes per character
- Most applications use Latin characters requiring only 1 byte
- UTF-16 char[] resulted in 50% wasted space for Latin text
- New implementation uses byte[] with encoding identifier
- ISO-8859-1/Latin-1 uses 1 byte, UTF-8 uses 2 bytes
- Related classes (StringBuffer, StringBuilder) also updated
// Pre-JDK9
private final char value[];
// JDK9+
private final byte[] value;
String Immutability
Strings are immutable character sequences:
- Reassignment creates new memory allocation
- Concatenation requires new memory region
- replace() operations generate new strings
Reassignment Example:
public void demonstrateReassignment() {
String first = "data";
String second = "data";
first = "modified";
System.out.println(first == second); // false
System.out.println(first); // modified
System.out.println(second); // data
}
Concatenation Example:
public void demonstrateConcatenation() {
String base = "base";
String copy = base;
copy += "append";
System.out.println(copy); // baseappend
System.out.println(base); // base
}
Replacement Example:
public void demonstrateReplacement() {
String original = "text";
String modified = original.replace('t', 'b');
System.out.println(original); // text
System.out.println(modified); // bexb
}
StringTable Implementation
StringTable is a fixed-size Hashtable that prevents duplicate string storage:
- Default size: 1009 (JDK6), 60013 (JDK7+)
- Configurable via -XX:StringTableSize parameter
- Hash collisions degrade intern() performance
Performence Test:
public class StringTablePerformance {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("strings.txt"));
long startTime = System.currentTimeMillis();
String line;
while((line = reader.readLine()) != null) {
line.intern(); // Add to StringTable if not present
}
long endTime = System.currentTimeMillis();
System.out.println("Duration: " + (endTime - startTime));
}
}
Results: StringTableSize=1009 → 143ms, StringTableSize=100009 → 47ms
Memory Allocation Evolution
String constant pool locations:
- JDK6: Permanent Generation
- JDK7+: Java Heap
Relocation Benefits:
- Permanent Generation had limited space
- Infrequent Permanent Generation garbage collection
- Heap allows better memory management
- Reduced OutOfMemoryError occurrences
Memory Test:
public class StringMemoryTest {
public static void main(String[] args) {
Set<String> stringSet = new HashSet<>();
short counter = 0;
while(true) {
stringSet.add(String.valueOf(counter++).intern());
}
}
}
Output confirms strings reside in heap:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
String Concatenation Mechanics
Compile-time Optimization:
public void compileTimeOptimization() {
String combined = "x" + "y" + "z"; // Optimized to "xyz"
String literal = "xyz";
System.out.println(combined == literal); // true
}
Runtime Concatenation:
public void runtimeConcatenation() {
String part1 = "java";
String part2 = "script";
String result1 = "javascript";
String result2 = "java" + "script"; // Compile-time
String result3 = part1 + "script"; // StringBuilder
String result4 = "java" + part2; // StringBuilder
String result5 = part1 + part2; // StringBuilder
System.out.println(result1 == result2); // true
System.out.println(result1 == result3); // false
System.out.println(result1 == result4); // false
System.out.println(result1 == result5); // false
}
Final Variable Optimization:
public void finalOptimization() {
final String alpha = "a";
final String beta = "b";
String gamma = "ab";
String delta = alpha + beta;
System.out.println(gamma == delta); // true
}
Performance Comparison:
public void performanceTest() {
long start = System.currentTimeMillis();
//methodWithConcatenation(100000); // ~4000ms
methodWithBuilder(100000); // ~7ms
long end = System.currentTimeMillis();
System.out.println("Duration: " + (end - start));
}
void methodWithConcatenation(int iterations) {
String result = "";
for(int i = 0; i < iterations; i++) {
result = result + "x"; // Creates multiple objects
}
}
void methodWithBuilder(int iterations) {
StringBuilder builder = new StringBuilder();
for(int i = 0; i < iterations; i++) {
builder.append("x"); // Single object
}
}
String Interning
intern() Method:
public native String intern();
intern() ensures string uniqueness in constant pool:
- Checks if string exists in pool
- Returns existing referance if found
- Adds to pool and returns reference if not found
Object Creation Analysis:
// Creates 2 objects: heap String + constant pool "value"
String str = new String("value");
// Creates multiple objects
String complex = new String("a") + new String("b");
// Objects: StringBuilder, 2x String("a"), constant "a",
// 2x String("b"), constant "b", String("ab")
Advanced Interning Examples:
public class AdvancedInterning {
public static void main(String[] args) {
String s1 = new String("1");
s1.intern(); // Pool already contains "1"
String s2 = "1";
System.out.println(s1 == s2); // false
String s3 = new String("1") + new String("1");
s3.intern(); // JDK7+: pool references heap object
String s4 = "11";
System.out.println(s3 == s4); // JDK6: false, JDK7+: true
}
}
Memory Efficiency with intern()
Space Optimization:
public class InternEfficiency {
static final int MAX_ELEMENTS = 10000000;
static final String[] array = new String[MAX_ELEMENTS];
public static void main(String[] args) {
Integer[] numbers = {1,2,3,4,5,6,7,8,9,10};
for(int i = 0; i < MAX_ELEMENTS; i++) {
// Without intern: maintains separate heap objects
// array[i] = new String(String.valueOf(numbers[i % numbers.length]));
// With intern: shares constant pool references
array[i] = new String(String.valueOf(numbers[i % numbers.length])).intern();
}
}
}
StringTable Garbage Collection
GC Demonstration:
public class StringTableGC {
public static void main(String[] args) {
for(int i = 0; i < 100000; i++) {
String.valueOf(i).intern(); // Some will be GC'd
}
}
}
G1 String Deduplication
Background:
- Strings often comprise 25% of heap data
- Approximately 13.5% are duplicate strings
- Average string length: 45 characters
Deduplication Process:
- GC identifies candidate strings
- References added to processing queue
- Deduplication thread processes queue
- Hashtable tracks unique char arrays
- Duplicate strings reference same char array
- Original arrays become eligible for GC
Configuration Options:
- UseStringDeduplication(bool): Enable/disable feature
- PrintStringDeduplicationStatistics(bool): Detailed metrics
- stringDeduplicationAgeThreshold(uintx): Candidate age threshold