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

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

.class 文件是一个相当 well documented format 的文件,它定义了部分和大小,因此也定义了最大大小。

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

还有哪些限制?而且,如果您要制作最大的 .class 文件,它的大小是多少?

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

理论上,semi-realistic 方法的 class 限制很可能受常量池的限制。所有方法只能有 64K。 java.awt.Component 有 2863 个常量和 83548 个字节。与 bytes/constant 具有相同比率的 class 将 运行 从常量池中取出 1.9 MB。相比之下,像 com.sun.corba.se.impl.logging.ORBUtilSystemException 这样的 class 会 运行 大约 3.1 MB。

对于较大的 class,您可能 运行 常量池中的常量约 2 - 3 MB。

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

为了实验,我的编译在大约 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)

JVM 规范不强制限制 class 个文件,并且由于 class 个文件是可扩展的容器,支持 arbitrary custom attributes,您甚至可以尽可能多地使用它愿望.

u4 类型的每个 attribute has a size field,因此,最多可以指定 2³²-14GiB)的数量。因为在实践中,JRE API(ClassLoader 方法、Instrumentation API 和 Unsafe)都始终使用 byte[]ByteBuffer 来描述class 个文件,无法为超过 2³¹-1 个字节(2GiB)的 class 文件创建运行时 class。

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

如果你计算一下,你会得出这样的结论:一个结构良好的 class 文件的理论最大值可能超过任何实际存储空间 space(超过 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 文件在使用 Java 8 javac 编译时产生 105,236,439 字节。class-file。您还可以添加更多构造函数,尽管存在 javac 因 OutOfMemoryError 而失败的风险(使用 javac -J-Xmx4G 来克服此问题)。