Java

[Java] Garbage Collection

SeungbeomKim 2024. 12. 5. 00:05

Java 프로그램을 개발하다 보면 OutOfMemoryError을 마주하게 되는데, 이는 JVM Heap 메모리의 부족 현상에 밀접한 연관이 있는 에러입니다.

 

이를 해결하기 위해 Garbage Collection 과정이 발생하게 되는데 해당 개념과 동작원리, 알고리즘에 대해 알아보겠습니다. 

 

Garbage Collection 

  • 메모리 관리 기술중 하나로서 JVM의 Heap 영역에서 동적으로 할당되었던 메모리 중 사용하지 않은 객체를 모아 주기적으로 제거하는 프로세스입니다. 

JVM의 메모리 구조중 일부인 Heap 영역에 관여하는 프로세스입니다. 뒷단에 내용을 수월하게 이해하기 위해 JVM Memory 구조를 설명드린 후 핵심 내용에 대해 알아보겠습니다.

 

JVM Memory 구조

Static (Method) 영역

  • JVM이 실행될 때 Class가 load되어 생성됩니다.
  • Class 정보, Static 변수, 생성자, 메서드를 저장합니다.
  • 어디에서든 접근이 가능합니다.
  • JVM 종료 시 메모리에서 해제 됩니다. 즉, 프로그램이 종료되기 전까지 메모리 상에서 존재하게 됩니다.

Heap 영역

  • 인스턴스를 생성할 때 사용되는 메모리 영역입니다 (new 키워드를 통해 인스턴스 생성 시)
    • Stack 영역에서 생성된 객체에 대한 주소값 (Reference)을 저장합니다.
  • 참조형 데이터 타입이 저장됩니다. (String, Array, Enum, Class, Interface.)
  • 호출이 종료되도 삭제 X → GC에 의해 메모리에서 해제됩니다.
  • 스레드가 몇 개든 단 하나의 영역에서만 존재합니다. (Stack 영역은 Thread 별로 하나씩 생성)

Stack 영역

  • 기본 자료형 (int, double, boolean, byte..), 지역변수, 매개변수가 저장되는 메모리입니다.
  • 메서드 내부의 기본자료형에 해당하는 변수를 적재합니다.
  • Heap 영역에 생성된 데이터의 참조값이 할당됩니다.
  • 메서드가 호출될 때, 메모리에서 할당되고 메서드 종료 시 메모리에서 삭제됩니다.
  • 각 Thread마다 자신만의 Stack을 갖고 있습니다. Thread는 내부적으로 Static, Heap, Stack 영역을 가집니다. Thread는 다른 Thread에 접근할 수 없지만, static, heap 영역을 공유하여 사용이 가능합니다.

 

이제 GC와 연관된 Heap 영역의 세부적인 구조에 대해 알아보겠습니다. (Permanent 영역은 Java8 이후 Native Method Area으로 대체)

  • Young Generation (Eden + S0 + S1)
    • 새롭게 생성한 객체의 대부분이 이 영역에 위치합니다. 
    • 대부분의 객체가 금방 접근 불가능한 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라집니다.
    • 객체가 처음 생성되면, Eden 영역에 저장되고 Eden 영역의 메모리가 가득 차면 S0, S1로 옮겨지게 됩니다.
    • Minor GC: Eden영역이 가득 찼을 때, Young Generation 영역에서 객체가 사라지는 현상
  • Old Generation
    • 접근 불가능한 상태로 되지 않아 Young 영역에서 살아남은 객체가 이 영역으로 복사됩니다.
    • Young 영역보다 크게 할당되며 크기가 큰 만큼 Young 영역보다 GC는 적게 발생합니다.
    • Major (Full) GC: Old Generation이 가득 찼을 때, Old Generation 영역에서 객체가 사라지는 현상

 

