[Java] Garbage Collection

bbbooo·2024년 3월 5일

Java

목록 보기
4/4

Java 메모리 구조

1-1. JVM 메모리 구조

1-2. Heap 영역, Stack 영역, Method 영역 등의 역할 및 특징

  • **Heap** 영역 동적 데이터가 저장되는 영역. 즉, 프로그램 실행 중에 생성되는 인스턴스나 객체들이 저장되는 공간이다. Heap 영역은 메모리가 허용하는 한 크기가 자동으로 늘어나거나 줄어들며, 가비지 컬렉터에 의해 더 이상 참조되지 않는 객체들이 정리된다.
  • **Stack** 영역 정적 데이터가 저장되는 영역. 주로 지역 변수, 매개 변수, 리턴 값 등이 저장됩니다. 함수 호출이 발생할 때마다 해당 함수의 스택 프레임이 생성되어 스택 영역에 쌓이며, 함수가 종료되면 해당 함수의 스택 프레임은 제거된다.
  • **Method** 영역 클래스 정보, 전역 변수, 상수, 코드 등이 저장되는 영역. JVM에 의해 구성되며, 프로그램 시작부터 종료 시까지 유지되는 영역이다.

Java 메모리 관리

2-1. 객체 생성과 메모리 할당 과정

Java에서 객체를 생성하려면 new 키워드를 사용한다. 예를 들어, Student student = new Student();라는 코드는 Student라는 새로운 객체를 생성하고, 이 객체에 대한 참조를 student라는 변수에 저장하는 것이다.

new 키워드가 사용되면 JVM은 Heap 영역에서 해당 객체를 저장하기 위한 충분한 메모리 공간을 찾는다.

2-2. 객체 참조 및 접근 방식

생성된 객체는 참조 변수를 통해 접근된다. 이 참조 변수는 **Stack** 영역에 저장되며, 실제 객체를 가리키는 포인터 역할을 한다. 객체의 메소드를 호출하거나, 속성에 접근하기 위해서는 이 참조 변수를 통해 이루어진다. 예를 들어 student.getName();이라는 코드는 student라는 참조 변수를 통해 getName이라는 메소드를 호출하여 객체의 이름을 반환하는 것이다.

Java Garbage Collection이란?

3-1. 가비지 컬렉션의 필요성 및 역할

Java 프로그램이 실행되는 동안 계속해서 새로운 객체가 생성되고, 일부 객체는 더 이상 필요하지 않게 되는데, 이런 불필요한 객체들이 메모리에 계속 남아 있으면 메모리 공간이 점차 줄어들어 결국에는 프로그램의 성능 저하나 메모리 부족 등의 문제를 일으킬 수 있다. 가비지 컬렉션은 이런 문제를 방지하기 위해 필요하다.

가비지 컬렉션(Garbage Collection, GC)은 Java의 메모리 관리 기법 중 하나로, JVM이 메모리의 Heap영역에 동적으로 할당된 메모리 중 사용되지 않는 부분을 자동으로 회수하는 기능을 말한다.

가비지 컬렉션은 이런 더 이상 사용되지 않는 객체들을 자동으로 감지하여 메모리에서 제거하므로, 개발자가 직접 메모리를 관리하는 부담을 덜어준다. 즉, 개발자는 필요한 객체를 생성하고 사용하면 되고, 그 객체가 더 이상 필요 없게 되면 가비지 컬렉터가 알아서 메모리에서 제거해준다.

단, 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없다는 단점이 있다.

3-2. 가비지 컬렉션의 기본 원리 설명 : 도달 가능성과 Mark and Sweep

Mark and Sweep 과정

가비지 컬렉션의 기본 원리는 도달 가능성(Reachability) 이다. JVM은 Root Set이라는 기준점에서 시작해서 참조를 따라가며 도달할 수 있는 객체들을 '도달 가능한 객체'로 판단한다. Root Set은 Java 스택의 지역 변수나 입력 매개변수, 네이티브 스택의 JNI 참조, 메소드 영역의 정적 변수 등이 포함된다.

