Skip to content
Java 语言基础与运行时 · 第 1 篇 / 共 2 篇
领域编程与框架
专题Java 专题
当前序列Java 语言基础与运行时
阅读位置第 1 篇 / 共 2 篇当前专题第 1 个序列 / 共 10 个序列

String 到底能有多长:堆内字符串、常量池字符串和编译期限制

很多人会问一个看起来简单、但实际很容易答偏的问题:

Java 里的 String 最长能有多长?

如果只回答“受 int 限制”,这个答案不算错,但不够完整。因为不同场景下,限制来源并不一样。

先说结论

可以先把结论记成三句话:

  • 普通运行时创建的字符串,长度本质上受数组索引和可用堆内存限制
  • 直接写进 class 常量池的字符串字面量,编译期还会受到 CONSTANT_Utf8_info 长度限制
  • 所以“字符串最大长度”不是一个单一数字,而是跟创建方式有关

一、为什么常说 String 的长度受 int 限制

String 底层最终还是要依赖数组来存储字符内容。

在现代 JDK 中,底层实现不再简单等同于 char[],但数组长度依然由 int 表示,所以理论上上限依然受 int 范围约束。

这意味着:

  • 理论索引上限接近 Integer.MAX_VALUE
  • 但实际可用长度远小于理论值

因为真实运行时还会受到这些因素限制:

  • 对象头和数组本身的额外开销
  • JVM 最大堆大小
  • 连续内存分配是否成功
  • GC 压力和 OOM 风险

所以在工程里,几乎不会讨论“4GB 字符串能不能创建”,而是更关心:

  • 这个字符串为什么会大到离谱
  • 是否应该改成流式处理
  • 是否应该改成分块读取

二、为什么字符串字面量还有 65534 这个限制

如果你写的是这种代码:

java
String value = "......超长字符串字面量......";

这个字符串不是简单地“运行时 new 出来”,而是会先进入 class 文件常量池。

这里就会碰到 class 文件格式本身的限制:

  • 字符串字面量会关联到 CONSTANT_Utf8_info
  • 这个结构的长度字段是两个字节的无符号整数
  • 理论上最大值是 65535

但 JVM 规范相关实现里,超长字符串字面量通常会在编译期直接报错,所以经验上常说:

  • 单个字符串字面量长度上限大约是 65534

也就是说,这个限制不是 String 这个类自己的限制,而是 class 文件常量池编码格式带来的限制。

三、为什么运行时拼接又可能超过这个限制

下面两种写法,限制来源并不一样。

直接写超长字面量

java
String value = "abc......";

这里受 class 常量池限制。

运行时拼接

java
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    builder.append("abcdefg");
}
String value = builder.toString();

这里字符串是在运行时逐步构建出来的,不需要把完整结果作为一个超长字面量写进 class 文件,因此不会先撞上常量池的 65534 限制。

这时更主要的限制就变成:

  • 堆内存够不够
  • 数组能不能成功扩容
  • GC 能不能承受

四、常量池、intern 和“字符串都在常量池”不是一回事

这也是一个常见误区。

更准确地说:

  • 字符串字面量会进入运行时常量池体系
  • 普通 new String() 创建的对象默认在堆上
  • 调用 intern() 才可能把它关联到字符串常量池中的规范化实例

所以不要把下面几件事混为一谈:

  • Java 代码里的字符串字面量
  • 堆上的普通字符串对象
  • intern() 之后的池化行为

五、实际开发里更应该关注什么

对业务开发来说,这个问题真正有价值的地方,不是去记某个极限数字,而是知道下面这些判断:

  • 超大字符串通常是设计问题,不是能力问题
  • 文件、日志、导出数据不要优先用一个超大字符串整体承载
  • JSON、SQL、HTML 拼接过大时,要优先考虑流式写出或分段处理
  • 如果编译期就报“常量字符串过长”,先怀疑是不是把大段内容硬编码进源码了

一句话总结

String 的长度限制要分场景看:

  • 运行时字符串,主要受数组索引和堆内存限制
  • 字符串字面量,还会额外受 class 常量池的编码长度限制

所以“Java 字符串最大长度是多少”这件事,本身就不该只用一个数字回答。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读Java 类加载的触发时机适合先把 String、类加载这类基础运行时问题看顺,再进入集合和并发。Java 专题 · Java 语言基础与运行时同专题其他序列 · Java 类加载与运行时细节对象是在堆上分配还是在栈上逃逸适合把类加载、双亲委派和运行时对象分配放到一起系统看。Java 专题 · Java 类加载与运行时细节同专题其他序列 · Java 类加载与运行时细节双亲委派模型到底解决了什么适合把类加载、双亲委派和运行时对象分配放到一起系统看。Java 专题 · Java 类加载与运行时细节同专题其他序列 · Java 线程池与异步编排线程池参数怎么配适合把线程池参数、拒绝策略、CompletableFuture 和 ForkJoin 放在一起连续看。Java 专题 · Java 线程池与异步编排跨专题关联 · 同场景:基础学习@Conditional 系列注解怎么配合使用适合把自动装配、条件装配、Profile 和循环依赖放回 Spring Boot 启动过程里理解。Spring 专题 · Spring Boot 启动、装配与配置跨专题关联 · 同场景:基础学习保留策略和日志压缩适合哪些场景适合把副本机制、acks、批量压缩和日志组织放在一条 Kafka 投递主线上理解。Kafka 专题 · Kafka 投递链路与日志存储机制
继续阅读Java 语言基础与运行时当前序列第 1 篇 / 共 2 篇当前专题第 1 个序列 / 共 10 个序列
往前看
这一侧已经是当前阅读路径的起点。
往后看
下一篇Java 类加载的触发时机继续当前序列下一章下一序列Java 集合与容器从第 1 篇开始:Java 集合怎么选

把零散经验整理成可查、可复用、可持续更新的企业级知识门户