내가 만든 자바 코드가 어떤 과정으로 실행되는지 알아보기 위한 "JVM 알아보기" 네 번째 포스팅입니다.
이번 포스팅에서는 JVM 의 GC
와 Heap
에 대해서 알아보겠습니다.
JVM GC(Garbage Collection)
개발자는 직접 자바의 메모리 관리를 하지 않아도 된다.
메모리 관리를 JVM 이 알아서 하기 때문인데, JVM 내에서 메모리 관리를 해주는 것을 가비지 컬렉터 라고 부른다.
가비지 컬렉터는 힙에서 사용 가능한 모든 객체를 추적하고 사용하지 않는 객체에 할당 된 메모리를 해제한다.
이 작업을 가비지 컬렉션, GC 라고 한다.
GC 의 프로세스를 알아보기 이전에, 먼저 GC 와 관련 된 개념들을 정리해보자.
UnReachable Object
유효한 참조가 없는 객체를 UnReachable Object(도달할 수 없는 객체)라고 한다.
그리고 UnReachable 하다고 간주되는 객체는 GC 의 대상이 된다.
그렇다면 유효한 참조를 판단하는 기준은 무엇일까?
앞서 JVM 의 GC 는 힙에서 사용 가능한 모든 객체를 추적한다고 했다. GC 는 객체 그래프를 순회하며 모든 객체를 식별한다.
그래프를 순회하려면 시작점이 필요한데 이 시작점을 GC의 root 라고 한다.
이 GC 루트에서 직접 또는 간접적으로 참조되는 모든 객체는 Reachable 한 객체(도달할 수 있는 객체)로 구분되어 GC 되지 않는다.
반대로 GC 루트에서 참조되지 않는 UnReachable 한 객체들은 사용하지 않는다고 간주되어 GC 의 대상이 된다.
GC root 는 주로 다음과 같다.
- Class, 클래스 변수
- Java Stack, 메서드 실행 시에 사용하는 지역 변수와 파라미터들
- 메서드 영역의 정적 변수
- Native Stack, JNI(Java Native Interface) 에 의해 생성된 객체
GC 의 대상이 되는 객체
- null 로 선언 된 참조 변수
- 재할당 된 참조 변수
- 메서드 내부에서 생성된 객체
- 고립된 섬(Island of Isolation) : 객체 A 와 B 가 서로만을 참조할 때 두 객체 모두 호출할 수 있는 방법이 없어진 경우 또는 참조되지 않는 단일 객체
Island of Isolation 의 예시를 보자.
객체를 두 개 만들어서 서로만을 참조하게 한 뒤, 두 객체를 모두 null 로 만들고 가비지 컬렉터를 호출해보자.
Java
// Main class
public class GFG {
GFG i;
// Method 1
// Main driver method
public static void main(String[] args)
{
// Creating object of class inside main() method
GFG t1 = new GFG();
GFG t2 = new GFG();
// Object of t1 gets a copy of t2
t1.i = t2;
// Object of t2 gets a copy of t1
t2.i = t1;
// Till now no object eligible
// for garbage collection
t1 = null;
// Now two objects are eligible for
// garbage collection
t2 = null;
// Calling garbage collector
System.gc();
}
// Method 2
// overriding finalize() Method
@Override protected void finalize() throws Throwable
{
// Print statement
System.out.println("Finalize method called");
}
}
Output
Finalize method called
Finalize method called
객체를 파괴하기 전에 가비지 컬렉터는 finalize 메서드를 호출한다.
t1 만 null 로 만들고 가비지 컬렉터를 호출하면 GC 가 동작하지 않는다. t2 라는 외부 참조가 있기 때문이다.
t1 과 t2 가 모두 null 이 되면 두 객체를 호출할 수 있는 방법이 없으므로 두 객체 모두 GC 의 대상이 된다.
stop-the-world
GC 는 root 에서부터 객체 그래프를 순회하며 객체가 참조되고 있는지 추적한다. 그리고 참조되지 않는 객체를 제거한다.
이 작업은 애플리케이션이 실행되는 중에 병행되어야 하는데, 그러기 위해서는 GC 이외의 모든 쓰레드의 작업을 중단해야한다.
GC 를 실행하기 위해 JVM 이 GC 를 실행하는 쓰레드 이외의 모든 쓰레드의 작업을 멈추는 것을 stop-the-world 라고 한다.
작업이 완료될 때까지 모든 작업이 중지되기 때문에 성능 저하의 원인이 된다.
따라서 이 stop-the-world 의 작업 시간을 줄이는 것이 GC 튜닝의 주요 목적이 된다.
JVM GC 의 알고리즘
Mark and Sweep
기본적으로 GC 는 Mark and Sweep 이라는 두 가지 프로세스로 동작한다.
Marking 단계에서 사용 중인 메모리와 그렇지 않은 메모리를 식별하고
Sweep 단계에서 사용하지 않는다고 식별 된 메모리를 제거한다.
새 메모리 할당 시의 성능을 위해 참조되지 않는 객체를 삭제한 뒤에 빈 공간을 압축하는 방법도 있다.
GC 와 Heap
Weak Generational Hypothesis, 약한 세대 가설
GC 는 Heap 영역 내의 모든 객체들을 대상으로 동작한다. 그리고 실행 중인 애플리케이션은 시간이 지남에 따라 더 많은 객체를 생성한다.
따라서 시간이 지날수록 점점 더 객체가 많아지고 GC 작업의 시간이 증가할 것이다.
이를 해결하기 위해 JVM 은 객체의 세대를 분리했다. 다음 그래프를 보자.
X 축은 객체의 수명을, Y 축은 살아있는 객체 수(할당 된 총 바이트 수)를 나타낸다.
그래프의 분포를 보면 왼쪽에 수명이 짧은 객체가 엄청나게 많다. 대부분의 객체는 짧은 시간동안 사용되고 메모리가 해제되는 것이다.
이것을 weak generational hypothesis 라고 한다.
이 전제를 통해 GC 의 효율적인 수집이 가능하다. 객체의 세대를 분리하여 각각 GC 를 수행하는 것이다.
Young 영역
우선, Young 영역부터 알아보자. 이 영역에는 새로 생성한 객체의 대부분이 할당된다.
Young 영역의 GC 프로세스는 다음과 같다.
- Eden 영역에 객체가 생성된다.
- Eden 영역이 가득 차면 GC가 발생하여 Mark and Sweep 의 과정을 거쳐서 살아있는 객체만 Survivor 영역 중 하나로 이동되고, 다시 Eden 영역을 채운다.
- 이 과정을 반복하여 하나의 Survivor 영역이 가득 차게 되면 살아있는 객체만 나머지 비어있는 Survivor 영역으로 이동된다. 이때, Eden 영역에 있는 살아있는 객체들도 비어있던 Survivor 영역으로 간다. 즉, Survivor 영역의 둘 중 하나는 반드시 비어있어야 한다.
이와 같이 Young 영역에서 발생하는 GC 를 Minor GC 라고 한다.
Minor GC 는 대부분의 객체가 가비지일 것을 가정하고 최적화되어 동작한다.
일반적으로 더 작은 공간이 할당되고, 많은 객체를 검사하지 않기 때문에 속도가 빠르다.
몇 차례의 Minor GC 가 수행되고도 살아남은 객체들은 Old 영역으로 이동하게 된다. 이를 Promotion 이라고 한다.
각 객체는 Minor GC 에서 살아남은 횟수를 기록하는 age bit 를 가지고 있고, Minor GC 가 발생할 때마다 age bit 값이 1씩 증가한다.
age bit 값이 MaxTenuringThreshold 라는 설정값을 초과하게 되는 경우 객체가 이동된다.
Old 영역
Old 영역에서 수행되는 GC 는 Major GC 라고 한다. 모든 라이브 객체를 수집해야 하기 때문에 훨씬 속도가 느리며, 수행 빈도도 적다.
Old 영역의 GC 는 GC 방식에 따라 처리 프로세스가 달라진다.
GC 방식에는 여러 유형이 있다.
- Serial GC
- Parallel GC
- Parallel Old GC (Parallel Compacting GC)
- Concurrent Mark and Sweep (CMS)
- G1(Garbage First) GC
- 그 외의 여러 유형
마치며
이번 포스팅에서는 JVM 의 GC 와 JVM Heap 의 구조에 대해 알아보았습니다.
다음 포스팅에는 GC 의 유형 별 프로세스와 장단점에 대해 다루도록 하겠습니다.
Reference
'Java' 카테고리의 다른 글
[Java] JVM 알아보기 - (6) Thread, Java Thread Model (0) | 2022.12.05 |
---|---|
[Java] JVM 알아보기 - (5) JVM GC 유형, G1 GC (0) | 2022.11.29 |
[Java] JVM 알아보기 - (3) JVM Memory Structure (0) | 2022.11.23 |
[Java] JVM 알아보기 - (2) JVM ClassLoader (0) | 2022.11.17 |
[Java] JVM 알아보기 - (1) JDK, JRE, JVM (0) | 2022.11.16 |