Я знаю, что каждый объект требует кучи памяти, и каждый примитив / ссылка в стеке требует стековой памяти.
Когда я пытаюсь создать объект в куче, и для этого недостаточно памяти, 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);
}
}
источник
OutOfMemoryException
а затем сделать что-то, что включало создание большого буфера ...OutOfMemoryError
и сохранит ссылку на нее. Выясняется, что перехватOutOfMemoryError
не так полезен, как можно подумать, потому что вы почти ничего не можете гарантировать о состоянии вашей программы при перехвате. См stackoverflow.com/questions/8728866/...Ответы:
JVM никогда не хватает памяти. Это делает вычисление памяти стека кучи заранее.
Структура виртуальной машины Java, глава 3 , раздел 3.5.2 гласит:
Для кучи , раздел 3.5.3.
Таким образом, он делает вычисления заранее, прежде чем делать выделение объекта.
Что происходит, так это то, что JVM пытается выделить память для объекта в памяти, называемой областью постоянного поколения (или PermSpace). Если распределение завершается неудачно (даже после того, как JVM вызовет сборщик мусора, чтобы попытаться выделить свободное пространство), он выбрасывает
OutOfMemoryError
. Даже для исключений требуется место в памяти, поэтому ошибка будет генерироваться бесконечно.Дальнейшее чтение. ? Кроме того,
OutOfMemoryError
могут возникнуть различные структуры JVM.источник
Грэм Борланд, кажется, прав : по крайней мере, моя JVM, по-видимому, повторно использует OutOfMemoryErrors. Чтобы проверить это, я написал простую тестовую программу:
Запуск это производит этот вывод:
Кстати, JVM, на которой я работаю (на Ubuntu 10.04), выглядит так:
Редактировать: я пытался увидеть, что произойдет, если я заставил JVM полностью запустить из памяти, используя следующую программу:
Как выяснилось, кажется, что петля навсегда. Однако, что любопытно, попытка завершить программу с помощью 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
источник
n
OOM и повторно использовать один из них впоследствии, чтобы всегда можно было выбросить OOM . JVM от Sun / Oracle не поддерживает хвостовую рекурсию вообще IIRC?n
кадры в стеке, и он в конечном итоге создает и уничтожает кадрыn+1
на вечность, создавая впечатление бесконечной работы.Большинство сред времени выполнения предварительно выделяют при запуске или иным образом резервируют достаточное количество памяти, чтобы справиться с ситуациями нехватки памяти. Я думаю, что большинство нормальных реализаций JVM сделают это.
источник
catch
предложение пытается использовать больше памяти, JVM может просто снова и снова генерировать один и тот же экземпляр OOM.В прошлый раз, когда я работал в Java и использовал отладчик, инспектор кучи показал, что JVM выделяет экземпляр OutOfMemoryError при запуске. Другими словами, он распределяет объект до того, как ваша программа сможет начать потреблять, не говоря уже об исчерпании памяти.
источник
Из спецификации JVM, глава 3.5.2:
Каждая виртуальная машина Java должна гарантировать, что она выдаст
OutOfMemoryError
. Это означает, что он должен быть способен создать экземплярOutOfMemoryError
(или иметь созданный заранее), даже если не осталось места в куче.Хотя это не должно гарантировать, что достаточно памяти, чтобы перехватить ее и напечатать красивую трассировку стека ...
прибавление
Вы добавили некоторый код, чтобы показать, что JVM может исчерпать пространство кучи, если ему придется выбросить более одного
OutOfMemoryError
. Но такая реализация нарушила бы требование сверху.Не требуется, чтобы брошенные экземпляры
OutOfMemoryError
были уникальными или создавались по требованию. JVM может подготовить ровно один экземплярOutOfMemoryError
во время запуска и выбросить его всякий раз, когда у него заканчивается свободное пространство в куче, что обычно бывает в обычной среде. Другими словами: случай,OutOfMemoryError
который мы видим, мог быть единственным.источник
Интересный вопрос :-). В то время как другие дали хорошие объяснения теоретических аспектов, я решил попробовать это. Это на 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 как можно дольше, но если нажать толчок, он просто повторно использует тот же экземпляр, а не будет ничего бросать.
Вывод
Примечание: я обрезал некоторые следы стека, чтобы сделать вывод более легким для чтения ("[...]").
Программа
источник
System.out
ноprintStackTrace()
используетеSystem.err
по умолчанию. Вы, вероятно, получите лучшие результаты, если будете последовательно использовать любой поток.Я почти уверен, что JVM будет абсолютно уверена, что у нее по крайней мере достаточно памяти, чтобы вызвать исключение, прежде чем ей не хватит памяти.
источник
Исключения, указывающие на попытку нарушить границы среды управляемой памяти, обрабатываются средой выполнения указанной среды, в данном случае JVM. JVM - это собственный процесс, в котором выполняется IL вашего приложения. Если программа попытается сделать вызов, который расширяет стек вызовов за пределы или выделит больше памяти, чем может зарезервировать JVM, среда выполнения сама выдаст исключение, что приведет к отмене стека вызовов. Независимо от объема памяти, в которой ваша программа нуждается в данный момент, или глубины стека вызовов, JVM выделит достаточно памяти в пределах собственных границ процесса, чтобы создать указанное исключение и внедрить его в ваш код.
источник
Вы, кажется, путаете виртуальную память, зарезервированную JVM, в которой JVM запускает программы Java с собственной памятью хост-ОС, в которой JVM запускается как собственный процесс. JVM на вашем компьютере работает в памяти, управляемой ОС, а не в памяти, зарезервированной JVM для запуска программ Java.
Дальнейшее чтение:
И как последнее замечание, пытаясь поймать на java.lang.Error (и его потомков классов), чтобы напечатать StackTrace не может дать вам никакой полезной информации. Вы хотите кучу свалки вместо этого.
источник
Чтобы дополнительно уточнить ответ @Graham Borland, функционально JVM делает это при запуске:
Позже JVM выполняет один из следующих байт-кодов Java: «новый», «anewarray» или «multianewarray». Эта инструкция заставляет JVM выполнить несколько шагов в состоянии нехватки памяти:
allocate()
.allocate()
пытается выделить память для какого-то нового экземпляра определенного класса или массива.doGC()
, , которая пытается выполнить сборку мусора.allocate()
пытается выделить память для экземпляра еще раз.throw OOME;
allocate , ссылаясь на OOME, который был создан при запуске. Обратите внимание, что ему не нужно выделять этот OOME, он просто ссылается на него.Очевидно, это не буквальные шаги; В зависимости от реализации они будут варьироваться от JVM к JVM, но это идея высокого уровня.
(*) Значительный объем работы происходит здесь до сбоя. JVM будет пытаться очистить объекты SoftReference, попытаться выделить непосредственно в поколение с правами владения при использовании сборщика поколений и, возможно, другие вещи, такие как финализация.
источник
Ответы, которые говорят, что JVM будет предварительно распределять
OutOfMemoryErrors
, действительно верны.В дополнение к проверке этого путем провоцирования ситуации нехватки памяти мы можем просто проверить кучу любой JVM (я использовал небольшую программу, которая просто спит, запустив ее с помощью Oracle Hotspot JVM из Java 8 update 31).
С помощью
jmap
мы видим, что, похоже, существует 9 экземпляров OutOfMemoryError (хотя у нас достаточно памяти):Затем мы можем сгенерировать дамп кучи:
и откройте его с помощью Eclipse Memory Analyzer , где OQL-запрос показывает, что JVM, похоже, предварительно выделяет
OutOfMemoryErrors
все возможные сообщения:Код для Java 8 Hotspot JVM, который фактически предварительно распределяет их, можно найти здесь , и выглядит так (некоторые части опущены):
и этот код показывает, что JVM сначала попытается использовать одну из предварительно выделенных ошибок с пространством для трассировки стека, а затем вернется к одной без трассировки стека:
источник
Metaspace
. Было бы неплохо, если бы вы могли показать фрагмент кода, который обслуживает также PermGen, и сравнить его с Metaspace.