JVM은 Root Set에서 시작하여 참조를 따라가며 도달할 수 있는 모든 객체를 마크한다. 이 과정이 끝나면, 마크되지 않은 객체, 즉 Root Set에서 참조를 따라 도달할 수 없는 객체들은 '도달 불가능한 객체'로 간주되며, 이런 객체들은 가비지로 취급된다. 이 과정을 Mark 단계라고 한다.

이렇게 도달 불가능한 객체들은 가비지 컬렉터에 의해 메모리에서 제거된다. 가비지 컬렉터는 이런 가비지들을 찾아내어 그 메모리를 회수하고, 필요에 따라 회수된 메모리를 재사용 가능한 상태로 만들어준다. 이 과정을 Sweep 단계라고 한다.

Mark 단계와 Sweep 단계, 이 두 단계로 이루어지는 작업을 Mark and Seep이라고 하며, 가비지 컬렉션이 동작하는 아주 기초적인 청소 과정이라고 할 수 있다.

Heap 영역의 메모리 구조와 Garbage Collection

4-1. Heap 영역의 메모리 구조

Java 8부터 Permanent 영역은 Native Method Stack에 편입되었다.

JVM의 힙 영역은 동적으로 레퍼런스 데이터가 저장되는 공간으로서, 가비지 컬렉션에 대상이 되는 공간이다. Heap영역은 처음 설계될 때 다음의 2가지를 전제 (Weak Generational Hypothesis)로 설계되었다.

  1. 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
  2. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.

즉, 객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다. 효율적인 메모리 관리를 위해 이러한 특성을 이용해서 객체의 생존 기간에 따라 물리적으로 Heap 영역을 나누게 되었고, YoungOld 2가지 영역으로 설계하였다. (초기에는 Perm 영역도 존재하였지만 Java 8부터 제거되었다)

Young Generation

  • 새롭게 생성된 객체가 할당(Allocation)되는 영역
  • 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
  • Young 영역에 대한 가비지 컬렉션을 Minor GC라고 부른다.
  • Young 영역은 다시 Eden, survivor 0, survivor 1으로 나뉜다.
    • Eden : new를 통해 새로 생성된 객체가 위치한다. 이곳에서 GC 후 살아남은 객체는 survivor로 이동한다.
    • survivor 0 / survivor 1 : 최소 1번 이상의 GC에서 살아남은 객체가 위치한다. 0와 1 둘 중 하나는 반드시 비어 있어야한다.

Old Generation

  • Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
  • Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
  • Old 영역에 대한 가비지 컬렉션을 Major GC 또는 Full GC라고 부른다.

4-2. Minor GC와 Major GC

Minor GC

Minor GC는 Eden 영역이 꽉차게 된 경우 발생하며, Young 영역의 크기가 작기 때문에 속도가 빠르다. 보통은 0.5초에서 1초 사이에 끝난다고 한다.

  1. 처음 생성된 객체는 Young 영역의 Eden에 위치하게 된다.
  2. Eden 영역이 꽉차게 되면 Minor GC가 실행된다.
  3. Eden영역에서 살아남은 객체와 survivor 영역에서 살아남은 객체들은 비어있는 survivor 영역으로 이동한다.
  4. 이용하지않는 객체들은 메모리가 해제되고, 살아남은 객체는 age가 1씩 증가한다.
  5. 위의 과정을 반복한다.

Major GC

Major GC는 Old 영역이 꽉차게 된 경우 발생하며, Old영역의 크기가 크기 때문에 속도가 Minor GC에 비해 상대적으로 느리다. 보통 10배 이상의 시간이 걸린다고 한다.

  1. 객체의 age가 일정한 값에 도달하게 되면 Old 영역으로 이동하게 된다.
  2. Old 영역이 꽉차게 되면 Major GC가 실행된다.
  3. 위의 과정을 반복한다.

