Цикл добавления For предотвращает OutOfMemoryError

Когда я удаляю цикл for, я получаю OutOfMemoryError. Когда я использую цикл, я не получаю ошибку. Может ли кто-нибудь помочь мне в понимании этого поведения?

public class JavaMemoryPuzzlePolite {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];
        }
        for (int i = 0; i < 1; i++) {
            System.out.println("Please be so kind and release memory");
        }
        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {
        JavaMemoryPuzzlePolite jmp = new JavaMemoryPuzzlePolite();
        jmp.f();
    }
}

Ответ 1

Метод f() выполняется в интерпретированном фрейме. Интерпретированные кадры ведут себя иначе, чем JIT-скомпилированные фреймы. Вот как он выглядит в псевдокоде без цикла for:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Allocate dataSize bytes of memory
4. Store it into variable slot #1

Итак, у вас есть OutOfMemoryError на шаге # 3, поскольку старый массив byte[] все еще находится в переменной # 1. Однако добавление цикла for (фактически добавление переменной i) делает другое:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Store 0 to slot #1 (thus byte[] array is now eligible for GC)
4. Do the for loop
5. Allocate dataSize bytes of memory
6. Store it into variable slot #2

Здесь, когда вы выделяете новый массив на шаге 5, первый массив уже может быть собран в мусор.

Обратите внимание, что компилятор JIT может вести себя умнее и отменить первый массив из переменной, поскольку он становится неиспользуемым (в вашем конкретном случае он вообще не выделяет его).

Также обратите внимание, что в вашем конкретном случае результат зависит от java-компилятора. ECJ (компилятор Eclipse) достаточно умен, чтобы не хранить первый массив в переменной вообще, поскольку он не используется. Таким образом, вы не получите OutOfMemoryError в ECJ-компилированном классе даже без цикла for.

Для получения более подробной информации вы можете посмотреть вывод дизассемблирования байт-кода, предоставляемый утилитой javap, и посмотреть, как повторно использовать слоты переменных.