Що відбувається, коли недостатньо пам'яті, щоб кинути 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);
    }
}

2
Ваша відповідь буде значною мірою залежною від JVM
MozenRath

23
Одна бібліотека телефонії, яку я колись використовував (у 90-х роках), використовував для того, щоб ловити OutOfMemoryExceptionта робити щось, що стосувалося створення великого буфера ...
Том Хоутін - таклін

@ TomHawtin-tackline Що робити, якщо операції, пов'язані з цим, кидають інший OOM?
Pacerier

38
Як і у мобільного телефону, у нього закінчується акумулятор, але у нього є достатньо акумулятора, щоб тримати спам "Вичерпано акумулятор".
Казума

1
"Що станеться, коли ця зарезервована пам'ять буде використана": це може статися лише в тому випадку, якщо програма застане першу OutOfMemoryErrorі збереже посилання на неї. Виявляється, що ловити a 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можуть зустрічатися в різній структурі СВМ.


10
Я маю на увазі "так", але хіба віртуальній машині Java також не потрібна пам'ять, щоб кинути OutOfMemoryError? Що відбувається, коли немає пам'яті, щоб кинути УМВ?
Pacerier

5
Але якщо JVM не поверне посилання на той самий екземпляр OOM, чи не погоджуєтесь ви, що в кінцевому підсумку це закінчиться в зарезервованій пам'яті? (як показано у коді у запитанні)
Pacerier

1
Дозвольте мені поставити посилання на коментар Грехема тут: stackoverflow.com/questions/9261705 / ...
Pacerier

2
Можливо, буде приємно, якщо ВМС зберегла єдиний виняток OOM для крайнього випадку і на атомній електростанції.
Джон К

8
@JohnK: Я би сподівався, що атомні електростанції не запрограмовані на Java, як і космічні човники, а Boeing 757s не запрограмовані на Яві.
Дітріх Епп

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-бітний сервер VM
Pacerier

Цікаво. Це робить його схожим на те, що JVM не цілком розуміє хвостову рекурсію ... (Наче це робить якийсь хвостовий рекурсивний повторний стек повторного використання, але не зовсім достатній, щоб очистити всю пам'ять ...)
Izkata

Змінив ваш код, щоб побачити, скільки я отримую - я завжди отримую іншу гілку рівно 5 разів.
Ірфі

@Izkata: Я скоріше скажу, що це свідоме рішення про попереднє розподілення nOOM та повторне використання одного з них після цього, щоб завжди можна було кинути OOM . JVM Sun / Oracle взагалі не підтримує рекурсію хвоста?
Ірфі

10
@Izkata Петля працює, мабуть, нескінченно, тому що JVM постійно кидає одну і ту ж (5-ю або стільки) OOM після того, як вичерпається пам'ять. Таким чином, у нього є nкадри на стеку, і це закінчується створенням і руйнуванням кадру n+1на вічність, надаючи зовнішньому вигляду безкінечного бігу.
Ірфі

41

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


1
stackoverflow.com/questions/9261215/… : Це правда, але якщо JVM зарезервував 100 одиниць пам'яті для цього і витратив 1 одиницю на перший OOM, що станеться, якщо в моєму блоці спіймання OOM я робити e.printStackTrace ()? e.printStackTrace () теж вимагає пам'яті. Тоді JVM витратить ще одну одиницю пам’яті, щоб кинути мені ще одну OOM (залишилось 98 одиниць), і я зловив це за допомогою e.printStackTrace (), тож JVM кидає мені ще одну OOM (залишилось 97 одиниць) і добре, що спіймано, і я хотів ..
Пейсьєр

3
Саме тому OOME ніколи не включав слід стека - сліди стека займають пам'ять! OOME тільки почав намагатися включити слід стека в java 6 ( blogs.oracle.com/alanb/entry/… ). Я припускаю, що якщо стеження стека неможливо, то виняток буде викинуто без сліду стека.
Шон Рейлі

1
@SeanReilly Я маю на увазі виняток, у якому немає сліду стека - це все-таки Об'єкт, який все ще потребує пам'яті. Пам'ять потрібна незалежно від того, чи надано слід стека. Чи правда, що в блоці вилову, якщо не залишилося пам'яті для створення OOM (немає пам'яті для створення навіть однієї без сліду стека), то я би спіймав нуль?
Pacerier

17
JVM може повернути кілька посилань на один статичний екземпляр винятку OOM. Тож навіть якщо ваша catchстаття намагається використовувати більше пам’яті, JVM може просто продовжувати кидати той самий екземпляр OOM знову і знову.
Грем Борланд

1
@ TheEliteGentleman Я погоджуюся, що і це дуже хороші відповіді, але JVM живе на фізичній машині. Ці відповіді не пояснювали, яким чином JVM може магічно мати достатню пам'ять, щоб завжди надати примірник OOM. «Це завжди один і той же екземпляр», здається, вирішує загадку.
Pacerier

23

Минулого разу я працював у Java та використовував налагоджувач, інспектор купи показав, що JVM виділив екземпляр OutOfMemoryError при запуску. Іншими словами, він виділяє об'єкт до того, як у вашої програми з’явиться шанс почати споживати, не кажучи вже про втрату пам'яті.


12

З специфікації JVM, розділ 3.5.2:

Якщо стеки віртуальної машини Java можна динамічно розширювати і намагатися розширити, але недостатньо пам'яті може бути доступна для здійснення розширення, або якщо недостатня пам'ять може бути доступна для створення початкового стека віртуальної машини Java для нового потоку, віртуальної Java машина кидаєOutOfMemoryError .

Кожна віртуальна машина Java повинна гарантувати, що вона викине OutOfMemoryError. Це означає, що він повинен бути здатний створити екземпляр OutOfMemoryError(або мати створений заздалегідь), навіть якщо не залишилося місця для купи.

Хоча це не повинно гарантувати, що залишилось достатньо пам'яті, щоб схопити його та надрукувати хороший стек-трек ...

Доповнення

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

Немає вимоги, щоб викинуті екземпляри OutOfMemoryErrorбули унікальними або створювалися на вимогу. JVM може підготувати рівно один екземпляр OutOfMemoryErrorпід час запуску і кидати це, коли у нього не вистачає простору купи - що є колись у звичайних умовах. Іншими словами: екземпляр того, OutOfMemoryErrorщо ми бачимо, може бути сингтом.


Я думаю, щоб реалізувати це, довелося б утримуватися від запису стеження стека, якби простір був недостатньо.
Raedwald

@Raedwald: По суті, саме це робить Oracle VM, дивіться мою відповідь.
sleske

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);
            }
        }

    }
}