4-3. Stop-The-World

가비지 컬렉션은 객체에 할당된 메모리를 해제하기 전에 애플리케이션 동작을 멈추도록하는데 이것을 stop-the-world라고 한다. 여기서 애플리케이션 동작이 멈춘다는 것은 GC와 관련된 스레드를 제외한 모든 스레드가 멈춘다는 의미이다. 이렇게되면 서비스 이용에 차질이 생길 것이 분명한데, 왜 가비지 컬렉션은 반드시 stop-the-world를 해야하는걸까?

  1. 메모리 단편화 관리

    할당된 메모리를 해제하는 과정에서 메모리 공간이 남게되고, 메모리 단편화가 발생하게 된다. 보통은 이를 해결하기 위해 Compaction 작업을 하게되는데, 이때 객체를 새로운 주소로 이동시키고 다시 주소를 참조하는 과정을 진행하므로 stop-the-world가 필수로 선행되어야한다.

  2. 객체 일관성

    Garbage Collector가 더 이상 사용되지 않는 객체를 탐지하고 마킹하는 작업을 수행하는데, 이 과정에서 메모리 내의 객체들의 상태가 변경될수도 있고, 다른 스레드가 객체를 참조할 수도 있다. 이 과정에서 예기치 못한 오류가 생길 확률이 크다.

    예를 들어 Garbage Collector가 마킹한 객체를 스레드가 동시에 참조했다고 가정하면, 나중에 다시 호출 됐을 때 이미 객체는 메모리 해제되어 존재하지 않기 때문에 예기친 못한 오류를 만날 수 있다.

위와 같은 이유로 stop-the-world는 가비지 컬렉션을 사용할 때 필수로 선행되어야하지만, 너무 자주 발생하거나 긴 시간 동안 지속될 경우 애플리케이션의 성능이 떨어질수있기 때문에 적절한 가비지 컬렉터를 사용하고, 가비지 컬렉션에 대한 최적화 작업(GC 튜닝)을 해야한다.

Garbage Collector 종류

Java에서는 다양한 가비지 컬렉터를 제공하고 있다. 각 가비지 컬렉터는 특정한 알고리즘과 전략을 사용하여 메모리를 관리하기 때문에, 각 특징을 알고 적절한 가비지 컬렉터를 사용하는 것이 중요하다.

5-1. Serial GC

싱글 스레드로 가비지 컬렉션을 수행하는 가장 간단한 형태의 가비지 컬렉터이다.

  • Young Generation과 Old Generation에서 Mark-Sweep-Compact 알고리즘을 사용합니다.
  • 가비지 컬렉션 동작 동안 JVM의 다른 작업을 모두 중단합니다.
  • 하지만 보통 실무에서 사용하는 경우는 없다. 디바이스 성능이 안좋아서 CPU 코어가 1개인 경우에만 사용한다.
  • -XX:+UseSerialGC 옵션으로 활성화할 수 있다.
    java **-XX:+UseSerialGC** -jar Application.java

5-2. Parallel GC

Java 8 이하의 버전에서 기본으로 설정되어있는 가비지 컬렉터이다.

  • Parallel GC는 멀티스레드를 활용하여 가비지 컬렉션을 병렬로 처리하는 가비지 컬렉터이다.
  • Young Generation에서 Mark-Copy를, Old Generation에서 Mark-Sweep-Compact를 사용한다. → Mark Sweep Compation 알고리즘
  • 가비지 컬렉션 동안 JVM의 다른 작업을 모두 중단하지만, 병렬 처리로 인해 전체 가비지 컬렉션 시간을 단축시켰다.
  • -XX:+UseParallelGC 옵션으로 활성화할 수 있다.
    java **-XX:+UseParallelGC** -jar Application.java
  • Parallel Old GC
    • Parallel GC는 Yound 영역에 대해서만 병렬 처리 방식을 사용했지만, Parallel Old GC는 Old영역까지 병렬 처리 방식을 사용한다.
    • Mark Sweep Compation 알고리즘 대신 개선된 Mark Summary Compation 알고리즘을 사용한다.
    • -XX:+UseParallelGCThreads=n 옵션으로 멀티 스레드 개수를 지정할 수 있다.
      java **-XX:+UseParallelGC** **-XX:ParallelGCThreads=4** -jar Application.jar

