Что происходит, когда недостаточно памяти для выброса ошибки OutOfMemoryError?

207

Я знаю, что каждый объект требует кучи памяти, и каждый примитив / ссылка в стеке требует стековой памяти.

Когда я пытаюсь создать объект в куче, и для этого недостаточно памяти, JVM создает в куче java.lang.OutOfMemoryError и выдает его мне.

Таким образом, это неявно означает, что JVM зарезервировала некоторую память при запуске.

Что происходит, когда эта зарезервированная память израсходована (она, безусловно, будет израсходована, см. Обсуждение ниже), а JVM не хватает памяти в куче для создания экземпляра java.lang.OutOfMemoryError ?

Это просто висит? Или он бросит мне, nullтак как нет памяти для newэкземпляра OOM?

try {
    Object o = new Object();
    // and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
    // JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
    // what next? hangs here, stuck forever?
    // or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
    e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}

==

Почему JVM не может зарезервировать достаточно памяти?

Независимо от того, сколько памяти зарезервировано, эта память все еще может быть использована, если у JVM нет способа «восстановить» эту память:

try {
    Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
    // JVM had 100 units of "spare memory". 1 is used to create this OOM.
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        // JVM had 99 units of "spare memory". 1 is used to create this OOM.
        try {
            e.printStackTrace();
        } catch (java.lang.OutOfMemoryError e3) {
            // JVM had 98 units of "spare memory". 1 is used to create this OOM.
            try {
                e.printStackTrace();
            } catch (java.lang.OutOfMemoryError e4) {
                // JVM had 97 units of "spare memory". 1 is used to create this OOM.
                try {
                    e.printStackTrace();
                } catch (java.lang.OutOfMemoryError e5) {
                    // JVM had 96 units of "spare memory". 1 is used to create this OOM.
                    try {
                        e.printStackTrace();
                    } catch (java.lang.OutOfMemoryError e6) {
                        // JVM had 95 units of "spare memory". 1 is used to create this OOM.
                        e.printStackTrace();
                        //........the JVM can't have infinite reserved memory, he's going to run out in the end
                    }
                }
            }
        }
    }
}

Или более кратко:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}
Pacerier
источник
2
ваш ответ будет зависеть от JVM в значительной степени
MozenRath
23
Одна библиотека телефонии, которую я когда-то использовал (в 90-х годах), использовалась для того, чтобы поймать, OutOfMemoryExceptionа затем сделать что-то, что включало создание большого буфера ...
Том Хотин - tackline
@ TomHawtin-tackline Что делать, если операции, связанные с этим, выдают еще одно OOM?
Pacerier
38
Как и в мобильном телефоне, он разряжен, но у него достаточно батареи, чтобы продолжать рассылать спам "У вас разряжена батарея".
Казума
1
«Что происходит, когда эта зарезервированная память израсходована»: это может произойти, только если программа перехватит первую OutOfMemoryErrorи сохранит ссылку на нее. Выясняется, что перехват OutOfMemoryErrorне так полезен, как можно подумать, потому что вы почти ничего не можете гарантировать о состоянии вашей программы при перехвате. См stackoverflow.com/questions/8728866/...
Raedwald

Ответы:

145

JVM никогда не хватает памяти. Это делает вычисление памяти стека кучи заранее.

Структура виртуальной машины Java, глава 3 , раздел 3.5.2 гласит:

  • Если стеки виртуальных машин Java могут динамически расширяться, и предпринимается попытка расширения, но может быть недостаточно доступной памяти, чтобы осуществить расширение, или если недостаточно памяти может быть доступно для создания начального стека виртуальной машины Java для нового потока, виртуальная Java машина кидает OutOfMemoryError.

Для кучи , раздел 3.5.3.

  • Если для вычислений требуется больше кучи, чем может быть сделано доступной системой автоматического управления хранением, виртуальная машина Java выдает OutOfMemoryError.

Таким образом, он делает вычисления заранее, прежде чем делать выделение объекта.