Коли поштовх приходить, він не може виділити пам'ять для OOME, тому він промиває вже створені.
Buhake Sindi

1
Незначна примітка: частина вашого виводу здається не послідовною, імовірно, тому, що ви друкуєте, System.outале printStackTrace()використовуєте System.errза замовчуванням. Напевно, ви отримаєте кращі результати, використовуючи будь-який потік послідовно.
Ільмарі Каронен

@IlmariKaronen: Так, я це помітив. Це лише приклад, тому це не мало значення. Очевидно, ви б не використовували його у виробничому коді.
sleske

Правда, мені просто знадобився певний час, щоб зрозуміти, що **** відбувається, коли я вперше подивився на вихід.
Ільмарі Каронен

6

Я впевнений, що JVM буде абсолютно впевнений, що у нього є принаймні достатня кількість пам'яті, щоб викинути виняток, перш ніж у нього не залишиться пам'яті.


1
Але вони потраплять у цю ситуацію незалежно від того, скільки пам’яті зарезервовано: stackoverflow.com/questions/9261705 / ...
Pacerier

4

Винятки, що вказують на спробу порушення меж середовища керованої пам’яті, обробляються під час виконання зазначеного середовища, у цьому випадку JVM. JVM - це власний процес, який запускає ІЛ вашої програми. Якщо програма намагатиметься здійснити виклик, який розширює стек виклику за межі або виділяє більше пам'яті, ніж може резервувати JVM, сам час виконання вводить виняток, що спричинить розкручування стека викликів. Незалежно від обсягу пам’яті, яка зараз потрібна вашій програмі, або наскільки глибокий її стек викликів, JVM виділить достатню кількість пам’яті в межах власних меж процесу, щоб створити вказаний виняток і ввести його у свій код.


"JVM виділить достатню кількість пам'яті в межах власних меж процесу, щоб створити вказаний виняток", але якщо ваш код зберігає посилання на цей виняток, тому його не можна повторно використовувати, як він може створити інший? Або ви припускаєте, що у нього є спеціальний об'єкт Singleton OOME?
Raedwald