5-3. CMS GC : Concurrent Mark Sweep GC

CMS GC는 최대한 'Stop-The-World' 일시 중단을 줄이기 위해 만들어진 가비지 컬렉터다.

  • GC 대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다.
  • 애플리케이션 중단 시간을 최소화 할 수 있지만 메모리 단편화가 발생할 수 있다.
  • Old Generation에서 Concurrent MarkingSweeping을 사용한다.
  • CMS GC는 Java 9 이후부터 deprecated되었고, 결국 Java14에서는 사용이 중지되었다.
  • -XX:+UseConcMarkSweepGC 옵션으로 활성화할 수 있다.
    java **-XX:+UseConcMarkSweepGC** -jar Application.java

5-4. G1 GC : Garbase-First GC

Java 9 이상의 버전에서 기본으로 설정되어있는 가비지 컬렉터이다.

  • 4GB 이상의 힙 메모리, stop-the-world 시간이 0.5초 정도 필요한 상황에서 사용하며, Heap 크기가 클수록 잘 동작한다. → Heap이 너무 작을 경우는 미사용 권장
  • 기존의 GC 알고리즘은 Heap 영역을 물리적으로 고정된 Young/Old 영역으로 나눠서 사용했지만, G1 GC는 새로운 논리적 단위인 Region이라는 개념을 사용한다.
    • 전체 Heap 영역을 Region이라는 영역으로 분할하여 상황에 따라 Eden, Survivor, Old등의 역할을 동적으로 부여한다.

  • Garbage-First : 다른 알고리즘처럼 전체 Heap 영역을 한 번에 처리하는 것이 아니라, 나누어진 영역 중 가비지가 많은 영역부터 처리하기 때문에 GC 빈도가 줄어들고 효율적이다.
  • 전체 힙을 여러 개의 작은 영역으로 분할하여 관리하므로, 메모리 단편화를 최소화하고 효율적으로 메모리를 관리할 수 있다.
  • 공간 부족 상태를 조심해야한다. → Minor GC, Major GC를 수행하고 나서도 여유공간이 부족하게 되면 Full GC(Heap 영역 전체 GC)가 발생하는데, 이 GC는 싱글 스레드로 동작한다.
  • -XX:+UseG1GC 옵션으로 활성화할 수 있다.
    java **-XX:+UseG1GC** -jar Application.java

5-5. Shenandoah GC

CMS GC가 가진 메모리 단편화, G1이 가진 pause의 이슈를 해결한 가비지 컬렉터이다.

  • RedHat에서 개발하고, JDK 12 버전에서 릴리즈되었다.
  • CMS의 메모리 단편화, G1의 stop-the-world 이슈를 개선하였다.
  • 일명 low-pause GC 라고 불리며, 큰 가비지 컬렉션을 적은 횟수로 수행하는 것이 아닌 작은 가비지 컬렉션을 여러 번 수행한다.
  • 작은 단위의 가비지 컬렉션을 자주 수행하기 위해 동시성(Concurrency)을 보장한다. 즉, CPU를 더 사용하는 대신 stop-the-world(pause) 시간을 줄였다.
  • Heap 사이즈에 영향을 받지않고 일정한 pause 시간이 소요되는 것이 특징이다.

  • -XX:+UseShenandoahGC 옵션으로 활성화할 수 있다.
    java **-XX:+UseShenandoahGC** -jar Application.java

 Garbase Collector 알고리즘

6-1. Parallel Old GC

