【调优】Java 调优学习笔记之字符串
一、紧凑字符串(Compact Strings)
-
核心机制:
Java 9 引入Compact Strings
特性(Java 11 默认开启-XX:+CompactStrings
),当字符串仅包含 Latin-1 字符(ASCII 范围,0-255) 时,使用 8 位字节数组(byte[]
) 存储;若包含非 Latin-1 字符,则仍使用 16 位字符数组(char[]
)。
⚠️ 注意:Java 8 及之前版本统一使用char[]
(每个字符占 2 字节,属于 16 位字符数组)。 -
内存优化效果:
对于纯 Latin-1 字符串场景,Java 11 的内存占用约为 Java 8 的 75%(字符存储字节数减半)。在常规 Java 应用中,字符串可能占据堆内存的 50% 以上,因此优化效果显著。
二、重复字符串处理
1. 字符串去重(String Deduplication)
-
开启条件:
- 自 Java 8 Update 20(JDK 8u20) 引入,需通过
-XX:+UseStringDeduplication
开启(默认false
)。 - 必须搭配 G1 垃圾收集器(需添加
-XX:+UseG1GC
),且仅对 老年代字符串 生效。
- 自 Java 8 Update 20(JDK 8u20) 引入,需通过
-
未默认开启的原因:
- GC 停顿时间增加:去重操作由 G1 回收线程在 新生代回收 或 混合回收阶段 同步执行,需扫描存活字符串并合并重复项,可能延长 STW(Stop The World)时间。
- 内存开销风险:若重复字符串较少,去重所需的 哈希表存储(记录唯一字符串) 和 元数据追踪 可能导致内存占用增加。
2. 字符串驻留(String.intern ())
-
机制说明:
String.intern()
会将字符串存入 字符串常量池(位于 JVM 堆外的元空间),而非原生内存。- Java 8 及之后,常量池的哈希表初始大小为 60013(可通过
-XX:StringTableSize
调整为质数,如 1009、32791)。 - 当存储的字符串数量超过哈希表容量时,会发生 哈希碰撞,碰撞的字符串以 链表 形式存储。
- Java 8 及之后,常量池的哈希表初始大小为 60013(可通过
-
性能隐患:
链表过长会导致intern()
查询时间复杂度退化为 O (n),建议:- 避免在循环中频繁调用
intern()
; - 对长字符串或大量唯一字符串,慎用
intern()
。
- 避免在循环中频繁调用
补充说明
-
分析工具:
jmap -histo:live <pid>
:分析堆中字符串实例数量及占比(java.lang.String
条目)。-XX:+PrintStringDeduplicationStatistics
:打印去重统计信息(如去重率、节省内存量)。
-
参数调优示例:
# Java 11 调优参数示例(开启紧凑字符串、G1 GC、字符串去重) java -XX:+CompactStrings -XX:+UseG1GC -XX:+UseStringDeduplication -jar app.jar