关于jvm:Java .class文件的最大大小是多少?

What is the maximum size of a Java .class file?

.class文件是一种文档详尽的格式,用于定义节和大小,因此也定义了最大大小。

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

其他限制是什么? 而且,如果您要制作最大的.class文件,它的大小是多少?

如果需要,将答案限制为Java。 这意味着如果Scala或Clojure(或...)更改了一些限制,请忽略这些值。


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

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

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

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


使用嵌套的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可以克服此问题)。


具有方法的类的理论上的,半现实的限制很可能受到常量池的限制。所有方法中只能有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)