Minor GC 단계

  • Eden 영역이 가득 차게 되면, 마이너 GC가 발생한다.
  • 이 때 Eden 영역에 있는 모든 살아있는 객체들이 Survivor 영역 중 하나로 이동된다.
  • 이 과정에서 Eden 영역과 Survivor 영역 사이의 객체는 Age 값이 증가되며, 일정 Age 값 이상이 되면 Old 영역으로 이동된다.
  • Survivor 영역에 있는 객체 중 일부는 살아 있는 객체이므로, 이러한 객체들만 다음 GC 사이클에 대상이 된다. 나머지 객체들은 해제된다.

Major GC 단계

  1. 초기 마크 (Initial Mark)
    • GC는 stop-the-world를 통해 애플리케이션을 일시 중지한다.
    • 모든 살아 있는 객체의 root set을 식별한다.
    • Old 영역 내의 모든 객체를 순회하면서 도달 가능한 객체들을 마킹한다.
  2. 병렬 마크 (Concurrent Mark)
    • GC는 마킹 작업을 이어가면서 동시에 애플리케이션을 실행한다. 즉, GC와 애플리케이션의 스레드가 병행하여 동작하며 stop-the-world가 발생하지 않는다.
    • Old 영역의 객체들을 순회하면서 마킹 작업을 계속한다. 이 과정에서 GC는 애플리케이션이 동작하는 동안 새로 생성된 객체들을 마킹해야 한다.
    • 마킹 작업이 진행되는 동안 새로운 객체가 생성되고 기존 객체가 참조 구조가 변경될 수 있다. GC는 이런 변경 사항을 추적한다.
  3. 최종 마크 (Final Mark)
    • GC는 stop-the-world를 통해 애플리케이션을 일시 중지한다.
    • 이 단계에서는 마킹 작업을 완료하고 변경된 참조를 처리한다.
    • 변경된 참조란, 병렬 마크 단계에서 마킹 작업이 진행되는 동안 애플리케이션이 계속 실행되면서 변경된 객체 참조를 의미한다. 변경된 참조는 마킹 작업이 진행되는 동안 추적된다.
  4. 스위핑 (Parallel Scavenge)
    • 마킹 단계가 완료되면 GC는 스위핑 단계로 이동합니다. 병렬로 처리되며, stop-the-world가 발생하지 않는다.
    • 스위핑 단계에서는 마킹되지 않은 객체들을 식별하고, 이 객체들을 해제하여 메모리를 회수한다.
  5. 병렬 압축 (Parallel Compaction)
    • 스위핑 단계가 완료되면, 메모리 공간의 조각화를 최소화하기 위해 압축 단계가 진행됩니다. 병렬로 처리되며, stop-the-world가 발생하지 않는다.
    • 이 과정에서는 유휴 영역과 사용 중인 영역을 식별하고, 사용 중인 영역에 있는 객체를 유휴 영역으로 복사한다.
  6. 종료 (Termination)
    • 압축 단계가 완료되면 GC는 모든 작업이 완료되었음을 알리고, 애플리케이션을 다시 실행한다.

6-2. G1 GC

G1 GC는 Young-Only 단계와 Space Reclamation 단계를 반복하면서 수행하는 Cycle 구조로 진행된다. 위 이미지에서 사이클 중 모든 원은 stop-the-world가 발생한 것을 나타낸 것이고, 원의 크기에 따라 stop-the-world 소요 시간이 달라진다고 생각하면 된다.

파란 원은 Minor GC(= Young GC, Evacuation Pause)가 진행함에 따라 stop-the-world가 발생한 것이고, 주황 원은 Major GC(= Old GC, Concurrent Cycle)이 진행하면서 객체를 마킹 및 기타 과정을 하기 위해 STW가 발생한 것이고, 빨간 원은 Mixed GC를 진행함에 따라 STW가 발생한 것이다.