Что происходит, так это то, что JVM пытается выделить память для объекта в памяти, называемой областью постоянного поколения (или PermSpace). Если распределение завершается неудачно (даже после того, как JVM вызовет сборщик мусора, чтобы попытаться выделить свободное пространство), он выбрасывает OutOfMemoryError. Даже для исключений требуется место в памяти, поэтому ошибка будет генерироваться бесконечно.

Дальнейшее чтение. ? Кроме того, OutOfMemoryErrorмогут возникнуть различные структуры JVM.

Бухаке синди
источник
10
Я имею в виду да, но разве виртуальной машине Java также не понадобится память для выдачи ошибки OutOfMemoryError? Что происходит, когда нет памяти, чтобы выбросить OOM?
Pacerier
5
Но если JVM не возвращает ссылку на тот же экземпляр OOM, разве вы не согласны с тем, что в конечном итоге он исчерпает свою зарезервированную память? (как показано в коде в вопросе)
Pacerier
1
Позвольте мне добавить ссылку на комментарий Грэма здесь: stackoverflow.com/questions/9261705/…
Pacerier
2
Возможно, было бы неплохо, если бы виртуальная машина сохранила одно исключение OOM для указанного крайнего случая и на атомной электростанции.
Джон К
8
@JohnK: Я надеюсь, что атомные электростанции не запрограммированы на Java, точно так же, как космические корабли и Боинг 757 не запрограммированы на Java.
Дитрих Эпп
64

Грэм Борланд, кажется, прав : по крайней мере, моя JVM, по-видимому, повторно использует OutOfMemoryErrors. Чтобы проверить это, я написал простую тестовую программу:

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

Запуск это производит этот вывод:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

Кстати, JVM, на которой я работаю (на Ubuntu 10.04), выглядит так:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

Редактировать: я пытался увидеть, что произойдет, если я заставил JVM полностью запустить из памяти, используя следующую программу:

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

Как выяснилось, кажется, что петля навсегда. Однако, что любопытно, попытка завершить программу с помощью Ctrl+ Cне работает, а только выдает следующее сообщение:

Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

Илмари Каронен
источник
Хороший тест, то же самое для меня с версией "1.7.0_01" Java HotSpot (TM) 64-разрядная серверная виртуальная машина
Pacerier
Интересный. Это создает впечатление, что JVM не полностью понимает хвостовую рекурсию ... (Как будто она выполняет некоторое рекурсивное повторное использование стека, но этого недостаточно, чтобы очистить всю память ...)
Izkata
Модифицировал ваш код, чтобы увидеть, сколько разных я получаю - я всегда выполняю ветвь else ровно 5 раз.
Irfy
@Izkata: Я бы скорее сказал, что это сознательное решение предварительно распределить nOOM и повторно использовать один из них впоследствии, чтобы всегда можно было выбросить OOM . JVM от Sun / Oracle не поддерживает хвостовую рекурсию вообще IIRC?
Irfy
10
@Izkata Цикл работает, по-видимому, бесконечно, потому что JVM постоянно выбрасывает одно и то же (5-е или около того) OOM после того, как ему не хватает памяти. Таким образом, у него есть nкадры в стеке, и он в конечном итоге создает и уничтожает кадры n+1на вечность, создавая впечатление бесконечной работы.
Irfy
41

Большинство сред времени выполнения предварительно выделяют при запуске или иным образом резервируют достаточное количество памяти, чтобы справиться с ситуациями нехватки памяти. Я думаю, что большинство нормальных реализаций JVM сделают это.

