Java .class文件的最大大小是多少?

.class文件是一种相当完整的文档格式 ,它定义了节和大小,因此也定义了最大大小。

例如,一个.class文件包含一个幻数(4个字节),一个版本(4个字节),一个常量池(可变大小)等。但是可以在几个级别定义大小:你可以有65535个方法,每个方法都是限制为65535字节。

其他限制是什么? 并且,如果您可以创建最大的.class文件,它的大小是多少?

如果需要,限制Java的答案。 这意味着如果Scala或Clojure(或……)改变了某些限制,则忽略这些值。

JVM规范没有强制限制类文件,并且由于类文件是可扩展的容器,支持任意自定义属性 ,您甚至可以根据需要将其最大化。

每个属性都有一个 u4类型的大小字段 ,因此可以指定最多2³²-14GiB )的数字。 实际上,由于JRE API( ClassLoader方法,Instrumentation API和Unsafe )一直使用byte[]ByteBuffer来描述类文件,因此无法创建具有超过2³¹-1字节的类文件的运行时类( 2GiB )。

换句话说,即使单个自定义属性的大小也可能超过实际可加载类的大小。 但是一个类可以有65535个属性,加上65535个字段,每个属性都有自己的65535个属性和65535个方法,每个属性也有多达65535个属性。

如果进行数学运算,您将得出结论:仍然良好形成的类文件的理论最大值可能超过任何实际存储空间(超过2⁶⁵字节)。

使用嵌套的finally块来制作巨大的StackMapTable非常容易,因为javac不明智地为每个嵌套级别生成单独的变量。 这允许从非常简单的方法产生几兆字节,如下所示:

 class A {{ int a; try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { a=0; }}}}}}}}}}}} }} 

无法添加更多嵌套级别,因为单个方法的代码大小将超出。 您还可以使用将实例初始化程序复制到每个构造函数的事实来复制它:

 class A {{ int a; try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { try {a=0;} finally { a=0; }}}}}}}}}}}} } A() { } A(int a) { } A(char a) { } A(double a) { } A(float a) { } A(long a) { } A(short a) { } A(boolean a) { } A(String a) { } A(Integer a) { } A(Float a) { } A(Short a) { } A(Long a) { } A(Double a) { } A(Boolean a) { } A(Character a) { } } 

使用Java 8 javac编译时,这个简单的java文件产生105,236,439字节的.class-file。 您还可以添加更多构造函数,但javac存在使用OutOfMemoryError失败的风险(使用javac -J-Xmx4G来克服此问题)。

具有方法的类的理论上的半实际限制很可能受常量池的约束。 所有方法只能有64K。 java.awt.Component有2863个常量和83548个字节。 具有相同字节/常量比的类将耗尽1.9 MB的常量池。 相比之下,像com.sun.corba.se.impl.logging.ORBUtilSystemException这样的类将耗尽大约3.1 MB。

对于大型类,您可能会在常量池中耗尽约2 – 3 MB的常量。

通过合同sun.awt.motif.X11GB18030_1$Encoder充满了大的常量字符串,只有68个常量,它是122KB。 这个类没有任何方法。

对于实验,我的编译器在大约21800个常量处爆发了太多常量。

 public static void main(String[] args) throws FileNotFoundException { try (PrintWriter out = new PrintWriter("src/main/java/Constants.java")) { out.println("class Constants {"); for (int i = 0; i < 21800; i++) { StringBuilder sb = new StringBuilder(); while (sb.length() < 100) sb.append(i).append(" "); out.println("private static final String c" + i + " = \"" + sb + "\";"); } out.println("}"); } } 

此外,似乎编译将文本加载到ByteBuffer中。 这意味着源不能是1 GB或编译器得到此错误。 我的猜测是字节中的字符溢出为负数。

 java.lang.IllegalArgumentException at java.nio.ByteBuffer.allocate(ByteBuffer.java:334) at com.sun.tools.javac.util.BaseFileManager$ByteBufferCache.get(BaseFileManager.java:325)