Каков максимальный размер файла Java.class?

Файл A .class представляет собой довольно хорошо документированный формат , который определяет разделы и размер, а также максимальные размеры.

Например, файл .class содержит магическое число (4 байта), версию (4 байта), постоянный пул (размер переменной) и т.д. Но размеры могут быть определены на нескольких уровнях: вы можете иметь 65535 и каждый из них ограничен 65535 байт.

Каковы другие ограничения? И, если бы вы сделали максимально возможным файл .class, какой размер был бы?

При необходимости ограничьте ответы на Java. Это означает, что если Scala или Clojure (или...) изменить некоторые ограничения, игнорируйте эти значения.

Ответ 1

Спецификация JVM не дает ограничений для файлов классов, и поскольку файлы классов являются расширяемыми контейнерами, поддерживая произвольные пользовательские атрибуты, вы можете даже максимизировать его как угодно.

Каждый атрибут имеет поле размера типа u4, поэтому может указывать число до 2³²-1 (4GiB). Поскольку на практике API JRE API (ClassLoader, Instrumentation API и Unsafe) все последовательно используют либо byte[], либо ByteBuffer для описания файлов классов, невозможно создать класс выполнения файла класса, имеющего более 2³¹-1 байтов (2GiB).

Другими словами, даже один пользовательский атрибут может иметь размер, превышающий размер реально загружаемых классов. Но класс может иметь 65535 атрибутов плюс 65535 полей, каждый из которых имеет 65535 собственных атрибутов плюс 65535 методов, каждый из которых имеет атрибут до 65535.

Если вы выполните математику, вы придете к выводу, что теоретический максимум файла с хорошо сформированным классом может превышать любое реальное пространство памяти (более 2 байтов).

Ответ 2

Очень просто сделать огромный StackMapTable с помощью вложенных блоков finally, поскольку 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 файла. Вы также можете добавить больше конструкторов, хотя есть риск, что javac завершится с OutOfMemoryError (используйте javac -J-Xmx4G, чтобы преодолеть это).

Ответ 3

Теоретический полуреалистичный предел для класса с методами, скорее всего, связан постоянным пулом. Вы можете использовать только 64K для всех методов. java.awt.Component имеет 2863 константы и 83548 байт. Класс, который имел такое же отношение байтов/констант, закончил бы постоянный пул с 1,9 МБ. Для сравнения класс, подобный com.sun.corba.se.impl.logging.ORBUtilSystemException, истекал бы около 3,1 МБ.

Для большого класса вы, скорее всего, исчерпаете постоянный в постоянном пуле около 2 - 3 МБ.

По контракту sun.awt.motif.X11GB18030_1$Encoder полно больших константных строк и оно составляет 122 КБ с 68 константами. Этот класс не имеет методов.

Для экспериментов мой компилятор взрывает слишком много констант со скоростью около 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 ГБ или компилятор получает эту ошибку. Я предполагаю, что символы в байтах переполнены отрицательным числом.

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