substring() 在旧版 JDK 中会共享原字符串的字符数组,导致大字符串无法被回收;JDK 7u6 后改为复制新字符数组,但处理超大字符串时仍可能因内存不足触发 OutOfMemoryError。
`substring()` 在旧版 jdk 中会共享原字符串的字符数组,导致大字符串无法被回收;jdk 7u6 后改为复制新字符数组,但处理超大字符串时仍可能因内存不足触发 `outofmemoryerror`。
在 Java 中,String.substring(int beginIndex) 方法看似轻量,实则在不同 JDK 版本中存在显著的内存行为差异,不当使用可能引发 java.lang.OutOfMemoryError: Java heap space。
? 内存行为演进(关键背景)
JDK 6 及之前:substring() 返回的新字符串不复制底层 char[] 数组,而是通过偏移量(offset)和长度(count)引用原数组。这意味着即使只取一个字符(如 s.substring(1000000, 1000001)),整个原始巨型字符串的 char[] 仍被强引用,无法被 GC 回收——造成“内存泄漏式”占用。
JDK 7u6 及之后(含 JDK 8/9/11/17+):substring() 总是创建新的 char[](或 byte[],JDK 9+ 使用压缩字符串)并拷贝对应字符。此举避免了长生命周期小子串阻塞大字符串回收,但代价是:每次调用都需分配新内存空间。
⚠️ 内存溢出真实场景示例
假设 JVM 堆最大为 6 GB,且你加载了一个约 4 GB 的超长字符串(例如解析超大日志文件或序列化数据):
立即学习“Java免费学习笔记(深入)”;
String huge = Files.readString(Paths.get("huge.log")); // 占用 ~4GB 堆内存
String tail = huge.substring(1); // 需要再分配 ~4GB 新 char[](JDK 7u6+)此时 JVM 尝试分配近 4 GB 连续堆内存,而剩余可用空间不足,立即抛出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
? 注意:现代应用极少直接将 GB 级文本加载为 String(推荐流式处理、CharBuffer 或 InputStreamReader),但该问题在大数据解析、内存映射文件转字符串等边缘场景仍具现实意义。
✅ 安全实践建议
- 避免对超大字符串调用 substring():优先使用 String.valueOf(char[] data, int offset, int count) 或基于 CharSequence 的流式切片(如 java.nio.CharBuffer.wrap(array, offset, length))。
- 显式控制生命周期:若必须截取,确保原始大字符串尽早置为 null(JDK 7u6+ 下可加速其回收,但新子串本身仍需独立内存)。
- 监控与调优:使用 -XX:+PrintGCDetails 和堆分析工具(如 VisualVM、Eclipse MAT)识别 char[]/byte[] 内存热点。
- 替代方案考虑:对超大文本处理,选用 java.nio.file.Files.lines() 流式读取,或 Apache Commons StringUtils.substring()(无内存优势,但语义更清晰)。
总之,substring() 本身不是“内存炸弹”,但在极端数据规模与有限堆配置下,其内存分配模型可能成为压垮 JVM 的最后一根稻草——理解其底层机制,方能写出健壮、可伸缩的字符串处理逻辑。


