Грэм Борланд
источник
1
stackoverflow.com/questions/9261215/… Верно, но если JVM зарезервировала 100 единиц памяти для этого и потратила 1 единицу на первое OOM, что произойдет, если в моем блоке перехвата OOM я выполню e.printStackTrace ()? e.printStackTrace () тоже требует памяти. Затем JVM тратит еще одну единицу памяти, чтобы выбросить мне еще одно OOM (осталось 98 блоков), и я ловлю это с помощью e.printStackTrace (), поэтому JVM выдает мне еще одно OOM (осталось 97 блоков), и я поймаю, и я хотел ..
Pacerier
3
Именно поэтому OOME никогда не использовал трассировку стека - трассировки стека занимают память! OOME только начал пытаться включить трассировку стека в java 6 ( blogs.oracle.com/alanb/entry/… ). Я предполагаю, что если трассировка стека невозможна, то исключение генерируется без трассировки стека.
Шон Рейли
1
@SeanReilly Я имею в виду, что исключение, которое не имеет трассировки стека, все еще является объектом, который все еще требует памяти. Память необходима независимо от того, предоставляется ли трассировка стека. Правда ли, что в блоке catch, если не осталось памяти для создания OOM (нет памяти для создания даже одного без трассировки стека), я бы поймал ноль?
Pacerier
17
JVM может вернуть несколько ссылок на один статический экземпляр исключения OOM. Таким образом, даже если ваше catchпредложение пытается использовать больше памяти, JVM может просто снова и снова генерировать один и тот же экземпляр OOM.
Грэм Борланд
1
@TheEliteGentleman Я согласен, что это тоже очень хорошие ответы, но JVM живет на физической машине. Эти ответы не объясняют, каким образом JVM может волшебным образом иметь достаточно памяти, чтобы всегда предоставлять экземпляр OOM. «Это всегда один и тот же случай», кажется, решает загадку.
Pacerier
23

В прошлый раз, когда я работал в Java и использовал отладчик, инспектор кучи показал, что JVM выделяет экземпляр OutOfMemoryError при запуске. Другими словами, он распределяет объект до того, как ваша программа сможет начать потреблять, не говоря уже об исчерпании памяти.

benzado
источник
12

Из спецификации JVM, глава 3.5.2:

Если стеки виртуальных машин Java могут динамически расширяться, и предпринимается попытка расширения, но может быть недостаточно доступной памяти, чтобы осуществить расширение, или если недостаточно памяти может быть доступно для создания начального стека виртуальной машины Java для нового потока, виртуальная Java машина кидаетOutOfMemoryError .

Каждая виртуальная машина Java должна гарантировать, что она выдаст OutOfMemoryError. Это означает, что он должен быть способен создать экземплярOutOfMemoryError (или иметь созданный заранее), даже если не осталось места в куче.

Хотя это не должно гарантировать, что достаточно памяти, чтобы перехватить ее и напечатать красивую трассировку стека ...

прибавление

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

Не требуется, чтобы брошенные экземпляры OutOfMemoryErrorбыли уникальными или создавались по требованию. JVM может подготовить ровно один экземпляр OutOfMemoryErrorво время запуска и выбросить его всякий раз, когда у него заканчивается свободное пространство в куче, что обычно бывает в обычной среде. Другими словами: случай, OutOfMemoryErrorкоторый мы видим, мог быть единственным.

Андреас Долк
источник
Я предполагаю, что для реализации этого пришлось бы воздержаться от записи трассировки стека, если места было мало.
Raedwald
@Raedwald: На самом деле, это именно то, что делает Oracle VM, см. Мой ответ.
слеске
11

Интересный вопрос :-). В то время как другие дали хорошие объяснения теоретических аспектов, я решил попробовать это. Это на Oracle JDK 1.6.0_26, Windows 7 64 бит.

Испытательная установка

Я написал простую программу для исчерпания памяти (см. Ниже).

Программа просто создает статический элемент java.util.Listи продолжает вставлять в него свежие строки, пока не будет выдано OOM. Затем он ловит его и продолжает набивать бесконечный цикл (плохая JVM ...).

Результат испытаний

Как видно из вывода, первые четыре раза OOME выбрасывается, он идет с трассировкой стека. После этого последующие OOME печатаются, только java.lang.OutOfMemoryError: Java heap spaceесли они printStackTrace()вызваны.

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

Также интересен хеш-код OOME. Обратите внимание, что первые несколько OOME имеют разные хэши. Как только JVM начинает пропускать трассировки стека, хэш всегда остается тем же. Это говорит о том, что JVM будет использовать свежие (предварительно выделенные?) Экземпляры OOME как можно дольше, но если нажать толчок, он просто повторно использует тот же экземпляр, а не будет ничего бросать.