Я цього не пропоную. Якщо ваша програма захоплює всі винятки, що коли-небудь створюються, включаючи створені та введені JVM або ОС, тоді сам JVM перевищить деяку межу, встановлену ОС, і ОС закриє її для GPF або аналогічна помилка. Однак, це в першу чергу поганий дизайн; винятки слід або обробляти, а потім виходити за межі, або викидати. І вам НІКОЛИ не намагайтеся зловити і продовжувати на ДП або ДОБРЕ; окрім "прибирання", щоб ви могли вийти вишукано, нічого не можете зробити для продовження виконання в цих ситуаціях.
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: 'new', 'anewarray' або 'multianewarray'. Ця інструкція змушує JVM виконувати ряд кроків у стані, що не має пам'яті:

  1. Скажіть нативну функцію, скажімо allocate(). allocate()спроби виділити пам'ять для якогось нового примірника певного класу чи масиву.
  2. Цей запит на розподіл виявляється невдалим, тому JVM посилається на іншу вроджену функцію, скажімо doGC(), яка намагається зробити збирання сміття.
  3. Коли ця функція повертається, allocate()намагається ще раз виділити пам'ять для екземпляра.
  4. Якщо це не вдається (*), то JVM, у межах throw OOME;allocate (), просто робить a , посилаючись на OOME, який він створив при запуску. Зауважте, що не потрібно було виділяти цю ОСОБУ, вона просто посилається на неї.

Очевидно, це не буквальні кроки; вони будуть відрізнятися від JVM до JVM у реалізації, але це ідея високого рівня.

(*) Значна кількість роботи відбувається тут, перш ніж вийти з ладу. Спільний JVM спробує очистити об'єкти SoftReference, спробувати виділити їх безпосередньо в заряджену генерацію при використанні колекції поколінь і, можливо, інших речей, таких як остаточна.


3

Відповіді, які говорять про те, що СВМ буде попередньо виділено OutOfMemoryErrors, дійсно правильні.
На додаток до тестування цього, спровокувавши ситуацію поза пам'яттю, ми можемо просто перевірити купу будь-якого JVM (я використав невелику програму, яка просто спить, запустивши її за допомогою програми Oracle Hotspot JVM з оновлення Java 8) 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, файл = 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;
  }
}

Добрий пост, я прийму це як відповідь протягом тижня, щоб надати йому більшої наочності, перш ніж повернутися до попередньої відповіді.
Pacerier

У Java 8 та новіших версіях простір постійного покоління повністю видалено, і ви більше не можете вказувати розмір пам’яті для розподілу простору купи від Java 8 і вище, оскільки вони запровадили управління пам’яттю метаданих динамічного класу, що називається Metaspace. Буде добре, якщо ви зможете показати фрагмент коду, який обслуговує також PermGen, і порівняти його з Metaspace.
Бухаке Сінді

@BuhakeSindi - Я не бачу, що з цим має відношення до Постійного покоління. Нові об'єкти не виділяються в Постійному поколінні, як ви заявляєте у своїй відповіді. Ви також ніколи не згадуєте про те, що OutOfMemoryErrors попередньо виділено (що є фактичною відповіддю на питання).
Йоган Кавінг

1
Гаразд, те, що я говорю, - це те, що з Java 8 виділення об'єктів обчислюється динамічно, тоді як до того, як воно було виділене за заздалегідь визначеним простором купи, отже, можливо, воно було попередньо виділене. Навіть незважаючи на те, що OOME попередньо виділено, робиться "обчислення", щоб визначити, чи потрібно скидати OOME (отже, чому я посилаюся на специфікацію JLS).
Buhake Sindi

1
Розмір купи Java настільки ж визначений в Java 8, як і раніше. Постійне покоління було частиною купи, яка містила метадані класу, інтерновані рядки та статику класів. Він мав обмежений розмір, який потрібно було настроїти окремо від загального розміру купи. У Java 8 метадані класу були переміщені до рідної пам’яті, а інтерновані рядки та статика класу були переміщені до звичайної кучі Java (див., Наприклад, тут: infoq.com/articles/Java-PERMGEN-Removed ).
Йоган Кавінг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.