Young Only 단계는 Minor GC만 수행하다가 한정된 Old 영역의 비율이 넘으면 Major GC가 수행된다. 그리고 Young Only 단계가 끝날 때까지 두 GC가 혼용된다.

Space Reclamation 단계는 Concurrent Marking Cycle이 일어난 후 Young/Old 영역의 Evacuation을 진행하는 페이즈이다. G1 GC는 PauseTime을 적게 가져가기 위해 Mixed GC가 짧게 여러번 일어난다.

MixedCollection은 특정조건을 만족할 때 까지 수행한다.

  • G1HeapWastePercent
    • The allowed unreclaimed space in the collection set candidates as a percentage. G1 stops the space-reclamation phase if the free space in the collection set candidates is lower than that.
  • G1MixedGCLiveThresholdPercent
    • 라이브 객체 점유율이 이 값보다 높은 old gen은 space-reclamation 단계에서 수집되지 않는다.
  • G1MixedGCCountTarget
    • The expected length of the space-reclamation phase in a number of collections.

Young-Only 단계

  1. Minor GC 수행
    • Young 영역에서의 가비지 컬렉션은 Minor GC로 처리된다.
    • Eden 영역이 가득 차면, 살아남은 객체들이 Survivor 영역 중 하나로 이동된다.
    • 이 과정에서 새로 생성된 객체와 이미 존재하는 객체의 Age를 관리한다. Survivor 영역에서 살아남은 객체들의 연령이 증가된다.
  2. Major GC 수행
    • Young 영역의 Minor GC가 진행되는 동안, Old 영역의 사용량을 모니터링한다.
    • Old 영역의 사용량이 미리 정의된 한계를 초과하면, Major GC가 실행된다.
    • 먼저 Initial Mark를 수행하는데, Minor GC와 동시에 수행되며 둘 다 stop-the-world를 수반하므로 다른 파란 원보다 크기가 크다. 그 이후에 애플리케이션 스레드, Minor GC, Concurrent Mark가 동시에 수행되는데 Remark가 수행되는 순간 다른 작업은 멈추게 된다. 그래서 Remark에 해당하는 주황색이 원이 큰 것을 알 수 있다. 그 이후에 자잘하게 Minor GC가 수행되다가 Major GC의 Cleanup이 발생한다.

💡 G1 GC의 Major GC의 과정

1. 초기 마크 (Initial Mark)
- GC는 애플리케이션을 일시 중지하여 stop-the-world 상태에 진입합니다.
- 모든 살아 있는 객체의 root set을 식별하여 마킹합니다. 이때, Old 영역에 있는 객체들 중에서만 식별 및 마킹 대상입니다.

2. 병렬 마크 (Concurrent Marking)
- 초기 마크 단계 이후, G1 GC는 애플리케이션의 동작과 병행하여 마킹 작업을 계속합니다. 이 과정에서 GC 스레드와 애플리케이션 스레드가 함께 동작하며, stop-the-world 상태를 최소화합니다.

3. 재마킹 (Remark)
- 모든 애플리케이션 스레드를 일시 중지하고 재마킹 작업을 수행한다. 이 단계에서는 초기 병행 마킹 단계 이후에 변경된 객체들을 다시 한 번 마킹하여 최신 상태를 유지한다.

4. 스위핑 (Parallel Scavenge)
- 모든 객체들을 스캔하고, 사용되지 않는 객체들을 해제하여 메모리를 회수한다. 이 과정에서는 stop-the-world 상태를 발생시키지 않으며, 여러 개의 스레드가 병렬로 작업을 수행하여 처리 속도를 높인다.

5. 이동 (Evacuation)
- Old 영역에 있는 객체들 중에서 살아 있는 객체들을 Young 영역으로 이동시킨다. 이 과정에서는 새로 생성된 객체들과 함께 영역을 이동하므로, Young 영역이나 Old 영역에 대한 GC 작업이 동시에 이루어진다.