Вывод

Примечание: я обрезал некоторые следы стека, чтобы сделать вывод более легким для чтения ("[...]").

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

Программа

public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}
sleske
источник
Когда пуш приходит к пушу, он не может выделить память для OOME, поэтому он сбрасывает уже созданные.
Бухаке Синди
1
Небольшое примечание: некоторые из ваших выводов выглядят не так, предположительно потому, что вы печатаете, System.outно printStackTrace()используете System.errпо умолчанию. Вы, вероятно, получите лучшие результаты, если будете последовательно использовать любой поток.
Ильмари Каронен
@IlmariKaronen: Да, я это заметил. Это всего лишь пример, так что это не имеет значения. Очевидно, вы не будете использовать его в производственном коде.
Слёске
Правда, мне просто понадобилось время, чтобы понять, что происходит, когда я впервые посмотрел на результат.
Ильмари Каронен
6

Я почти уверен, что JVM будет абсолютно уверена, что у нее по крайней мере достаточно памяти, чтобы вызвать исключение, прежде чем ей не хватит памяти.

Оскар Гомес
источник
1
Но они попадут в эту ситуацию независимо от того, сколько памяти зарезервировано: stackoverflow.com/questions/9261705/…
Pacerier
4

Исключения, указывающие на попытку нарушить границы среды управляемой памяти, обрабатываются средой выполнения указанной среды, в данном случае JVM. JVM - это собственный процесс, в котором выполняется IL вашего приложения. Если программа попытается сделать вызов, который расширяет стек вызовов за пределы или выделит больше памяти, чем может зарезервировать JVM, среда выполнения сама выдаст исключение, что приведет к отмене стека вызовов. Независимо от объема памяти, в которой ваша программа нуждается в данный момент, или глубины стека вызовов, JVM выделит достаточно памяти в пределах собственных границ процесса, чтобы создать указанное исключение и внедрить его в ваш код.

Keiths
источник
«JVM выделит достаточно памяти в пределах своих собственных процессов для создания указанного исключения», но если ваш код сохраняет ссылку на это исключение, поэтому его нельзя использовать повторно, как он может создать другое? Или вы предлагаете, чтобы у него был специальный объект Singleton OOME?
Raedwald
Я не предлагаю это. Если ваша программа перехватывает и удерживает каждое создаваемое исключение, включая созданное и внедренное JVM или ОС, то в конечном итоге сама JVM превысит некоторую границу, установленную ОС, и ОС отключит ее для GPF или похожая ошибка. Тем не менее, это плохой дизайн в первую очередь; исключения должны быть либо обработаны, а затем выходить за рамки или выбрасываться. И вы никогда не должны пытаться поймать и продолжить на SOE или OOME; кроме «очистки», чтобы вы могли выйти изящно, вы ничего не можете сделать, чтобы продолжить выполнение в таких ситуациях.
KeithS
«это плохой дизайн в первую очередь»: ужасный дизайн. Но педантично, будет ли JVM соответствовать спецификации, если она потерпит неудачу таким образом?
Raedwald
4

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

Дальнейшее чтение:

И как последнее замечание, пытаясь поймать на java.lang.Error (и его потомков классов), чтобы напечатать StackTrace не может дать вам никакой полезной информации. Вы хотите кучу свалки вместо этого.

Майкл Тиффани
источник
4

Чтобы дополнительно уточнить ответ @Graham Borland, функционально JVM делает это при запуске:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Позже JVM выполняет один из следующих байт-кодов Java: «новый», «anewarray» или «multianewarray». Эта инструкция заставляет JVM выполнить несколько шагов в состоянии нехватки памяти:

  1. Вызов родной функции, скажем, allocate() . allocate()пытается выделить память для какого-то нового экземпляра определенного класса или массива.
  2. Этот запрос на выделение не выполняется, поэтому JVM вызывает другую собственную функцию, скажем, doGC() , , которая пытается выполнить сборку мусора.
  3. Когда эта функция возвращается, allocate() пытается выделить память для экземпляра еще раз.
  4. Если это не удается (*), то JVM внутри allocate () просто выполняет throw OOME; allocate , ссылаясь на OOME, который был создан при запуске. Обратите внимание, что ему не нужно выделять этот OOME, он просто ссылается на него.

