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


207

모든 객체에는 힙 메모리가 필요하고 스택의 모든 기본 / 참조에는 스택 메모리가 필요하다는 것을 알고 있습니다.

힙에 객체를 만들려고하는데 메모리가 충분하지 않으면 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);
    }
}

2
귀하의 답변은 JVM에 크게 의존합니다
MozenRath

23
한때 (90 년대에) 사용했던 하나의 전화 라이브러리 OutOfMemoryException는 큰 버퍼를 만드는 것과 관련된 것을 수행 하는 데 사용되었습니다 .
Tom Hawtin-tackline

@ TomHawtin-tackline 그 작업과 관련된 작업이 다른 OOM을 발생시키는 경우
Pacerier

38
휴대 전화와 마찬가지로 배터리가 부족하지만 "배터리가 부족합니다"라는 스팸 메일을 유지할 수있는 충분한 배터리가 있습니다.
Kazuma

1
"이 예약 된 메모리가 모두 사용되면 어떻게됩니까?": 프로그램이 첫 번째 메모리를 잡고 OutOfMemoryError참조를 보유한 경우에만 발생할 수 있습니다. a를 잡는 OutOfMemoryError것이 생각하는 것만 큼 유용하지 않다는 것을 알 수 있습니다 . 프로그램을 잡는 데 대한 프로그램 상태에 대해 거의 아무것도 보증 할 수 없기 때문 입니다. 참조 stackoverflow.com/questions/8728866/...
Raedwald

답변:


145

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 구조 에서 발생할 수 있습니다 .


10
내 말은 그렇지만 Java 가상 머신에 OutOfMemoryError를 발생시키는 데 메모리가 필요하지 않습니까? OOM을 던질 메모리가 없으면 어떻게됩니까?
Pacerier

5
그러나 JVM이 동일한 OOM 인스턴스에 대한 참조를 반환하지 않으면 결국 예약 메모리가 부족하다는 데 동의하지 않습니까? (질문의 코드에서
알 수 있듯이

1
여기에 Graham의 의견에 대한 언급을
드리겠습니다

2
명시된 극단적 인 경우와 원자력 발전소에서 VM이 싱글 톤의 OOM 예외를 유지한다면 좋을 것입니다.
John K

8
@ JohnK : 우주 왕복선과 마찬가지로 보잉 757이 Java로 프로그래밍되지 않은 것처럼 원자력 발전소가 Java로 프로그래밍되지 않기를 바랍니다.
Dietrich Epp

64

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


버전 "1.7.0_01"자바 핫스팟 (TM) 64 비트 서버 VM과 나를 위해 같은 멋진 테스트,
Pacerier

흥미 롭군 즉 (... 그것은 어떤 꼬리 재귀 스택 재사용을하고 있지만, 모든 메모리를 정리하는 것은 매우 충분하지 것처럼) JVM이 완전히 꼬리 재귀를 이해하지 못하는 것처럼 ... 보이
Izkata

얼마나 많은 다른 것들을 볼 수 있도록 코드를 수정했습니다. 나는 항상 else 분기를 정확히 5 번 실행합니다.
Irfy

@Izkata : OOM을 미리 할당 n하고 나중에 그 중 하나를 재사용하여 OOM을 항상 던질 수 있도록하는 것은 의식적인 결정이라고 말하고 싶습니다 . 썬 / 오라클의 JVM은 모든 IIRC에서 테일 재귀를 지원하지 않습니까?
Irfy

10
@Izkata JVM이 메모리가 부족한 후 JVM에서 지속적으로 동일한 OOM을 던지기 때문에 루프가 끝없이 실행되고 있습니다. 따라서 n스택에 프레임이 n+1있으며 영원히 프레임 을 만들고 파괴 하여 끝없이 달리는 것처럼 보입니다.
Irfy

41

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


1
stackoverflow.com/questions/9261215/… : True이지만 JVM이 100 단위의 메모리를 예약하고 첫 번째 OOM에서 1 단위를 소비 한 경우 OOM catch 블록에서 e.printStackTrace ()를 수행하면 어떻게됩니까? e.printStackTrace ()에도 메모리가 필요합니다. 그런 다음 JVM은 다른 OOM (98 단위 남음)을 던지기 위해 다른 메모리 단위를 소비하고 e.printStackTrace ()로 그것을 잡아서 JVM이 다른 OOM (97 단위 남음)을 던지면 잘 잡히고 싶습니다. ..
Pacerier

3
이것이 바로 OOME이 스택 트레이스를 포함시키는 데 사용한 적이없는 이유입니다. 스택 트레이스는 메모리를 사용합니다! OOME은 java 6 ( blogs.oracle.com/alanb/entry/… ) 에 스택 추적을 포함시키기 시작했습니다 . 스택 추적이 불가능하면 스택 추적없이 예외가 발생한다고 가정합니다.
Sean Reilly

1
@SeanReilly 스택 추적이없는 예외는 여전히 메모리가 필요한 Object입니다. 스택 추적 제공 여부에 관계없이 메모리가 필요합니다. catch 블록에서 OOM을 만드는 데 남은 메모리가 없으면 (스택 추적없이 하나를 만들 수있는 메모리가없는 경우) null을 잡을 수 있습니까?
Pacerier

17
JVM은 OOM 예외의 단일 정적 인스턴스에 대한 여러 참조를 리턴 할 수 있습니다. 따라서 catch절에서 더 많은 메모리를 사용하려고 시도 하더라도 JVM은 동일한 OOM 인스턴스를 계속 반복해서 던질 수 있습니다.
Graham Borland

1
@ TheEliteGentleman 나는 이것들도 매우 좋은 대답이지만 JVM이 실제 머신에 살고 있다는 데 동의합니다.이 답변은 JVM이 마술처럼 항상 OOM 인스턴스를 제공하기에 충분한 메모리를 가질 수있는 방법을 설명하지 못했습니다. "항상 같은 사례" 는 수수께끼를 해결하는 것으로 보인다.
Pacerier

23

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


12

JVM 스펙, 3.5.2 장에서 :

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

모든 Java Virtual Machine은 OutOfMemoryError. 즉, OutOfMemoryError힙 공간이 없어도 인스턴스를 만들 거나 미리 만들 수 있어야합니다.

보장 할 필요는 없지만 메모리를 잡아서 멋진 스택 트레이스를 인쇄 할 수있는 충분한 공간이 남아 있습니다 ...

덧셈

JVM에 둘 이상을 던져야하는 경우 힙 공간이 부족할 수 있음을 보여주는 코드를 추가했습니다 OutOfMemoryError. 그러나 이러한 구현은 위의 요구 사항을 위반합니다.

던져진 인스턴스 OutOfMemoryError가 고유하거나 필요시 생성 될 필요는 없습니다. JVM은 OutOfMemoryError시작 하는 동안 정확히 하나의 인스턴스를 준비 하고 힙 공간이 부족할 때마다 (일반 환경에서는 한 번)이를 던질 수 있습니다. 다시 말해, OutOfMemoryError우리가 본 사례 는 싱글 톤일 수 있습니다.


이것을 구현하려면 공간이 좁 으면 스택 추적을 기록하지 않아야합니다.
Raedwald

@Raedwald : 사실, 이것은 Oracle VM이하는 일입니다. 제 대답을보십시오.
sleske

11

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

테스트 설정

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

이 프로그램은 static을 만들고 java.util.ListOOM이 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);
            }
        }

    }
}

푸시는 밀려 오면 OOME에 메모리를 할당 할 수 없으므로 이미 만들어진 메모리를 플러시합니다.
Buhake Sindi

1
마이너 주 : 출력의 일부는, 순서가 될 아마도 당신 때문에있는 거로 인쇄 것으로 보인다 System.out하지만 printStackTrace()사용 System.err기본적으로. 스트림을 일관되게 사용하면 더 나은 결과를 얻을 수 있습니다.
Ilmari Karonen

@IlmariKaronen : 그렇습니다. 이것은 단지 예일 뿐이므로 중요하지 않습니다. 분명히 프로덕션 코드에서는 사용하지 않을 것입니다.
sleske 2012