세부적인 구조에 대해서도 파악했으니, GC의 공통된 2가지 기능과 알고리즘 종류 5가지에 대해 설명드리겠습니다. 

 

  1. stop-the-world
    • GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 행위입니다.
    • 어떤 GC 알고리즘을 사용하더라도 "stop-the-world"는 발생하게 되는데, 대개의 경우 GC 튜닝은 이 "stop-the-world" 시간을 줄이는 것이라고 합니다.
    • GC를 해도 더이상 사용 가능한 메모리 영역이 없는데 계속 메모리르 할당하려고 하면, OutOfMemoryError가 발생하여 WAS가 다운될 수도 있습니다. 즉, 서버가 요청을 처리 못하고 있는 상태가 됩니다.
  2. Mark and Sweep
    • Mark: 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업입니다.
    • Sweep: Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업입니다.
    • Stop-The-World를 통해 모든 작업을 중단시키면, GC는 스택의 모든 변수 또는 Reachable 객체를 스캔하면서, 어떤 객체를 참고하고 있는지를 탐색하게 됩니다.

 

GC Algorithm

 

  1. Serial GC(-XX:+UseSerialGC)
    • Young 영역: Mark-Sweep 방식으로 GC 수행
    • Old 영역: Mark-Sweep-Compact 방식으로 GC 수행
      • Old 영역에 살아있는 객체를 식별하고 (Mark), 힙의 앞부분부터 확인하여 살아있는 객체만 남긴 후 (Sweep) 객체가 존재하는 부분과 객체가 없는 부분으로 나눕니다. (Compact)
    • 적은 메모리, 적은 CPU 코어 개수에 적합합니다.
  2. Parallel GC(-XX:+UseParallelGC)
    • Serial GC와 알고리즘은 동일하지만, GC를 처리하는 Thread의 개수가 다릅니다.
    • Parallel GC는 GC를 처리하는 Thread가 여러 개지만, Serial GC는 한 개입니다.
    • 속도 측면에서 Parallel GC가 빠르며, 메모리가 충분하고 코어의 개수가 많을 때 적합합니다.

Serial GC, Parallel GC 차이

3. Parallel Old GC(-XX:+UseParallelOldGC)

  • JDK 5 update 6부터 제공한 방식입니다.
  • Old 영역의 GC 알고리즘이 Mark-Summary-Compaction입니다.
  • Summary 단계는 GC를 수행한 영역에 대해 별도로 살아있는 객체를 식별한다는 점에서 Sweep 단계 보다 약간 더 복잡한 단계를 거칩니다.

4. CMS GC(-XX:+UseConcMarkSweepGC)

  • Initial Mark 단계: 클래스 로더에서 가장 가까운 객체 중 살아있는 객체만 찾습니다.
  • Concurrent Mark 단계: 방금 살아있다고 확인한 객체에서 참조하고 있는 개체들을 따라가면서 확인합니다.
  • Remark 단계: Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인합니다.
  • Concurrent Sweep 단계: 쓰레기를 정리하는 작업을 실행합니다.
  • 위 4가지 단계의 과정들을 다른 Thread가 실행 중인 상태에서 동시에 실행됩니다.
  • Stop-the-world 시간이 매우 짧으면서, 애플리케이션의 응답 속도가 중요할 때 사용합니다.
  • 장점만큼 메모리와 CPU를 더 많이 사용해야 한다는 단점이 있습니다.
  • Parallel GC와의 차이는 Compaction 작업 유무의 차이입니다.
    • Compaction: 메모리 공간에서 사용하지 않는 빈 공간이 없도록 옮겨서 메모리 분산을 제거하는 작업
  • Java 9 deprecated, Java 14에서 사용이 중지되었습니다.

