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

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

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

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

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

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

try {
    Object o = new Object();
    // and operations which require memory (well.. that 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 going to run out in the end
                    }
                }
            }
        }
    }
}

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

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

Ответ 1

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

Структура JVM, глава 3, раздел 3.5.2 гласит:

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

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

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

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


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

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

Ответ 2

Грэм Борланд, кажется, прав: по крайней мере моя 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

BTW, 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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

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

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

Добавление

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

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

Ответ 6

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

Настройка тестирования

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

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

Результат теста

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

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

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

Выход

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

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

    }
}

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

private static final OutOfMemoryError OOME = new OutOfMemoryError();

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

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

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

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

Ответ 11

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

Используя 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 для всех возможные сообщения:

enter image description here

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

...
// 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;
  }
}