尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

String 底层原理与常量池全解析:byte []+coder、intern 陷阱、substring 内存泄漏,一篇讲透

String 底层原理与常量池全解析:byte []+coder、intern 陷阱、substring 内存泄漏,一篇讲透 文章收录专栏Java 核心原理全解源码・并发・面试实战引子String是 Java 里用得最多的类也是最容易 “灯下黑” 的类new String(abc)到底几个对象s1.intern() s1到底 true 还是 falseJDK 9 为什么把char[]换成byte[]JDK 7 的substring又藏着什么内存泄漏事故今天一次讲透 String 的底层存储、常量池机制、拼接原理、内存模型与经典方法变迁面试背熟、生产避坑两不误。一、String 的核心特性final 不可变final类、内部存储数组final、对外无 setter所有 “修改” 操作都返回全新对象。不可变 ⇒天生线程安全可多线程安全共享。二、底层存储char [] → byte [] coderJDK 8 及以前private final char value[]; // 每字符固定 2 字节纯 ASCII 也浪费JDK 9 起JEP 254 Compact Stringsprivate final byte[] value; // 存储字节 private final byte coder; // 0 LATIN11字节/字符1 UTF162字节/字符全在 Latin-1 范围 → LATIN1每字符 1 字节否则 UTF162 字节方法分StringLatin1/StringUTF16两套LATIN1 下哈希、indexOf 计算更快可用-XX:-CompactStrings关闭关闭后全走 UTF16主流场景平均省一半内存。⚠️length()返回char 数不是逻辑字符数emoji 等增补字符以代理对表示1 个逻辑字符占 2 个 char。数真实字符用s.codePointCount(0, s.length())—— 参数是起止索引(beginIndex, endIndex)且该方法需遍历、有代价日常用length()即可。三、字符串常量池3.1 本质与引用性质全局唯一的表HashTable 结构存字符串对象的引用。存的是强引用不是弱引用但 GC 时 JVM 会专门扫描 StringTable对除池外无其他 GC Roots 的 String 回收并清理对应条目 —— 这是特殊强引用 GC 清理策略不是弱引用语义。3.2 位置变迁必考版本位置JDK 6 及以前方法区永久代JDK 7移到堆JDK 8永久代→元空间池仍在堆调优-XX:StringTableSize设桶数JDK 7 前约 1009、JDK 8 起约 60013较新版本支持动态扩容、参数近于初始桶数-XX:PrintStringTableStatistics打印统计排查 intern 滥用。3.3 三个 “常量池” 别混淆Class 文件常量池每类一个、运行时常量池每类一个元空间、字符串常量池全局唯一堆。3.4 intern () 的两个陷阱陷阱一版本差异——JDK 6 intern 复制进永久代两份JDK 7 直接复用堆对象引用一份。陷阱二intern()s的成立条件—— 只有调用前池中不存在该字符串才 true只要池中已存在就必 falseString s1 new String(abc); // 字面量 abc 已入池 s1.intern() s1; // false返回池引用 // 前提程序执行前从未加载过字面量 abc String s2 new String(new char[]{a,b,c}); s2.intern() s2; // trueJDK7池原本没有四、new String (“abc”) 到底几个对象分版本场景JDK 6JDK 7池中无 “abc”2 个永久代池对象 堆对象两份独立实例2 个堆内池对象 堆内 new 对象池中已有 “abc”1 个堆对象1 个堆对象String s1 abc; // 1 个池对象 String s2 new String(abc); // 只创建 1 个堆对象 s1 s2 // false s1 abc // trueJDK 7 的 “池对象” 本身在堆上计数默认只算 String 实例若把底层数组也算作对象则数量更多。五、字符串拼接常量折叠 vs StringBuilder vs invokedynamic常量折叠abc编译期变abc零开销final String xa; xb也是编译期折叠入池高频变体JDK 8 变量拼接编译为new StringBuilder().append().toString()JDK 9JEP 280javac不再硬编码 StringBuilder改invokedynamicStringConcatFactory运行期策略选优精确预分配 / 预估容量 / BC_SBByteCode StringBuilder误区澄清JEP 280 只是编译期不再强制 StringBuilder运行期策略仍可能内部使用 StringBuilder变量拼接结果都是堆中新对象只有常量折叠才指向常量池反常识澄清JDK 9 的StringBuilder.toString()依然是复制数组源码注释 “Create a copy, don’t share the array”不存在 “共享 byte [] 零拷贝”—— 真正的优化来自预分配策略不是 toString循环是 O (n²)必须 StringBuilder。六、equals /hashCode 底层equals 完整流程引用 →instanceof→ 比coder编码 →比长度→ 逐字节对比hashCodes[0]*31^(n-1)...s[n-1]用 31奇素数 (h5)-h优化hash字段默认 0 懒计算空字符串恒为 0、不重复计算哈希恰为 0 的非空字符串会失去缓存收益。七、String 与 StringBuilder别再混用类可变性线程安全场景String不可变安全常量、keyStringBuilder可变不安全单线程拼接推荐StringBuffer可变安全多线程共享极少构造容量无参 16、String(str) len16、带参按指定扩容JDK 8 为旧*22ensureCapacity取 maxJDK 9 结合 coder 调整扩容要拷贝数组 → 预分配toString 结果不入池String s new StringBuilder().append(a).append(b).toString(); s ab // false s.equals(ab) // true八、常量池与 JVM 内存模型堆String 对象byte[]coder、静态变量JDK7、字符串常量池JDK7 元空间类元数据、运行时常量池每类一份 栈局部变量引用引用路径一个被 intern 的 String 有两条引用路径—— 栈上局部变量 和 StringTable 条目都指向堆中同一个对象StringTable 是堆中的哈希表不是独立 “内存层”静态变量在堆的 Class 镜像不在方法区池可被 GC 回收但别滥用 intern需可控去重用Map/Set自管。九、为什么 String 不可变—— 以及它的边界线程安全2. 哈希稳定3. 池复用省内存4. 参数安全5. 可缓存 hashCode。边界反射可setAccessible修改内部valueJDK 8 改char[]JDK 9 改byte[]且必须同步改coder否则错乱JDK 16 默认强封装拦截需--add-opens java.base/java.langALL-UNNAMED放行。仅研究用生产禁用。十、12 个生产坑点循环→ O(n²)new String(abc)双对象误用比内容应equals密码存储char[]可清零但堆 dump 仍见明文、只能降险不能杜绝叠加 PBKDF2/BCrypt、禁入日志intern 滥用 → StringTable 冲突、内存上涨误区JDK9 后内部还是char[]→ 是byte[]误区常量池在元空间 → 在堆误区静态变量在方法区 → 在堆StringBuilder 不预分配 → 频繁拷贝单线程用 StringBuffer → 无谓同步JDK6substring复用大 char [] offset/count小片段钉住大数组 → 内存泄漏JDK7u6 修复高频String.format→ 性能差。十一、经典方法变迁与进阶优化substring高频追问JDK 6 复用原始完整char[]、仅调 offset/count 标记区间大字符串截小片段 → 大数组无法回收 →内存泄漏JDK 7u6 起复制新数组牺牲性能换安全。G1 字符串去重JEP 192-XX:UseStringDeduplication仅 G1 生效ZGC/Shenandoah 不支持GC 时让内容相同的 String 共享底层数组与 intern 无关、不走 StringTable。总结记忆金句存储JDK 9byte[]coder常量池JDK 7 起在堆、存引用、强引用 GC 扫描清理对象数池中无 2JDK7 都在堆池中有 1interns仅 “池中原本没有” 时 true拼接常量折叠含 final/ JDK8 StringBuilder / JDK9 invokedynamictoString 不入池且始终复制substringJDK 7u6 由共享改复制比内容务必equals。
返回列表