代码过长,编译失败

今天当发版工具人时遇到编译报错「代码过长(code too large)」,把这段说明发到工作群里,瞬间听到周围一片爽朗的笑声……

挺让我惊讶,原来 Java 里的方法大小是有限制的——编译后的字节码大小不能超过 64kb。

JVM 规范 Chapter 4. The class File Formatclass 结构的 method_info (方法信息)里定义了一个 u1 类型[1]的数组来保存该 Java 方法编译后的数据,这个数组的大小由一个 u2 类型[2]的变量确定。

1 个 u2 类型的值占 2 byte, 2 byte 就是 16 bit(位),16 bit 所能表示的最大值是 2 的 16 次方——65536。因此一个 u2 类型的变量的最大值就是 65536,所以这个数组也就最多只能保存 65536 byte,即 64kb 的数据。(1 kb = 1024 byte)

这个错误虽然可以在编译期间暴露,但 JVM 并不能保证它加载的 class 文件都是满足要求的,因此在加载 class 文件的验证阶段,还会对该数组的长度进行检查。


除了普通方法外,构造方法、初始化代码块也受到此限制。

初始化代码块为什么也受到此限制呢?我猜测初始化代码块在 JVM 层面还是当作方法来使用的。

同时,如果一个 Java 类里有多个初始化代码块,虽然每个代码块里的代码没有超出此限制,但当它们之和超过该限制时,也会编译报错「代码过长」。我猜测多个初始化代码块在 JVM 层面会被优化成一个方法来使用。

代码块保存在 JVM 的哪块区域?

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DayOne{
{
System.out.println("哈哈哈哈哈");
// repeat 4000 times
System.out.println("哈哈哈哈哈");
}
{
System.out.println("哈哈哈哈哈");
// repeat 4000 times
System.out.println("哈哈哈哈哈");
}
{
System.out.println("哈哈哈哈哈");
// repeat 4000 times
System.out.println("哈哈哈哈哈");
}
}

更有趣的是,Java 中数组的大小理论上与 int 的取值范围相关,但实际上可能在初始化数组时,还未达到最大容量便报错「代码过长」:

1
2
3
4
5
6
7
{
String[] arr = new String[]{
"哈哈哈含安安安安哈哈哈含安安安安哈哈哈含安安安安哈哈哈含安安安安",
//…… repeat 10000 times
"哈哈哈含安安安安哈哈哈含安安安安哈哈哈含安安安安哈哈哈含安安安安"
}
}

类似的,JVM 规范 还介绍了「类或接口可以声明的字段数量限制在 65535」「方法参数的数量限制为 255」等情况。:)


  1. 1 byte (字节)的无符号数 ↩︎

  2. 2 byte 的无符号数 ↩︎