6. 압축 (Compaction)
- Young 영역이나 Old 영역의 메모리 공간을 압축하여 조각화를 최소화한다. 이 과정은 메모리 단편화를 줄이고 메모리 사용 효율성을 향상시킨다.

Space Reclamation 단계

  1. Mixed GC 수행
    • Mixed GC는 Evacuation Pauses와 유사한 알고리즘을 사용하여 Young 영역과 Old 영역의 가비지 컬렉션을 동시에 수행한다.
    • mark 단계가 없기 때문에 stop-the-world가 yound-only 단계에 비해 적다.
    • Young 영역과 Old 영역을 동시에 가비지 컬렉션하여 메모리를 효율적으로 관리하고 응답 시간을 최적화한다.
  2. 공간 회수 및 압축
    • 수집된 Garbage로 인해 발생하는 메모리 공간의 재사용이 이루어진다.
    • 또한, 수집된 Garbage로 인해 발생하는 메모리의 조각화를 최소화하기 위해 압축 작업이 수행된다.

Java Garbage Collection 튜닝

7-1. GC 튜닝

GC 튜닝은 가비지 컬렉션의 동작을 조정하여 애플리케이션의 성능을 최적화하는 프로세스를 말한다. GC 튜닝을 하는 것은 좋지만 아래 사항을 꼭 생각해보고 진행하여야한다.

  1. GC 튜닝 옵션은 서비스마다 다르다.

    서비스마다 애플리케이션 코드도 다르고, 객체의 크기나 살아있는 기간도 다르기 때문에 본인의 서비스에 맞는 GC 튜닝을 진행해야한다.

  2. GC 튜닝은 가장 마지막에 해야한다.

    GC 튜닝은 튜닝 과정에서 생기는 소요 시간과 리스크에 비해 효과가 적기 때문에 애플리케이션 코드로 최적화를 먼저 진행하는 것이 좋다.

7-2. GC 튜닝의 핵심

GC 튜닝의 핵심은 Minor GC보다 stop-the-world 시간이 긴 Major GC의 관리이다.

  1. Full GC 빈도 줄이기 : Old 영역으로 넘어가는 객체 수 최소화하기

    Old 영역은 Young 영역에 비해 크기가 크고 GC 시간이 오래 걸린다. 그러므로 애초에 Old 영역으로 이동하는 객체 수를 줄이고 Full GC가 발생하는 빈도를 줄인다.

  2. Full GC 시간 줄이기

    Old 영역의 크기를 조절하여 Full GC의 시간을 줄인다. 여기서 Old 영역의 크기를 확 줄여버리면 OutOfMemoryError가 발생하거나 반대로 Full GC의 빈도가 늘어날 수 있으므로 적절하게 조절하는 것이 중요하다.

7-3. GC 튜닝의 절차

1) 분석 및 모니터링

애플리케이션의 GC 활동을 분석하고 모니터링하여 어떤 유형의 GC가 발생하는지, GC의 빈도와 지속 시간은 어떤지 등을 파악한다.

GC 로그는 가장 기본적인 GC 모니터링 도구이다. JVM 옵션을 통해 GC 로그를 활성화하면, GC 이벤트가 발생할 때마다 로그에 기록된다. 이 로그에는 각 GC 이벤트의 유형, 시간, 지속 시간, 힙 메모리 사용량 등의 정보가 포함되어 있다. GC 로그는 다음과 같은 JVM 옵션을 사용하여 활성화할 수 있다.

-verbose:gc
-Xloggc:<filename>
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps

JVM 옵션말고도 GUI GC 모니터링 분석 툴도 많다.

📄 GUI GC 모니터링 분석 툴

VisualVM : VisualVM은 Oracle이 제공하는 Java 애플리케이션 프로파일링 도구이다. GC 활동을 실시간으로 시각화하여 보여주기 때문에 GC의 전반적인 상황을 쉽게 파악할 수 있다.

