What is the maximum size of a Java .class file?
.class文件是一种文档详尽的格式,用于定义节和大小,因此也定义了最大大小。
例如,.class文件包含一个幻数(4个字节),一个版本(4个字节),常量池(可变大小)等。但是大小可以在多个级别上定义:您可以有65535个方法,每个方法 限制为65535个字节。
其他限制是什么? 而且,如果您要制作最大的.class文件,它的大小是多少?
如果需要,将答案限制为Java。 这意味着如果Scala或Clojure(或...)更改了一些限制,请忽略这些值。
-
stackoverflow.com/questions/5497495/和stackoverflow.com/questions/107855/帮助吗?
-
@s ??? s ??????否:第一个仅显示方法的大小(这是我在问题中所引用的)。第二个关于Java文件大小(由于空格被丢弃,因此可以是无限的)。此问题与.class文件的最大大小有关。
-
仅作记录:在实际情况中,最大大小无关紧要。如果最终生成的类文件超出了此限制,则意味着Java源必须很大。如此之大,以至于超出几个数量级就不能被认为是合理的。当然,这是一个有趣的理论问题,但是当您的日常工作受到答案的影响时,则很可能是:您做错了什么;-)
-
@GhostCat是的,这是一个理论问题,而不是实际问题。实际上,Id对文件系统可以处理多少个文件更感兴趣,因为我通常将代码切成短文件而不是大文件;)
-
该限制仅是理论上的。在处理生成的源代码或生成的字节码时,限制很重要。至于文件数量,现代文件系统完全能够处理庞大和/或众多的文件,因此无需担心。
-
这个问题可能重复
JVM规范对类文件没有限制,并且由于类文件是可扩展的容器,支持任意的自定义属性,因此您甚至可以根据需要将其最大化。
每个属性都有一个u4类型的大小字段,因此可以指定一个最大为232-1(4GiB)的数字。实际上,由于JRE API(ClassLoader方法,Instrumentation API和Unsafe)都始终使用byte[]或ByteBuffer来描述类文件,因此无法创建具有以下内容的类文件的运行时类超过231-1个字节(2GiB)。
换句话说,即使是单个自定义属性,其大小也可能超过实际可加载类的大小。但是一个类可以具有65535个属性,外加65535个字段,每个字段都具有自己的65535个属性,以及65535个方法,每个方法也最多具有65535个属性。
如果您进行数学计算,您将得出结论,仍然格式良好的类文件的理论最大值可能会超出任何实际存储空间(超过2个字节)。
-
我不知道该格式具有属性机制的可扩展性,我认为所有内容都以.class格式解释。我的错。那几乎是任何人都可以给出的最终答案。在接下来的几天中,您的答案会被标记为可接受答案,以允许其他有见地的答案出现。
-
结论是绝对正确的,并且Ive设法编写了一个程序,该程序创建了一个2147483483647字节的有效类文件,该文件可以成功加载到HotSpot JVM中,但不能再加载一个字节。但是,原因不是JRE API,因为引导类加载器未使用byte[]或ByteBuffer或Unsafe。但是,HotSpot classFileParser仍然依赖int变量来处理类文件流。
-
@apangin:您的主类通常是通过应用程序类加载器加载的,该类是专门的URLClassLoader,因此绑定到了众所周知的API。如果引导加载程序的实施受到相同类型的限制,我不会感到惊讶,但是在现实生活中,引导加载程序读取共享的类数据(.jsa)归档文件,而不是类文件。
-
我同意答案的重点,我只是说看着JRE API不足以得出结论,不可能在真实的JVM中加载更大的类文件。即使在JDK 9应用程序类加载器中,它也不再是URLClassLoader的实例,因此它的行为可能有所不同(尽管实际上没有)。
-
@apangin:好吧,依靠引导加载程序会违反格式正确的Java程序的定义。无论如何,重要的是实际的考虑因素,例如如果我实现了类转换器,那么JVM是否知道加载庞大的类文件的方式都没有关系,因为Instrumentation API要求它创建适合字节数组的表示形式。同样,如果我生成一个要在运行时实例化的类文件,则必须面对这样一个事实,即没有JRE方法可以接受更大的文件(引导加载程序没有Java接口),等等。
-
AddToBootstrapClassLoaderSearch及其Java对应物是引导程序类加载器的合法公共API :)
-
@apangin:JVMTI不是Java API,也不是JVM必须提供此API。 Instrumentation API是Java API,但仍不适用于任意Java应用程序,仅对Java Agents有效,这同样不是干净Java程序可以依赖的强制功能。
使用嵌套的finally块制作巨大的StackMapTable非常容易,因为javac不明智地为每个嵌套级别生成了单独的变量。这样可以通过非常简单的方法产生几兆字节,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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;
}}}}}}}}}}}}
}} |
无法添加更多的嵌套级别,因为您将超出单个方法的代码大小。您还可以使用实例初始化程序复制到每个构造函数的事实来复制此代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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文件。您还可以添加更多的构造函数,尽管存在javac在OutOfMemoryError下失败的风险(使用javac -J-Xmx4G可以克服此问题)。
-
好东西。 现在,让我们将其与try-with-resource结合起来……
-
这真太了不起了。 :)
-
值得注意的是,此行为仅适用于Java 1.6以下的标准编译器,其中最终将块编译为内联字节码,而不使用JSR指令。 有关更多信息,请参见此问题的答案。
具有方法的类的理论上的,半现实的限制很可能受到常量池的限制。所有方法中只能有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充满了大常量字符串,它是122KB,只有68个常量。此类没有任何方法。
为了进行实验,我的编译在21800个常数附近爆炸了太多常数。
1 2 3 4 5 6 7 8 9 10 11 12
| 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,否则编译器会收到此错误。我的猜测是,以字节为单位的字符溢出到负数。
1 2 3
| java. lang. IllegalArgumentException
at java. nio. ByteBuffer. allocate(ByteBuffer. java:334)
at com. sun. tools. javac. util. BaseFileManager$ByteBufferCache. get(BaseFileManager. java:325) |
-
包含价值64KiB a=a+a;的方法呢?几乎没有常量,但是有很多语句。您的答案比Id实际期望值低几个数量级(至少4 GiB +)。您谈论的是(半)现实的限制,我不记得曾在我的问题中施加过任何此类限制。
-
@OlivierGrgoire您可以在方法中创建long byte [],以使用不带常量的代码填充它。这东西没有实用价值。
-
我知道,虽然我很重视您的回答,但我不认为它回答了理论上的问题。问题是关于.class文件,而不是关于被编译成.class文件的.java文件。
-
@OlivierGrgoire经过实验,似乎编译器无法处理1 GB或更大的源文件。我怀疑这个问题可能会解决,尽管我怀疑甲骨文是否会这样做。
-
我只是检查了源代码。不出所料,该限制是由int溢出引起的,因此,该限制为2GiB,而不是1GiB。
-
@Holger 2 GiB将源字节转换为字符后。将文本转换回UTF8字符串后,这是.java文件的大约1 GB。使用三个字节的字符可能意味着?3 GB。
-
@Holger,即使用ASCII的1 GB源文件,使用chars转换为2 GB的内存。
-
堆栈跟踪清楚地指向ByteBuffer,试图分配多个字节。从这个地方,我无法推断出与char的关系。但是问题仍然是关于类文件而不是源文件……