5. G1 GC(-XX:+UseG1GC)

  • G1 GC는 힙을 일정한 크기의 리전(Region)으로 분할합니다.
  • 각각의 리전은 Young 또는 Old Generation이 될 수 있고, 하나의 리전이 GC가 수행되는 최소 단위가 됩니다. G1 GC는 이 리전들을 가비지의 양과 종류에 따라 우선순위를 두어 효율적으로 처리합니다.
  • 리전은 통상의 GC와 마찬가지로 young generation, old generation으로 나뉘며 이들 영역의 위치는 정해져 있지 않고 메모리 상태에 따라 유동적으로 정해집니다.
  • Region
    • Young generation
      • Eden: 객체가 최초에 생성되는 영역
      • Survivor: Eden에서 생존한 객체가 이동되는 영역
    • Old generation
      • 일정 시간 이상 살아남은 객체가 이동되는 영역
    • Humongous
      • Region 크기의 절반 이상(50%)인 거대한 객체가 할당되는 영역

G1 GC 동작과정

  • Young-Only Phase
    • Young Generation에서 Normal Young Collection이 반복적으로 수행되며, 이 과정에서 Young Generation에서 Old Generation으로 객체가 승격됩니다. Old Generation의 점유율이 Initiating Heap Occupancy 값에 도달하면, Young-only Phase에서 Space-reclamation Phase로 전환 준비를 시작합니다.
    • Concurrent Start
      • Young GC를 수행하면서 동시 Marking (객체 생존 여부 탐지) Phase가 진행됩니다.
      • Concurrent Mark는 Old Region을 GC 하기 위해 현재 도달할 수 있는 live 객체를 결정합니다.
      • Concurrent Mark가 진행되는 도중에, Normal Young GC가 동작할 수 있습니다.
    • Remark
      • 마킹 작업을 최종 확인 및 완료합니다.
      • 클래스 참조 (객체 참조관계를 확인 및 정리), 클래스 언로딩, 비어있는 Region, 내부 데이터 구조를 정리합니다.
      • Remark 단계와 Cleanup 단계 사이에서 G1은 Old Generation 영역에서 어떤 영역을 수집할지 결정하고, 이를 기반으로 공간을 회수합니다.
    • Cleanup
      • Region 회수를 진행할지를 결정합니다.
      • Space-reclamation Phase가 시작될 경우, Prepare Mixed Young Collection이 실행되어 Young-only Phase를 종료합니다.
  • Space Reclamation
    • Young Generation 영역과 함께 Old Generation 영역의 일부도 정리하는 Mixed Collection이 수행됩니다.
    • Mixed Collection은 유효 객체를 Old Generation에서 다른 영역으로 이동하고 가비지가 많은 영역을 해제합니다.
    • G1은 더 이상 Old Generation 영역에서 회수할 메모리가 충분하지 않다고 판단되면, Space-Reclamation Phase를 종료합니다.
    • Space Reclamation 단계가 종료되면 Collection Cycle이 Young Phase 단계에서부터 재시작됩니다.
  • Full GC (back up)
    • 애플리케이션 메모리 부족 상태에 도달했거나, 생존 객체 정보를 수집하는 동안 메모리가 부족한 경우, 전체 Heap에 대해 Stop-the-World Full GC를 수행합니다.
  • GC방식보다 처리 속도가 가장 빠르며 멀티 프로세스 기반으로 운영되는 애플리케이션으로 적합하며, Java 9부터 기본 GC로 지정되었습니다.

 

<참고 자료>

https://lucas-owner.tistory.com/38

https://mangkyu.tistory.com/119

https://www.baeldung.com/java-system-gc

https://d2.naver.com/helloworld/1329

https://s-core.co.kr/insight/view/about-g1-gc/

https://thinkground.studio/2020/11/07/일반적인-gc-내용과-g1gc-garbage-first-garbage-collector-내용/

https://docs.oracle.com/en/java/javase/17/gctuning/garbage-first-g1-garbage-collector1.html#GUID-3A99AE6C-F80A-4565-A27C-B4AEDF5CDF71

'Java' 카테고리의 다른 글

[Java] ClassNotFoundException VS NoClassDefFoundError  (1) 2024.02.06
[Java] Exception  (0) 2023.08.21