JConsole : JConsole은 Java Management Extensions (JMX)를 사용하여 Java 애플리케이션을 모니터링하는 GUI 도구이다. 메모리 사용량, 로드된 클래스 수, 스레드 수, CPU 사용량 등 다양한 정보를 제공하며, GC 활동도 포함되어 있다.

JStat : JStat은 명령줄에서 사용할 수 있는 JVM 통계 모니터링 도구이다. GC 이벤트에 대한 통계를 실시간으로 제공한다.

2) 결정

모니터링 결과 분석에 따라 GC 튜닝을 수행할지 결정해야한다. 만약 모니터링 결과가 다음 조건에 모두 부합한다면 굳이 GC 튜닝을 진행할 필요가 없다.

  • Minor GC의 처리 시간이 빠르다. : 50ms 내외
  • Major GC의 주기가 빈번하지 않다. : 10초 내외
  • Full GC의 처리시간이 빠르다. : 1초 내외
  • Full GC의 주기가 빈번하지 않다. : 10분에 1회

3) 적절한 가비지 컬렉터 선택

모니터링 결과와 본인의 애플리케이션에 맞는 가비지 컬렉터를 선택한다.

4) Heap 크기 조정

구분옵션설명
Heap 영역 크기-XmsJVM 시작 시 Heap 영역 크기
Heap 영역 크기-Xmx최대 Heap 영역 크기
New 영역 크기-XX:NewRatioNew 영역과 Old 영역의 비율
New 영역 크기-XX:NewSizeNew 영역의 크기
New 영역 크기-XX:SurvivorRatioEden 영역과 Survivor 영역의 비율
Old 영역 크기-XX:OldSizeOld 영역의 초기 크기 설정
Old 영역 크기-XX:MaxOldSizeOld 영역의 최대 크기 설정
Survivor 관련-XX:MaxTenuringThresholdSurvivor 영역으로 이동할 수 있는 최대 Age 지정
Survivor 관련-XX:SurvivorPaddingSurvivor 영역의 크기를 조절하기 위한 여유 공간 설정

이 중에서 중요한 옵션은 -Xms, Xmx, -XX:NewRatio 옵션이다. 앞의 두 개는 거의 필수로 지정하길 권장하며, newRatio의 경우 어떻게 설정하느냐에 따라 GC 성능에 많은 차이가 발생한다.

Young 영역 조정

  • Young 영역의 크기를 조정하여 임시 객체의 수를 최소화한다.
  • Eden 영역과 Survivor 영역의 크기를 조정하여 GC 주기를 최적화한다.
  • Xmn, XX:NewRatio, -XX:SurvivorRatio등의 JVM 옵션을 사용하여 Young 영역을 튜닝한다.

Old 영역 조정

  • Old 영역의 크기를 조정하여 장기간 생존하는 객체를 관리한다.
  • Xms, Xmx, XX:MaxTenuringThreshold 등의 JVM 옵션을 사용하여 Old 영역을 튜닝한다.

5) 튜닝 테스트와 분석

  • 설정한 옵션을 기반으로 24시간 이상 애플리케이션을 실행하고, GC 동작을 모니터링하며 성능을 평가한다.
  • 튜닝된 설정이 목표에 부합하는지 확인하기 위해 부하 테스트와 성능 테스트를 수행한다.
  • 분석할 때는 다음 사항을 중심으로 살펴보는 것이 좋다. 아래 사항은 우선순위로 정렬되어있다.
    1. Full GC 수행 시간
    2. Minor GC 수행 시간
    3. Full GC 수행 간격
    4. Minor GC 수행 간격
    5. 전체 Full GC 수행 시간
    6. 전체 Minor GC 수행 시간
    7. 전체 GC 수행 시간
    8. Full GC 수행 횟수
    9. Minor GC 수행 횟수

참고자료
G1GC Garbage Collector에 대해 알아보기 - 1
[Java] G1 GC 에 대해
☕ 가비지 컬렉션 GC 튜닝 절차 맛보기

0개의 댓글