[JVM 심층 이해]: OutOfMemoryError 이상 요약

12697 단어 jvm유난히
JVM 메모리 영역에서는 프로그램 카운터를 제외한 나머지 몇몇 런타임 영역에서 OutOfMemory Error(OOM) 예외가 발생할 수 있습니다.본고는 OOM이상에 대해 정리하고 코드를 통해 JVM규범에 기술된 운행시 구역에 저장된 내용을 검증한다.이러한 영역의 OOM 이상을 초래할 수 있는 코드를 이해하면 작업 중에 이상 코드에 따라 메모리의 어느 영역을 포지셔닝할 수 있습니다.
Sun의 HotSpot 가상 머신을 기반으로 Eclipse에서 Run/Arguments에서 가상 머신 시작 매개 변수를 설정할 수 있습니다. 이 매개 변수는 실험 결과에 직접적인 영향을 미치므로 무시할 수 없습니다.

Java 더미 오버플로우


Java 더미는 대상을 저장하는 실례에 사용됩니다. 대상을 끊임없이 만들고 GC Roots에서 대상 사이에 도달할 수 있는 경로를 확보하여 쓰레기 회수 메커니즘이 이 대상을 제거하는 것을 피하고 대상 수량이 최대 더미 용량 제한에 도달하면 넘침이 발생합니다.
Java 스택의 크기를 20MB로 제한하고 최소값과 최대값이 같다(확장 불가).가상 머신에 메모리 넘침 이상이 발생했을 때 Dump는 사후 분석을 위해 현재 메모리 덤프 스냅샷을 내보냅니다.
package com.jvm.OutOfMemoryError;

import java.util.List;
import java.util.ArrayList;

/** * Java    * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */

public class HeapOOM {

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while(true) {
            // list    ,  Full GC    
            list.add(new OOMObject());
        }
    }

    static class OOMObject {

    }
}

실행 결과:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid7768.hprof ...
Heap dump file created [27987840 bytes in 0.142 secs]

이런 이상은 비교적 흔히 볼 수 있다. 일반적으로 메모리 이미지 분석 도구(예를 들어 Eclipse Memory Analyzer)를 통해 Dump에서 나온 덤프 메모리 스냅 사진을 분석하는데 메모리 유출인지 메모리 넘침인지 확인한다.
  • 메모리 유출은 유출 대상이 GCRoots에 대한 인용체인을 보고 유출 코드의 위치를 정한다.
  • 메모리 넘침이 누설되지 않으면 메모리에 있는 대상이 모두 살아있어야 한다. JVM더미 파라미터(-Xmx와 -Xms)를 검사하고 파라미터를 확대하며 코드에 일부 대상의 생명주기가 너무 길고 보유 상태가 너무 긴 것이 있는지 검사하여 프로그램 운행 기간의 메모리 소모를 줄인다.

  • 가상 머신 및 로컬 메소드 스택 오버플로우


    HotSpot은 가상 머신 스택과 로컬 메소드 스택을 구분하지 않으며 스택 용량은 -Xss 매개 변수로만 설정할 수 있습니다.
  • StackOverFlow: 스레드 신청의 창고 깊이가 허용된 최대 깊이
  • 를 초과
  • OutOfMemoryError: 가상 머신 확장 시 충분한 메모리 공간을 신청할 수 없음
  • StackOverFlow의 경우: 반복 호출 방법으로 대량의 로컬 변수를 정의하고 이 방법 프레임의 로컬 변수 테이블의 길이를 늘립니다.
    package com.jvm.OutOfMemoryError;
    /** *              StackOverFlow * VM Args:-Xss128k */
    
    public class JavaVMStackSOF {
    
        private int stackLength = 1;
    
        //       ,         ,               
        public void stackLeak() {
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) {
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println("stack length: " + oom.stackLength);
                throw e;
            }
        }
    
    }
    

    실행 결과:
    stack length: 999
    Exception in thread "main" java.lang.StackOverflowError
        at com.jvm.OutOfMemoryError.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:26)
        at com.jvm.OutOfMemoryError.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:26)
        at com.jvm.OutOfMemoryError.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:26)
        ......

    OutOfMemoryError: 스택 공간이 충분한지 여부에 관계없이 멀티스레드에서 메모리가 넘칩니다.모든 스레드의 창고에 할당된 메모리가 클수록 (매개 변수 - Xss) 구축할 수 있는 스레드의 수량이 적고, 스레드를 구축할 때 남은 메모리를 소모하기 쉬우며, 메모리가 넘치기 쉽다.이 경우 스레드 수를 줄이지 못하거나 64비트 가상 머신을 교체할 때 가장 많은 스택 용량을 줄이고 스택 용량을 줄이면 더 많은 스레드를 교체할 수 있다.
    주의: Windows 플랫폼 가상기에서 자바의 스레드는 운영체제의 내부 스레드에 비추어 실행되며, 아래의 코드 실행은 시스템의 가사를 초래할 수 있습니다!
    package com.jvm.OutOfMemoryError;
    /** *              * * !!!!!  !!!!! *              !!!! * * VM Args:-Xss2M(       ) */
    
    public class JavaVMStackOOM {
    
        private void dontStop() {
            while(true) {
    
            }
        }
    
        //              OutOfMemoryError
        public void stackLeakByThread() {
            while(true) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
    
        public static void main(String[] args) {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    }
    

    방법 영역과 운행 상수 탱크 넘침


    운행 시간 탱크


    JDK1.7 점차적으로'영구대'를 걷기 시작하는데 아래의 토론은 실제 영향을 시험할 수 있다.
    String.()는 이 String 대상의 내용과 같은 문자열이 실행 중인 경우 상수 탱크에 있는 문자열의 인용을 되돌려주는 Native 방법입니다.없으면 이 String 내용과 같은 문자열을 상수 풀에 만들고 상수 풀에서 만든 문자열의 인용을 되돌려줍니다.JDK7의 인터넷 () 방법은 상량 탱크에 이 문자열이 없을 때 상량 탱크에서 이 String 내용과 같은 문자열을 만드는 것이 아니라 상량 탱크에 기록된 더미에서 처음 나타나는 이 문자열의 인용으로 바꾸어 이 인용을 되돌려줍니다.
    JDK에서 1.6 이전에 상수 풀은 영구 세대에 분배되었고 다음 코드는 JDK1.6에서 실행하고 나서야 메모리 오버플로우가 발생합니다. JDK1.7 및 그 다음 버전이 실행되면 사순환이 발생합니다.
    package com.jvm.OutOfMemoryError;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /** *          * * jdk1.7          “    ”,jdk1.6               * -XX:PermSize=10M -XX:MaxPermSize=10M (           ) */
    
    public class RuntimeConstantPoolOOM {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            int i = 0;
            while(true) {
                // list    ,  Full GC    
                list.add(String.valueOf(i++).intern());
            }
        }
    }
    

    실행 결과:
    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        ......

    방법구


    방법구는Class와 관련된 정보를 저장하는 데 사용되며, 실행할 때 대량의 클래스가 방법구를 채우면 방법구의 메모리가 넘칠 수 있습니다.예를 들어 주류 프레임워크인 Spring,Hibernate가 대량의 클래스를 강화할 때 CGLib 바이트 코드를 이용하여 동적 클래스를 생성한다.대량의 JSP 또는 동적 JSP(JSP가 처음 실행될 때 Java 클래스로 컴파일되어야 함).
    다음은 CGLib 동적 생성 클래스로 인한 메소드 오버플로우입니다.
    package com.jvm.OutOfMemoryError;
    
    import java.lang.reflect.Method;
    import com.jvm.OutOfMemoryError.HeapOOM.OOMObject;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    /** *       * -XX:PermSize=10M -XX:MaxPermSize=10M */
    
    public class JavaMethodAreaOOM {
        public static void main(String[] args) {
            while(true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
    
                    @Override
                    public Object intercept(Object obj, Method m, Object[] objs, MethodProxy proxy) throws Throwable {
                        // TODO Auto-generated method stub
                        return proxy.invokeSuper(obj, objs);
                    }
                });
                enhancer.create();
            }
        }
    }
    

    실행 결과:
    Caused by: java.lang.OutOfMemoryError: PermGen space
        at java.lang.ClassLoader.defineClass1(Native Method)
        ......

    로컬 직접 메모리 오버플로우


    자바 가상기는 파라미터-XX:MaxDirectMemorySize를 통해 이 컴퓨터의 직접 메모리 사용 가능한 크기를 설정할 수 있으며, 지정하지 않으면 기본적으로 자바 더미 메모리 크기와 같습니다.JDK에서 반사로 Unsafe 클래스를 가져올 수 있습니다. (Unsafe의 getUnsafe () 방법은 클래스 마운트기 Bootstrap을 시작해야만 실례를 되돌릴 수 있습니다.) 이 컴퓨터의 직접 메모리를 조작할 수 있습니다.-XX:MaxDirectMemorySize=10M을 사용하여 최대 사용 가능한 네이티브 메모리 크기를 10MB로 제한합니다.
    import java.lang.reflect.Field;
    
    public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field unsafeField = Unsafe . class .getDeclaredFields()[0];     
            unsafeField.setAccessible( true );
            Unsafe unsafe = ( Unsafe ) unsafeField.get( null );
    
            while ( true ) {
                // unsafe            
                unsafe.allocateMemory( _1MB );
            }
        }
    }

    실행 결과:
    Exception in thread "main" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        ......

    이러한 예외의 뚜렷한 특징은 Heap Dump 파일에서 뚜렷한 예외가 보이지 않는다는 것입니다.만약 OOM 이후 Dump의 파일이 비교적 작고 프로그램에서 IO/NIO를 직접 또는 간접적으로 사용했다면 이 방면의 원인을 고려할 수 있다.
    1. 주지명을 참고하여 자바 가상기기: JVM의 고급 특성과 최상의 실천, 기계공업출판사

    좋은 웹페이지 즐겨찾기