ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] OOME, JVM8 메모리
    Programming/Java 2024. 2. 29. 17:28
    728x90

    out of memory error

    힙 메모리에서 발생하는 메모리 에러

     

    OOME가 발생하는 시점은 대부분 개발이 완료된 후 사용자 테스트 및 인수 테스트 단계에서 많이 발생한다.

    즉, 개발 단계에서 수행하는 단위 테스트의 경우 목적 기능에 대한 검중 위주로 진행되기 때문에 식별이 어렵고 가동 초기 단계 혹은 이와 유사한 테스트 환경에서 주로 발생하게 되는 것이다.

    OOME가 발생한 시점에 빠르게 대응해야 하는데 JVM Option을 통한 처리 방법과 Dump파일 분석을 통해 대응하게 된다.

     

    Java의 Reflaction기능을 이용하거나 Spring같은 프레임워크를 사용하는 경우 메모리를 고려하여 설계해야 한다.

     

    OOME 케이스

    1. java.lang.OutOfMemoryError: Java Heap Space
    2. java.lang.OutOfMemoryError: PermGen Space
    3. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
    4. java.lang.OutOfMemoryError: Metaspace
    5. java.lang.OutOfMemoryError: Requested array size exceeds VM limit
    6. java.lang.OutOfMemoryError: (Native method)

     

    Java Heap Space

    java.lang.OutOfMemoryError: Java Heap Space

     

    - Java의 Heap Memory공간이 부족하여 발생.

    - 오브젝트를 java heap에 할당할 수 없을 때 발생.

    - 애플리케이션의 생존 주기가 지나치게 길 경우 객체 참조를 오래하여 GC대상에서 제외되어 발생.

    - 외부 API를 사용할 경우 의도하지 않게 객체 참조를 오래하여 발생.

    - 지나친 finalizer사용으로 인해 발생.

    - 클래스가 finalizer메소드를 가지고 있다면 해당 타입의 객체는 자신들의 공간에 대한 GC를 포함시킬수 없음

    - finalizer가 finalizer queue를 처리하는 속도보다 빠를 경우 힙 공간이 가득차서 발생.

     

    해결 방법

    - Xmx옵션을 사용하여 Heap Memory의 크기를 증가시키는 방법

    하지만 이는 GC Time의 증가를 동반하기 때문에 충분한 사전 테스트가 필요하다.

    - Application 프로파일링

    OOME가 발생한 시점에 생성된 Heap Dump분석을 기반으로 많은 Memory를 사용하거나 Memory Leak을 유발하는 로직을 수정해야 한다.

     

    PermGen Space

    java.lang.OutOfMemoryError: PermGen Space

     

    JDK 1.8부터 삭제

     

    - Java Heap Memory영역 중 Permanent영역은 String pool, Class Method와 관련된 각종 Meta Data등을 저장하는 용도로 사용된다.

    - 따라서 JVM기동 시 로딩되는 Class또는 String의 수가 많다면 OOME:PermGen space에러의 원인이 된다.

    - Classloader Leak에 의해 OOME가 발생될 수 있다.

     

    해결 방법

    - JVM Option 튜닝

    하지만 Permanent영역은 Heap영역과는 달리 일반 비즈니스 프로그램으로 핸들링 할 수 없기에 JVM옵션 튜닝으로도 해결되지 않을 수 있다. WAS, JDK버그를 의심해 봐야 한다.

     

    GC Overhead Limit Exceeded

    java.lang.OutOfMemoryError: GC Overhead Limit Exceeded

     

    - GC빈도가 빈번하고 Java프로그램 실행이 느려지게 만들 수 있다.

    - GC이후 만약에 Java프로세스가 GC작업 하는 동안 전체 동작시간의 98%를 소비했음에도 heap영역이 2%이하로 확보되었을 때 발생

     

     

     

    Metaspace

    java.lang.OutOfMemoryError: Metaspace

     

    - Metaspace는 로드된 Class들의 메타데이터가 저장되는 공간

    - 클래스 메타데이터에 의해 사용되어질 Metaspace의 크기는 MaxMetaspaceSize명령어로 설정 가능

    - Class메타데이터들 위해 필요한 Native Memory(Metaspace)크기가 MaxMetaSpaceSize를 초과했을 때 에러 발생.

    - Heap크기 조정을 통해 조치 가능

     

    Requested array size exceeds VM limit

    java.lang.OutOfMemoryError: Requested array size exceeds VM limit

     

    - 애플리케이션이 배열의 할당을 heap size보다 크게 했을 때 발생

    Java는 런타임시 물리적 메모리를 초과한 경우 가상 메모리를 확장해 사용하게 되는데 가용한 가상메모리가 없을 경우 발생.

    예를 들어 heap size가 256mb인 메모리에 512mb크기의 배열 할당을 하면 에러가 발생한다.

     

     

    (Native method)

    java.lang.OutOfMemoryError: (Native method)

     

    - JVM에 설정된 것보다 큰 Native메모리가 호출될 때 발생.

     

     

    예제

    ArrayList에 1kb크기의 배열을 25만번 add해보자

    package org.example;
    
    import java.util.ArrayList;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Hello world!");
    
            ArrayList list = new ArrayList<>();
            try{
                for (int i = 0; i < 250000; i++) {
                    list.add(new byte[1000000]);
                    System.out.println(i);
                    Thread.sleep(1);
                }
            }
            catch (Exception e){
                e.printStackTrace();
            }
    
        }
    }

     

     java.lang.OutOfMemoryError: Java heap space 에러가 발생한다.

     

    Java Options

    Heap 메모리는 WAS의 경우 통상 2GB~4GB 정도가 설정되며, 독립 실행 배치는 125MB~256MB 정도 설정하고 부득이 한 경우 그 이상으로 설정대량 데이터를 로드하여 분석하는 시스템 같은 경우 10GB 이상도 설정하기도 함

     

    ● server : 해당 옵션은 최고 작동 속도를 최대화하도록 특별히 조정되었습니다.

    빠른 시작 시간 또는 더 작은 런타임 메모리 풋 프린트보다 가능한 가장 빠른 작동 속도가 필요한 장기 실행 서버 애플리케이션을 실행하기위한 것입니다.64bit JDK 는 암시적으로 무조건 server 옵션이 적용됩니다.

     

      Xms : 설정은 Java 힙의 초기 크기를 제어합니다.

    이 매개변수를 적절하게 조정하면 가비지 콜렉션의 오버헤드를 줄여서 서버 응답 시간 및 처리량을 개선합니다. 일부 응용프로그램의 경우, 이 옵션에 대한 기본 설정이 너무 낮아서 사소한 가비지 콜렉션의 수가 높아질 수 있습니다.

     

      Xmx : Java 힙의 최대 크기를 제어합니다.

    이 매개변수를 늘리면 Application Server에 사용 가능한 메모리가 늘어나고 가비지 콜렉션 빈도가 줄어듭니다. 이 설정을 늘리면 서버 응답 시간 및 처리량이 개선될 수 있습니다. 그러나 이 설정을 늘리면 가비지 콜렉션이 발생할 때 해당 콜렉션의 지속 기간이 늘어납니다. 이 설정은 Application Server 인스턴스에 대해 사용 가능한 시스템 메모리 이상으로 증가해서는 안됩니다. 설정을 사용 가능한 시스템 메모리 이상으로 늘리면 시스템 페이징 및 상당한 성능 감소를 유발할 수 있습니다.

     

      Xss : 스레드 스택 크기(바이트)를 설정합니다

     

      -XX:MetaspaceSize : 가비지 컬렉션을 처음 초과할 때 트리거할 할당된 클래스 메타데이터 공간의 크기를 설정합니다. 가비지 수집에 대한 이 임계값은 사용된 메타데이터 양에 따라 증가하거나 감소합니다. 기본 크기는 플랫폼에 따라 다릅니다.

     

      -XX:MaxMetaspaceSize : 클래스 메타데이터에 할당할 수 있는 최대 네이티브 메모리 양을 설정합니다.

    기본적으로 크기는 제한되지 않습니다. 응용 프로그램의 메타데이터 양은 응용 프로그램 자체, 실행 중인 다른 응용 프로그램 및 시스템에서 사용할 수 있는 메모리 양에 따라 달라집니다.

     

    JVM메모리 구조

    - JVM7이하 Heap메모리 구조 및 옵션

    JVM 7이하 Heap메모리 구조 및 옵션

     


    - JVM8 이상 Heap메모리 구조 및 옵션

    JVM8이상 Heap메모리 구조 및 옵션

     

    Java8 이전에는 metaspace영역이 아닌 permanent영역이 존재했다.

    permanent영역은 class의 meta정보나 method의 meta정보, static변수와 상수 정보들이 저장되는 공간이었다.

    하지만 Java8 버전부터는 기존의 Permanent영역이 Native영역으로 이동하여 Metaspace영역으로 변경되었다.

     

    Eden Space [Young Generation]

     처음 생성된 모든 객체는 에덴 영역에 존재합니다. JVM에 의해 정해진 임계치에 도달하면 minor gc가 수행됩니다. minor gc가 수행되면 참조되지 않는 객체들은 에덴 영역에서 제거되고, 살아남은 객체들은 'From' 영역에서 'To' 영역의 Survivor 영역으로 이동합니다. gc가 끝나고나면 'From'과 'To' Survivor 영역의 역할이 서로 바뀝니다. ('From'은 'To'가 되고, 'To'는 'From'이 됩니다.)

    • 새롭게 생성된 객체 대부분이 이 영역으로 위치한다.
    • 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다.
    • 이 곳이 가득차면 minor gc가 발생한다.
    • minor gc가 발생하면 살아있는 객체들만 체크하고 나머지는 다 없애버린다.
    • 살아남은 객체들 중 더 오래 쓸 것 같은 것들은 Tenured 영역으로 옮긴다.

     

    Survivor1 (From)

    이 영역은 에덴 영역으로부터 살아남은 객체가 담깁니다. 이전의 gc 과정에서는 'To'의 역할을 하던 영역입니다.

     

    Survivor2 (To)

     이 영역은 gc가 수행될 때, 에덴 영역과 'From'의 역할을 하던 Survivor 영역에서 살아남은 객체들이 담깁니다.

     

    Tenured (Old Generation)

     Survivor 영역의 객체가 minor gc에서 살아남아 다른 Survivor 영역으로 이동할 때마다 객체의 Age가 증가합니다. 이 Age가 일정 이상이 되면 Tenured 영역으로 이동하게 됩니다(Promotion).

    Promotion의 기준이 되는 Age는 -XX:MaxTenuringThreshold 옵션으로 설정할 수 있습니다. Java SE 8 에서의 default 값은 15이며, 설정 가능한 범위는 0 ~ 15 입니다.

    • Young 영역에서 살아남은 객체들은 이 곳으로 복사된다.
    • 이 곳이 가득차면 major gc(혹은 full gc)가 발생한다.
    • 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 gc는 적게 발생한다.
    • major gc는 minor gc 보다 오래걸린다.

     

     

     

     

     

     

     

    참고

    https://www.nextree.co.kr/p3878/

    https://cocococo331.tistory.com/17

    https://sharplee7.tistory.com/54

     

     

     

    728x90

    'Programming > Java' 카테고리의 다른 글

    [Java] Annotation, Reflection, Lombok, Spring Bean  (0) 2024.03.14
    [Java] Checked, Unchecked Exception  (0) 2024.03.13
    [Java] Spring 빌드 Jar, War  (0) 2024.02.29
    [Java] Garbage Collection  (2) 2024.02.28
    [Java] String 클래스  (1) 2024.02.06

    댓글

© 2022. code-space ALL RIGHTS RESERVED.