String的最大长度剖析
String常量最大长度剖析
一、理论分析
最近我在学JVM,发现一个比较有意思的事,再生成class文件的时候,一个String常量(CONSTANT_Utf8_info)由三个部分组成,一个tag占一个字节表示这是一个字符串,一个length占两个字节表示这个字符串的字符数,最后一个bytes[]表示字符串的内容,这个是不固定的,每一个占一个字节,如下所示:
CONSTANT_Utf8_info {u1 tag;u2 length;u1 bytes[length];
}
在让我们实际看一下是这么个事,我报了三个红框表示这是三个字符串常量,我们拿第一个举例01 00 03 6e 75 6d
,01 表示tag,表示后续为字符串常量,00 03 表示这个字符串常量的length占后续的三个字节,6e 75 6d 表示字符串实际的值
那么我们可以得出一个字符串常量最大仅为 2^16 - 1 = 65535 ,我们通过代码实践一下,由于太长了(不能使用循环 + 的方式,因为编译的时候会优化为StringBuilder类,调用他的append方法进行相加,再调用StringBuilder 的 toString() 方法会 new 一个 String 对象,String对象存字符串用的 char[](char的数组)(ps:jdk8以上用的byte[]),那么通过StringBuilder 的 toString() 方法 new String生成的对象放到的是堆里面,并不会保存在字符串常量池里面,所以不算一个字符串常量,但你可以通过 String a = new Stirng(“A…A”) 的方式会先把 A…A 放到字符串常量池里面,跟下图所示是一个用法),我就不展示全部代码了,截个图即可
二、测试
测试一下65534个A是否通过
是没有问题的,那么我们再加一个A试一下
我擦,报错了,但按照理论字符串常量最大不是为65535吗,为什么字符串常量到65535就会报错啊,我们检查一下class文件是否会生成,这里为了方便大家观看结果我就不用idea了
三、找问题
1、创建一个Test.java文件放 65534 个A,看看通过 javac 是否能编译为 class 文件
2、结果:发现果然可以生成一个class文件
3、删除class文件,修改java文件,添加一个A,使得 a 为 65535 个 A
4、测试:发现报错了,class文件也没有生成
四、找JAVAC的源码,通过源码看问题出在哪里
jdk8:jdk8/jdk8/langtools: 1ff9d5118aae src/share/classes/com/sun/tools/javac/jvm/Gen.java
jdk11:jdk/jdk11: 1ddf9a99e4ad src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java
我们就发现下面这串代码(下面是jdk8的):
/** Check a constant value and report if it is a string that is* too large.*/private void checkStringConstant(DiagnosticPosition pos, Object constValue) {if (nerrs != 0 || // only complain about a long string onceconstValue == null ||!(constValue instanceof String) ||((String)constValue).length() < Pool.MAX_STRING_LENGTH)return;log.error(pos, "limit.string");nerrs++;}
那么 Pool.MAX_STRING_LENGTH 又是什么呢,如下面的代码。jdk8/jdk8/langtools: 1ff9d5118aae src/share/classes/com/sun/tools/javac/jvm/Pool.java
public class Pool {public static final int MAX_ENTRIES = 0xFFFF;public static final int MAX_STRING_LENGTH = 0xFFFF;// 以下省略
}
这个数字是0xFFFF,前边写上0x,只是为了说明它是十六进制数。F,代表十进制的15;0xFFFF = 15 * 16^3 + 15 * 16^2 + 15 * 16^1 + 15 * 16^0 = 65535
那么编译的时候字符串常量的长度就要小于 65535 还不能等于 65535 ,这是为啥呢
五、看下JVM的规范
Chapter 4. The class File Format
start_pc, end_pc
The values of the two items and indicate the ranges in the array at which the exception handler is active. The value of must be a valid index into the array of the opcode of an instruction. The value of either must be a valid index into the array of the opcode of an instruction or must be equal to , the length of the array. The value of must be less than the value of .
start_pc``end_pc``code``start_pc``code``end_pc``code``code_length``code``start_pc``end_pc
The is inclusive and is exclusive; that is, the exception handler must be active while the program counter is within the interval [, ).start_pc``end_pc``start_pc``end_pc
The fact that is exclusive is a historical mistake in the design of the Java Virtual Machine: if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes.end_pc
翻译一下
start_pc、end_pc
这两个项的值和表示数组中异常处理程序处于活动状态的范围。的值必须是指令操作码数组中的有效索引。其中之一的值必须是指令操作码数组的有效索引,或者必须等于数组的长度。的值必须小于的值。start_pc
end_pccode
start_pccode
end_pccode
code_lengthcode
start_pcend_pc
是包容性的,也是排他性的;也就是说,当程序计数器在区间[,]内时,异常处理程序必须处于活动状态。start_pc
end_pc
start_pc
end-pc
这是Java虚拟机设计中的一个历史错误:如果一个方法的Java虚拟机代码正好是65535字节长,并且以1字节长的指令结尾,那么该指令就不能受到异常处理程序的保护。编译器编写者可以通过将任何方法、实例初始化方法或静态初始化器(任何代码数组的大小)的生成Java虚拟机码的最大大小限制为65534字节来解决这个错误。
end_pc`
原来是为了弥补早期设计时的一个bug,“长度刚好65535个字节,且以1个字节长度的指令结束,这条指令不能被异常处理器处理”,因此就将数组的最大长度限制到了65534了。
参考文档
String最大长度是多少?涉及知识面太多,不要错过!-腾讯云开发者社区-腾讯云
尚硅谷宋红康JVM全套教程