Garbage Collection

3 minute read

Garbage Collection

Q1. Garbage Collection이란?

보기

GC는 새로운 객체의 할당을 위해 한정된 힙 공간을 재활용하려는 목적으로 수행된다. 재활용을 위해 수행된 메모리의 해지는 할당한 그 자리에서 이루어지기 때문에 Garbage가 빠져나간 자리는 듬성듬성할 수 밖에 없다. 메모리의 개별 Free Space의 크기보다 큰 객체에게 공간을 할당할 경우 재활용이 무의미해진다. 이런 현상을 힙의 단편화(Fragmentation)라고 한다.

이러한 현상을 방지하기 위해 각 GC에서는 Compaction과 같은 다양한 알고리즘을 사용하고 있다. 결국, GC란 힙을 재활용하기 위해 Root Set에서 참조되지 않는 객체를 없애 가용한 공간을 만드는 작업이라고 할 수 있다.

Q2. Minor GC, Full GC(Major GC)의 차이점은?

보기

Minor GC

Young Generation에서 발생하는 GC를 Minor GC라고 한다. Young Generation은 빈번하게 GC가 수행되는 영역이다. 일반적으로 Full GC보다 속도가 빠르다.

Full GC

Old Generation에서 발생하는 GC를 Full GC(Major GC)라고 한다. Permanent 영역의 메모리가 부족해도 GC가 발생할 수 있는데 이때는 너무 많은 수의 Class Object가 로딩되어 Free Space가 없어졌기 때문이며 Permanent 영역이 부족하면 힙(Young + Old Generation)에 Free Space가 많더라도 Full GC가 발생한다.

요약

참고

Full GC와 Major GC를 구분할 경우

  • Major GC: Old Generation에서 발생하는 GC
  • Full GC: 힙 메모리 전체 영역(Young + Old Generation)에서 발생하는 GC

Q3. G1 GC란?

보기

JAVA 6에서 G1(Garbage First)이라는 새로운 알고리즘이 도입되어 early access로 제공되었으며, JAVA 7에서는 그 효율성이 증명되어 정식으로 G1 GC를 포함하여 제공하게 되었다. JAVA 9에서는 default GC로 G1 GC가 선택되었다.

G1 GC는 앞서 살펴본 GC와는 다른 방식으로 힙 메모리를 관리한다. 앞서 살펴보았던 Eden, Survivor, Old 영역이 존재하지만 고정된 크기로 고정된 위치에 존재하는 것이아니며, 전체 힙 메모리 영역을 Region 이라는 특정한 크기로 나눠서 각 Region의 상태에 따라 그 Region에 역할(Eden, Survivor, Old)이 동적으로 부여되는 상태이다.

JVM 힙은 2048개의 Region으로 나뉠 수 있으며, 각 Region의 크기는 1MB ~ 32MB 사이로 지정될 수 있다. (-XX:G1HeapRegionSize로 설정, Region 크기 변경은 권장되지 않음)

G1 GC에서는 그동안 봐왔던 Heap 영역에서 보지 못한 Humongous, Available/Unused가 존재하며 두 Region에 대한 역할은 아래와 같다.

  • Humongous: Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간이며, 이 Region에서는 GC 동작이 최적으로 동작하지 않는다.
  • Available/Unused: 아직 사용되지 않은 Region을 의미한다.

G1 GC에서 Young GC를 수행할 때는 STW(Stop-The-World) 현상이 발생하며, STW 시간을 최대한 줄이기 위해 멀티스레드로 GC를 수행한다. Young GC는 각 Region 중 GC대상 객체가 가장 많은 Region(Eden 또는 Survivor 역할)에서 수행되며, 이 Region에서 살아남은 객체를 다른 Region(Survivor 역할)으로 옮긴 후, 비워진 Region을 사용 가능한 Region으로 돌리는 형태로 동작한다.

G1 GC는 멀티 프로세서 머신에서 동작하는 대규모 업무용 어플리케이션을 위한 알고리즘으로 기본적으로 2GB 이상의 힙을 사용하는 크리티컬한 어플리케이션에서는 Full GC 수행 시에 스레드가 수십 초간 멈추는 한계를 보였는데 이를 극소화했다. 따라서 GC 시간 때문에 대용량 힙을 사용하지 못하는 일이 사라지게 되었다.

다른 JAVA 버전의 default GC

  • JAVA 5: Serial GC
  • JAVA 6: Serial GC
  • JAVA 7: Parallel GC
  • JAVA 8: Parallel GC

Q4. 개발자가 GC를 발생시키는 방법은 없을까?

보기

System.GC()를 명시적으로 사용하여 GC를 발생시킬 수 있지만 이 경우에는 Full GC가 발생한다.

참고

Root Set

GC는 말 그대로 Garbage를 모으는 작업인데 여기서 Garbage란 사용되지 않는 객체를 말한다. 좀 더 명확히 설명하면 객체의 사용 여부는 Root Set과의 관계로 판단하게 되는데 Root Set에서 어떤 식으로든 Reference 관계가 있다면 Reachable Object라고 하며 이를 현재 사용하고 있는 객체로 간주하게 된다.

Reachable Object를 판별하는 기준은 다음과 같다.

  • Local Variable Section, Operand Stack에 객체의 참조 정보가 있다면 Reachable Object이다.
  • 메소드 영역에 로딩된 클래스 중 constant pool에 있는 reference 정보를 토대로 쓰레드에서 직접 참조하진 않지만 constant pool을 통해 간접 link 하고 있는 객체는 Reachable Object이다.
  • 아직 메모리에 남아 있으며 Native Method 영역으로 넘겨진 객체의 reference가 JNI 형태로 참조관계가 있는 객체는 Reachable Object이다.
JVM 메모리 구조

  • Java Source: 사용자가 작성한 Java 코드(.java)
  • Java Compiler: Java Source 파일을 JVM이 해석할 수 있는 Java Byte Code로 변경
  • Java Byte Code: Java Compiler에 의해 수행된 결과물(.class)
  • Class Loader: JVM 내로 .class 파일들을 Load하여 Loading된 클래스들을 Runtime Data Area에 배치
  • Execution Engine: Loading된 클래스의 Byte Code를 해석(Interpret)
  • Runtime Data Area: JVM이라는 프로세스가 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간
  • 메소드 영역
    • 클래스, 변수, 메소드, 스태틱 변수, 상수 정보 등이 저장되는 영역
    • 모든 쓰레드가 공유
  • 힙 영역
    • new 명령어로 생성된 인스턴스와 객체가 저장되는 구역
    • Garbage Collection 발생
    • 모든 쓰레드가 공유
  • 스택 영역
    • 메소드 내에서 사용 되는 값들(매개변수, 지역변수, 리턴값 등) 저장
    • 메소드가 호출 시 LIFO로 하나씩 생성
    • 메소드 실행 완료 시 LIFO로 하나씩 제거
    • 각 쓰레드 별로 하나씩 생성
  • PC Register
    • CPU의 레지스터와 비슷한 역할
    • 현재 수행 중인 JVM 명령의 주소값 저장
    • 각 쓰레드 별로 하나씩 생성
  • Native Method Stack
    • 다른 언어(C/C++ 등)의 메소드 호출을 위해 할당된 구역
    • 언어에 맞게 스택이 형성되는 구역
Reference
  • https://www.holaxprogramming.com/2013/07/20/java-jvm-gc/
  • https://bkim.tistory.com/16
  • https://d2.naver.com/helloworld/37111
  • https://code-factory.tistory.com/48
  • https://b.luavis.kr/server/g1-gc
  • https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html

Tags:

Categories:

Updated: