programing

OutOfMemoryError를 발생시키기에 메모리가 부족하면 어떻게됩니까?

new-time 2020. 5. 5. 21:30
반응형

OutOfMemoryError를 발생시키기에 메모리가 부족하면 어떻게됩니까?


모든 객체에는 힙 메모리가 필요하고 스택의 모든 기본 / 참조에는 스택 메모리가 필요하다는 것을 알고 있습니다.힙에 객체를 만들려고하는데 메모리가 충분하지 않으면 JVM 이 힙에

java.lang.OutOfMemoryError

생성 하여 나에게 던집니다.따라서 암시 적으로 이것은 시작시 JVM이 예약 한 일부 메모리가 있음을 의미합니다.이 예약 된 메모리가 모두 사용되면 (아래에서 논의 내용을 읽음) JVM에

java.lang.OutOfMemoryError

인스턴스를 작성하기에 충분한 메모리가 힙에없는 경우 어떻게됩니까?그냥 걸어 요? 아니면 OOM 인스턴스에

null

대한 기억이 없기 때문에 그는 나를 던질 것

new

입니까?

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

JVM에는 실제로 메모리가 부족하지 않습니다. 힙 스택의 메모리 계산을 미리 수행합니다.

JVM

구조, 3 장 , 섹션 3.5.2는 다음과 같이 말합니다.

  • Java 가상 머신 스택을 동적으로 확장 할 수 있고 확장을 시도했지만 확장에 영향을 줄 수있는 메모리가 부족하거나 새 스레드에 대한 초기 Java 가상 머신 스택을 작성하는 데 메모리가 부족한 경우 Java 가상 기계를 던졌습니다 OutOfMemoryError.

들어

, 제 3.5.3.

  • 계산에 자동 스토리지 관리 시스템에서 사용할 수있는 것보다 많은 힙이 필요한 경우 JVM (Java Virtual Machine)은 OutOfMemoryError.

따라서 객체를 할당하기 전에 미리 계산을 수행합니다.


JVM이 Permanent Generation region (또는 PermSpace)이라는 메모리의 오브젝트에 메모리를 할당하려고합니다. JVM이 가비지 콜렉터를 호출하여 여유 공간을 할당 및 할당 한 후에도 할당에 실패하면가 발생합니다

OutOfMemoryError

. 예외조차도 메모리 공간이 필요하므로 오류가 무한대로 발생합니다.

더 읽을 거리.

? 또한

OutOfMemoryError

다른 JVM 구조 에서 발생할 수 있습니다

.


Graham Borland는 옳은 것 같습니다

. 적어도

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


대부분의 런타임 환경은 메모리 부족 상태를 처리하기에 충분한 메모리가 시작시 사전 할당되거나 예약됩니다. 제정신 JVM 구현이 대부분 그렇게 할 것이라고 생각합니다.


마지막으로 Java에서 작업하고 디버거를 사용했을 때 힙 관리자는 JVM이 시작시 OutOfMemoryError 인스턴스를 할당했음을 보여주었습니다. 다시 말해, 프로그램이 메모리를 다 쓰지 않고 소비를 시작하기 전에 개체를 할당합니다.


JVM 스펙, 3.5.2 장에서 :

Java 가상 머신 스택을 동적으로 확장 할 수 있고 확장을 시도했지만 확장에 영향을 줄 수있는 메모리가 부족하거나 새 스레드에 대한 초기 Java 가상 머신 스택을 작성하는 데 메모리가 부족한 경우

Java 가상 기계를 던졌습니다OutOfMemoryError

.

모든 Java Virtual Machine은을 던질 것을 보장해야합니다

OutOfMemoryError

. 즉,

OutOfMemoryError

힙 공간이 없어도 인스턴스를 만들 거나 미리 만들 수 있어야합니다.보장 할 필요는 없지만 메모리를 확보하고 멋진 스택 추적을 인쇄하기에 충분한 메모리가 남아 있는지 확인하십시오.

부가

JVM에 둘 이상을 던져야하는 경우 힙 공간이 부족할 수 있음을 보여주는 코드를 추가했습니다

OutOfMemoryError

. 그러나 이러한 구현은 위의 요구 사항을 위반합니다.던져진 인스턴스

OutOfMemoryError

가 고유하거나 필요시 생성 될 필요는 없습니다. JVM은

OutOfMemoryError

시작 하는 동안 정확히 하나의 인스턴스를 준비 하고 힙 공간이 부족할 때마다 (일반 환경에서는 한 번)이를 던질 수 있습니다. 다시 말해,

OutOfMemoryError

우리가 본 사례 는 싱글 톤일 수 있습니다.


재미있는 질문 :-). 다른 사람들은 이론적 측면에 대한 좋은 설명을 들었지만, 나는 그것을 시도하기로 결정했습니다. 이것은 Oracle JDK 1.6.0_26, Windows 7 64 비트에 있습니다.

테스트 설정

메모리를 소진하는 간단한 프로그램을 작성했습니다 (아래 참조).이 프로그램은 static을 만들고

java.util.List

OOM이 throw 될 때까지 새로운 문자열을 채워 넣습니다. 그런 다음 그것을 잡고 끝없는 루프 (가난한 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);
            }
        }

    }
}

JVM은 메모리가 부족하기 전에 예외를 throw하기에 충분한 메모리가 있는지 확인합니다.


관리 메모리 환경의 경계를 위반하려는 시도를 나타내는 예외는 해당 환경의 런타임 (이 경우 JVM)에 의해 처리됩니다. JVM은 애플리케이션의 IL을 실행하는 자체 프로세스입니다. 프로그램이 호출 스택을 한계 이상으로 확장하거나 JVM이 예약 할 수있는 것보다 많은 메모리를 할당하는 호출을 시도하면 런타임 자체에 예외가 발생하여 호출 스택이 풀릴 수 있습니다. 프로그램에 현재 필요한 메모리 양이나 호출 스택의 깊이에 관계없이 JVM은 해당 예외를 작성하고 코드에 삽입하기 위해 자체 프로세스 범위 내에서 충분한 메모리를 할당합니다.


JVM이 Java 프로그램을 실행하는 JVM이 예약 한 가상 메모리와 JVM이 기본 프로세스로 실행되는 호스트 OS의 기본 메모리를 혼동하는 것 같습니다. 머신의 JVM이 JVM이 Java 프로그램을 실행하기 위해 예약 한 메모리가 아닌 OS가 관리하는 메모리에서 실행 중입니다.더 읽을 거리 :

마지막 으로 스택 트레이스를 인쇄하기 위해 java.lang.Error (및 그 하위 클래스)

잡으려고

하면 유용한 정보가 제공되지 않을 수 있습니다. 대신 힙 덤프를 원합니다.


기능적으로 @Graham Borland의 답변을 더 명확하게하기 위해 JVM은 시작시 이것을 수행합니다.

private static final OutOfMemoryError OOME = new OutOfMemoryError();

나중에 JVM은 'new', 'anewarray'또는 'multianewarray'와 같은 Java 바이트 코드 중 하나를 실행합니다. 이 명령은 JVM이 메모리 부족 상태에서 여러 단계를 수행하게합니다.

  1. 네이티브 함수를 호출하십시오 (예 :) allocate(). allocate()특정 클래스 또는 배열의 새 인스턴스에 대한 메모리 할당을 시도합니다.
  2. 해당 할당 요청이 실패하므로 JVM은 doGC()가비지 수집을 시도하는 다른 기본 함수 (예 :)를 호출합니다 .
  3. 해당 함수가 반환되면 allocate()인스턴스에 메모리를 다시 한 번 할당하려고합니다.
  4. 실패하면 (*), throw OOME;assign () 내의 JVM 은 시작시 인스턴스화 한 OOME을 참조하여 간단히 a 를 수행합니다. OOME을 할당 할 필요는 없으며 단지 OOME을 참조합니다.

분명히, 이것은 문자적인 단계가 아닙니다. 구현에 따라 JVM마다 다를 수 있지만 이것은 고급 아이디어입니다.(*) 실패하기 전에 여기서 상당한 양의 작업이 발생합니다. JVM은 SoftReference 오브젝트를 지우고 세대 콜렉터를 사용하는 경우 테너 링 된 세대에 직접 할당을 시도하며 가능하면 다른 마무리 작업을 시도합니다.


JVM이 사전 할당한다는 답변

OutOfMemoryErrors

은 실제로 정확합니다.

 

메모리 부족 상황을 유발하여 이것을 테스트하는 것 외에도 모든 JVM의 힙을 확인할 수 있습니다 (자바를 수행하는 작은 프로그램을 사용하여 Java 8 업데이트 31의 Oracle Hotspot JVM을 사용하여 실행했습니다).사용하면

jmap

OutOfMemoryError의 인스턴스가 9 개있는 것으로 보입니다 (메모리가 충분하더라도).

> jmap-히스 토 12103 | grep OutOfMemoryError
 71 : 9288 java.lang.OutOfMemoryError
170 : 1 32 [Ljava.lang.OutOfMemoryError;

그런 다음 힙 덤프를 생성 할 수 있습니다.

> jmap-덤프 : 형식 = b, 파일 = heap.hprof 12315

 

Eclipse Memory Analyzer를

사용하여이를 엽니 다 . 여기서 OQL 쿼리는 JVM이 실제로

OutOfMemoryErrors

가능한 모든 메시지에 대해 사전 할당 된 것으로 보입니다 .

여기에 이미지 설명을 입력하십시오

실제로이를 사전 할당하는 Java 8 핫스팟 JVM의 코드는

here

에서 찾을 수 있으며 다음과 같습니다 (일부 부분은 생략 됨).

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

참고 URL :

https://stackoverflow.com/questions/9261705/what-happens-when-theres-insufficient-memory-to-throw-an-outofmemoryerror

반응형