1. 概述

Java中的字符串内部由char[]数组表示,每个char占用2字节,因为Java内部使用UTF-16编码。例如,当字符串仅包含英文字符时,每个字符的高8位都是0(ASCII字符只需1字节表示),这造成了明显的内存浪费。

统计显示,大多数字符实际只需8位(LATIN-1字符集)即可表示,而字符串通常占据JVM堆空间的很大比例。由于存储机制限制,字符串实例常占用实际所需空间的2倍

本文将探讨JDK6引入的压缩字符串选项和JDK9新增的紧凑字符串特性,两者都旨在优化JVM中字符串的内存消耗。

2. 压缩字符串 – Java 6

JDK 6 Update 21性能版引入了VM选项:

-XX:+UseCompressedStrings

启用后,字符串内部存储从char[]改为byte[],显著节省内存。但该选项在JDK7中被移除,主要原因是意外引发了性能问题——字符串构造函数只接受char[]参数,许多操作依赖字符数组,导致频繁解包操作。

3. 紧凑字符串 – Java 9

Java 9重新引入了紧凑字符串概念。核心机制是:

当字符串所有字符可用单字节(LATIN-1)表示时,内部使用byte[]存储;否则使用UTF-16双字节存储

关键问题:如何区分两种编码?解决方案是在String内部实现中新增coder字段

3.1. Java 9的String实现变化

传统实现:

private final char[] value;

新实现:

private final byte[] value;
private final byte coder; // 新增编码标识

coder取值定义:

static final byte LATIN1 = 0;
static final byte UTF16 = 1;

字符串操作会根据coder分发到不同实现:

public int indexOf(int ch, int fromIndex) {
    return isLatin1() 
      ? StringLatin1.indexOf(value, ch, fromIndex) 
      : StringUTF16.indexOf(value, ch, fromIndex);
}  

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

✅ 紧凑字符串默认启用,可通过-XX:-CompactStrings禁用。

3.2. coder的工作机制

长度计算示例:

public int length() {
    return value.length >> coder;
}
  • LATIN-1字符串(coder=0):长度 = value.length
  • UTF-16字符串(coder=1):长度 = value.length / 2

⚠️ 所有改动都在String内部实现中,对开发者完全透明。

4. 紧凑字符串 vs 压缩字符串

特性 压缩字符串 (Java 6) 紧凑字符串 (Java 9)
存储结构 byte[] byte[] + coder字段
性能问题 ❌ 频繁解包导致性能下降 ✅ 通过内联优化缓解
兼容性 ❌ 破坏现有操作 ✅ 完全向后兼容

Java 9通过以下优化提升性能:

  1. 关键方法使用内联函数
  2. JIT编译器生成的ASM代码优化

❗ 特殊情况:LATIN-1字符串的indexOf(String)调用内联方法,但indexOf(char)不会(UTF-16两者都支持),此问题将在后续版本修复。

4.1. 性能差异实测

测试代码(创建千万字符串并拼接):

long startTime = System.currentTimeMillis();
 
List<String> strings = IntStream.rangeClosed(1, 10_000_000)
  .mapToObj(Integer::toString) 
  .collect(toList());
 
long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
  "Generated " + strings.size() + " strings in " + totalTime + " ms.");

startTime = System.currentTimeMillis();
 
String appended = strings.stream()
  .limit(100_000)
  .reduce("", (l, r) -> l + r);
 
totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length() 
  + " in " + totalTime + " ms.");

启用紧凑字符串(默认)

Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.

禁用紧凑字符串(-XX:-CompactStrings

Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.

📊 测试显示:字符串创建速度提升约9%,拼接操作性能提升近90%!虽然这是特定场景的测试,但优化效果显著。

5. 总结

Java 9的紧凑字符串通过智能编码选择(LATIN-1/UTF-16)和内部实现优化,在保持完全兼容性的前提下:

  • 显著降低内存占用(尤其对纯ASCII文本)
  • 提升字符串操作性能
  • 避免了Java 6压缩字符串的踩坑问题

对于内存敏感型应用(如大数据处理、微服务),此优化堪称简单粗暴的福利。建议在生产环境中保持默认启用,除非遇到特殊兼容性问题。

完整代码示例见GitHub仓库


原始标题:Compact Strings in Java 9