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通过以下优化提升性能:
- 关键方法使用内联函数
- 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仓库。