Очевидно, это не буквальные шаги; В зависимости от реализации они будут варьироваться от JVM к JVM, но это идея высокого уровня.

(*) Значительный объем работы происходит здесь до сбоя. JVM будет пытаться очистить объекты SoftReference, попытаться выделить непосредственно в поколение с правами владения при использовании сборщика поколений и, возможно, другие вещи, такие как финализация.

ahawtho
источник
3

Ответы, которые говорят, что JVM будет предварительно распределять OutOfMemoryErrors, действительно верны.
В дополнение к проверке этого путем провоцирования ситуации нехватки памяти мы можем просто проверить кучу любой JVM (я использовал небольшую программу, которая просто спит, запустив ее с помощью Oracle Hotspot JVM из Java 8 update 31).

С помощью jmap мы видим, что, похоже, существует 9 экземпляров OutOfMemoryError (хотя у нас достаточно памяти):

> jmap -histo 12103 | grep OutOfMemoryError
 71: 9 288 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

Затем мы можем сгенерировать дамп кучи:

> jmap -dump: format = b, file = heap.hprof 12315

и откройте его с помощью Eclipse Memory Analyzer , где OQL-запрос показывает, что JVM, похоже, предварительно выделяет OutOfMemoryErrorsвсе возможные сообщения:

введите описание изображения здесь

Код для Java 8 Hotspot JVM, который фактически предварительно распределяет их, можно найти здесь , и выглядит так (некоторые части опущены):

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg());

  msg = java_lang_String::create_from_str("Metaspace", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = java_lang_String::create_from_str("Compressed class space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = java_lang_String::create_from_str("/ by zero", CHECK_false);
  java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_java_heap->klass();
  assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

и этот код показывает, что JVM сначала попытается использовать одну из предварительно выделенных ошибок с пространством для трассировки стека, а затем вернется к одной без трассировки стека:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}
Йохан Кавинг
источник
Хороший пост, я приму это как ответ в течение недели, чтобы сделать его более заметным, прежде чем вернуться к предыдущему ответу.
Пейсер
В Java 8 и более поздних версиях пространство постоянной генерации полностью удалено, и вы больше не можете указывать размер памяти для выделения кучи в Java 8 и более поздних версиях, так как они представили управление памятью метаданных динамического класса Metaspace. Было бы неплохо, если бы вы могли показать фрагмент кода, который обслуживает также PermGen, и сравнить его с Metaspace.
Бухаке Синди
@BuhakeSindi - я не вижу, что постоянное поколение связано с этим. Новые объекты не выделяются в постоянном поколении, как вы указали в своем ответе. Вы также никогда не упоминаете тот факт, что OutOfMemoryErrors предварительно распределены (что является фактическим ответом на вопрос).
Йохан Кавинг
1
Хорошо, я говорю о том, что из Java 8 распределение объектов вычисляется динамически, тогда как до того, как оно было выделено в заранее определенном пространстве кучи, возможно, оно заранее выделено заранее. Несмотря на то, что OOME предварительно выделены, для определения необходимости создания OOME необходимо выполнить «вычисления» (поэтому я и сделал ссылку на спецификацию JLS).
Бухаке Синди
1
Размер кучи Java такой же предопределенный в Java 8, как и раньше. Постоянное поколение было частью кучи, которая содержала метаданные класса, интернированные строки и статику класса. У него был ограниченный размер, который нужно было настраивать отдельно от общего размера кучи. В Java 8 метаданные класса были перемещены в собственную память, а встроенные строки и статические классы были перемещены в обычную кучу Java (см., Например, здесь: infoq.com/articles/Java-PERMGEN-Removed ).
Йохан Кавинг