개발 메모장

[Java] OOM과 GC (Out of Memory Error와 가비지 컬렉터) 본문

Java

[Java] OOM과 GC (Out of Memory Error와 가비지 컬렉터)

yyyyMMdd 2023. 12. 4. 18:01
728x90

#. 자바로 개발하는 초급개발자에겐 당연하고 익숙하지만 막상 설명하기 어렵고 면접을 보면 어딜 가든 물어보는 질문일 것입니다.
 
#. 자바 애플리케이션을 위한 런타임 환경 제공을 하는 JVM GC가 어떻게 흘러가고 어떻게 처리되는지 확인해 보겠습니다.
 


#. 개발하다 보면 정말 가끔씩 heap space 가 포함된 오류를 볼 수 있을 것입니다.
    (낮은 버전의 이클립스를 사용하면 더 높은 확률로 만났던 것 같습니다.)

    - 보통 이러한 경우 eclipse.ini에서 -Xms / -Xmx(최소 / 최대) 힙메모리를 지정하여 넘기곤 했을 것입니다.

    - 근본적인 오류의 원인은 말 그대로 Heap Memory 공간이 부족하여 생긴 오류입니다.

    - 인위적으로 오류를 발생시키기 위해 아래와 같이 코드를 입력해 봤습니다.

public static void outOfMemory() {
	List<Object> list = new ArrayList<>();
	while(true) {
		list.add(new Object());
	}
}

 
    - 작성 후 서비스 실행 시 새로운 객체를 생성해 List에 담다 보니 힙 메모리 공간이 부족해 아래와 같은 오류를 내뱉습니다.


    - 무한루프 외에도 int형 배열에 객체 생성 시 가능한 최대 크기로 배열을 할당하려 할 때도 발생할 수 있습니다.

    - 대용량 파일 업로드 시에도 발생할 수 있습니다.
 


 
#. 이렇게 극단적인 경우는 거의 드물며, 천천히 쌓여가는 힙 메모리를 관리하고 비워주는 역할을 GC : 가비지 컬렉터 라고 합니다.
 
    - 자바에선 다양한 GC 알고리즘을 사용 가능합니다.(직렬, 병렬, CMS, G1 등)

    - JVM 설정 및 힙 메모리 크기, 애플리케이션 동작, 런타임 조건 등을 통해 적절한 알고리즘을 선택하게 됩니다.

 

  • GC의 주요 역할

        1. 메모리 할당 및 해제
            - 힙 메모리에 쌓인 개체에 대한 메모리 할당을 관리하며, 참조되지 않은 개체나 사용되지 않는 객체의 메모리를 할당 해제하므로 다른 객체의 사용에 필요한 메모리 공간을 만들어둡니다.
        2. 조정 및 구성
             - JVM 설정 및 ini 옵션을 통해 GC의 동작, 힙 크기 등 최적화가 가능하도록 도와주며 이에 따른 메모리 관리를 도와줍니다.
        3. 런타임 성능 개선
             - GC의 수행 시기 및 방법 등은 런타임 성능에 영향을 미치는데 메모리 정리와 애플리케이션 실행의 효율성 간 균형을 맞추기 위한 관리를 도와줍니다.


  • GC의 처리 과정

    - GC 실행을 위해선 Stop the world를 가장 먼저 실행합니다.(GC 실행 스레드 외 모든 스레드의 작업을 멈추고 GC 처리 후 다시 시작합니다.)

    - GC는 Young 영역과 Old 영역으로 나뉘며 이는 각각 Minor GC, Major GC를 처리합니다.

    - 또한 Young 영역은 Eden 영역 1개, Survivor 영역 2개로 구성되어 있습니다.
 
    · 처리 방식
        1. 새로운 객체가 Eden 영역에 생성됩니다.
        2. Eden 영역에서 GC가 실행되며 그중 걸러지지 않은 객체가 Survivor 0 영역으로 이동하게 됩니다.
        3. 이와 같은 동작을 반복하며 Survivor 0 영역이 가득 차게 되면 Survivor 0 영역에서 GC가 실행됩니다.
        4. Survivor 0 이 가득 찰 때까지 걸러지지 않은 객체는 Survivor 1 영역으로 이동하게 되며 Survivor 0 영역은 비워집니다.
                 - 2개의 Survivor  영역 중 1개의 영역은 반드시 빈 상태여야 합니다.
        5. 이 동작을 반복하게 되며 특정 횟수만큼 반복되는 동안 걸러지지 않은 객체는 Old 영역으로 이동하게 됩니다.
        6. 이를 반복하며 Old 영역이 가득 차게 되어 더 이상 Old 영역으로 이동할 수 없게 되면 Old 영역에서 GC가 실행됩니다.

 


 
#. GC의 알고리즘 종류
 
1. Serial GC(싱글 스레드)
    - Young 영역에서는 위 설명 방식과 동일하며 Old 영역에서의 방식이 조금 다릅니다.
    - Old 영역에서는 Mark-Sweep-Compact라는 알고리즘을 사용합니다.
    - Old 영역에 살아있는 객체를 식별하고(Mark) 힙의 앞부분부터 다시 확인해 걸러지지 않은 것만 남깁니다.(Sweep)
    - 그 후 객체들을 힙의 앞부분부터 채워 객체가 있는 부분, 없는 부분으로 나눕니다.(Compact)


2. Parallel GC(멀티 스레드)
    - 기본적인 알고리즘은 같으나 스레드의 개수가 여러 개인 차이점이 있습니다.
    - 따라서 Serial GC보다 속도가 빠르며, 이 알고리즘은 코어 개수가 많을 때 유리합니다.


3. Parallel Old GC
    - Serial GC에서 Sweep 단계 대신 Summary 단계를 사용합니다.
    - Summary 단계는 앞서 GC를 수행한 영역에 대해 별도로 걸러지지 않은 객체를 식별합니다.


4. Concurrent Mark & Sweep GC
    - Initial Mark 단계에서는 걸러지지 않은 객체를 찾습니다.
    - 찾은 객체에서 참조하는 객체를 멀티 스레드를 이용해 동시에 Concurrent Mark 단계를 수행합니다.
    - Stop the world 및 Remark를 실행합니다. (Stop the World 실행 시간이 짧습니다.)
    - 멀티스레드를 동시에 실행하다 보니 자원 사용량(메모리 및 CPU)이 많습니다.


5. G1 GC
    - 바둑판의 각 영역에 객체를 할당하고 GC를 실행하는 방식입니다.
    - Young 영역, Old 영역에 대한 개념이 아닌 객체를 바둑판에 할당하고 그 바둑판이 꽉 차면 다른 칸에 할당해 GC를 실행하는 개념입니다.
 

 
===========================================================
틀린 내용이 있거나 이견 있으시면 언제든 가감 없이 말씀 부탁드립니다!
참고 및 출처 :
https://d2.naver.com/helloworld/1329 / 
===========================================================

728x90