String不可变,底层value数组为final,每次操作新建对象;StringBuilder可变,value数组非final,通过count和动态扩容复用同一对象。

String 和 StringBuilder 在内存布局上的核心差异,源于它们的设计目标和可变性:String 是不可变的(immutable),而 StringBuilder 是可变的(mutable)。这种语义差异直接决定了它们在堆内存中的对象结构、引用关系以及扩容行为。
String 的内存布局特点
String 对象内部持有一个 final char[](Java 8 及以前)或 final byte[](Java 9+,配合 coder 字段支持 Latin-1 编码优化),该数组一旦初始化就不可修改。每次字符串拼接(如 s1 + s2)都会创建新 String 实例,原数组仍被保留(除非无引用可达,等待 GC)。
- 底层字符数组被声明为 final,JVM 可对其做逃逸分析与栈上分配优化(但实际是否栈分配取决于运行时判定)
- String 对象本身轻量,除字符数组外仅含少量字段(如 offset、count 已在 Java 9 中移除;现主要为 value[]、hash、coder)
- 字符串常量池(String Pool)中存储的是对堆中 String 实例的引用,intern() 可复用已有实例,避免重复对象
StringBuilder 的内存布局特点
StringBuilder 是一个包装类,内部维护一个可动态扩展的 char[](Java 9+ 改为 byte[] + coder,逻辑同 String),并通过 count 字段记录当前有效字符长度。它不共享底层数组,也不参与字符串常量池。
- 初始容量默认为 16,当 append 超出当前容量时,会触发数组扩容(通常为
oldCapacity * 2 + 2),并新建数组、复制内容 - 其 value 数组不是 final,因此可被多次复用和覆盖,避免频繁创建中间 String 对象
- StringBuilder 实例本身包含额外状态字段:value[]、count、capacity(隐式由 value.length 表达)、toString() 缓存(无)——它不缓存结果,每次 toString() 都新建 String
关键对比点:堆中对象图差异
假设执行 StringBuilder sb = new StringBuilder("ab").append("cd").append("ef");:
立即学习“Java免费学习笔记(深入)”;
- 堆中只存在 1 个 StringBuilder 实例,其 value 数组从长度 16 → 16("ab")→ 16("abcd")→ 可能扩容至 34("abcdef")
- 若最后调用
sb.toString(),才生成 1 个新的 String 实例,其 value 数组是 StringBuilder 中 value 的副本(非共享),且内容截取自 [0, count) - 而等价的
"ab" + "cd" + "ef"(未启用 JEP 280 字符串拼接优化时)会在编译期或运行期生成多个临时 String 和 StringBuilder(或直接字节码内联),最终产生至少 3 个中间 String 对象
实际影响:何时关注内存布局?
日常开发中无需手动干预内存布局,但在以下场景需有意识:
- 高频拼接循环中反复使用
+=String,会持续分配短命对象,增加 GC 压力 —— 应改用 StringBuilder - 构造超长字符串后长期持有 StringBuilder,其内部数组可能远大于实际需要(例如 append 10MB 数据后只保留前 1KB),可调用
sb.setLength(1024)或new String(sb)截断释放冗余空间 - 多线程环境必须用 StringBuffer(同步版)或外加锁,StringBuilder 本身无同步,共享时存在数据竞争风险


