사실, 출력을 처음 보았을 때 ****이 무슨 일인지 알아내는 데 시간이 걸렸습니다.
Ilmari Karonen


4

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


"JVM은 해당 예외를 생성하기 위해 자체 프로세스 범위 내에서 충분한 메모리를 할당했을 것입니다."그러나 해당 코드에 해당 예외에 대한 참조가 유지되어 재사용 할 수 없으면 어떻게 다른 예외를 생성 할 수 있습니까? 아니면 특별한 Singleton OOME 객체를 제안하고 있습니까?
Raedwald

나는 그것을 제안하지 않습니다. 프로그램이 JVM 또는 OS에 의해 작성되고 주입 된 예외를 포함하여 지금까지 작성된 모든 예외에 걸리고 중단되는 경우 결국 JVM 자체가 OS에 의해 설정된 일부 경계를 초과하며 OS는 GPF 또는 비슷한 오류. 그러나 그것은 처음에는 나쁜 디자인입니다. 예외는 처리 된 다음 범위를 벗어나거나 발생해야합니다. 그리고 당신은 절대로 SOE 나 OOME을 따라 잡으려고 노력하지 않아야합니다. "정리"이외의 다른 기능을 사용하면 정상적으로 종료 할 수 있습니다. 이러한 상황에서 실행을 계속할 수있는 방법은 없습니다.
KeithS

"처음에는 나쁜 디자인": 끔찍한 디자인. 그러나 놀랍게도 JVM 이 사양에 실패한 경우 사양을 준수합니까?
Raedwald

4

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

더 읽을 거리 :

마지막 으로 스택 트레이스를 인쇄하기 위해 java.lang.Error (및 그 하위 클래스) 를 잡으려고 하면 유용한 정보가 제공되지 않을 수 있습니다. 대신 힙 덤프를 원합니다.


4

기능적으로 @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 오브젝트를 지우고 세대 콜렉터를 사용하는 경우 테너 링 된 세대에 직접 할당을 시도하며 가능하면 다른 마무리 작업을 시도합니다.


3

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

좋은 게시물, 나는 이전 답변으로 되돌아 가기 전에 더 많은 가시성을 제공하기 위해 이것을 일주일 동안 답변으로 받아들입니다.
Pacerier

Java 8 이상에서는 Permanent Generation Space가 완전히 제거되었으며 라는 동적 클래스 메타 데이터 메모리 관리 기능이 도입되어 더 이상 Java 8 이상의 힙 공간 할당 메모리 크기를 지정할 수 없습니다 Metaspace. PermGen 용 코드를 보여주고 메타 스페이스와 비교할 수 있다면 좋을 것입니다.
Buhake Sindi

@BuhakeSindi-영구 세대가 이것과 어떤 관련이 있는지 모르겠습니다. 귀하의 답변에 명시된대로 새 세대는 영구 생성에 할당되지 않습니다. 또한 OutOfMemoryErrors가 사전 할당되어 있다는 사실 (질문에 대한 실제 답변)에 대해서는 언급하지 않습니다.
Johan Kaving

1
좋아, 내가 말하는 것은 Java 8부터 객체 할당이 동적으로 계산되는 반면 사전 정의 된 힙 공간에 할당되기 전에 미리 할당되어 있다는 것입니다. OOME이 사전 할당되어 있어도 OOME이 발생해야하는지 여부를 결정하기 위해 "계산"이 수행됩니다 (따라서 JLS 사양을 참조하는 이유).
Buhake Sindi

1
Java 힙 크기는 이전과 마찬가지로 Java 8에서 사전 정의되어 있습니다. 영구 생성은 클래스 메타 데이터, 인터 턴 된 문자열 및 클래스 정적을 포함하는 힙의 일부였습니다. 총 힙 크기와 별도로 조정해야하는 크기가 제한되었습니다. Java 8에서 메타 데이터 클래스는 기본 메모리로 이동했으며 인턴 된 문자열 및 클래스 정적은 일반 Java 힙으로 이동되었습니다 (예 : infoq.com/articles/Java-PERMGEN-Removed 참조 ).
Johan Kaving
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.