C, C++은 개발자가 메모리를 직접 관리해야 하지만 Java에서는 개발자가 별도로 관리할 필요가 없이 JVM의 Garbage Collector에서 알아서 메모리를 관리해 준다.
가비지 컬렉터는 이름 그대로 쓰레기를 수집해 주는 뜻이다. 가비지 컬렉터를 이해하기 전에 쓰레기가 무엇을 뜻하는지 한번 알아보자.
Java에서 쓰레기는 Heap 메모리 영역에 생성된 객체들 중에 더 이상 참조되지 않는 객체들을 의미한다. 이게 무슨 뜻일까?
간단하게 다음과 같이 String 객체를 하나 선언했다고 하자. 이전 JVM 구조에서 메모리 영역을 공부할 때 스택 영역에는 지역 변수, 파라미터 등 임시 값, 힙 영역에는 new로 생성된 객체가 저장된다고 하였다.
1. String str = new String("Before");
2. str = new String("After");
따라서 1번 라인을 실행하면 스택 영역과 힙 영역에는 다음과 같이 할당될 것이다.
2번 라인을 수행하게 되면 어떻게 될까? str이라는 변수는 스택 영역에 새롭게 할당되지 않는다. 힙 영역에 "After"이라는 새로운 객체가 하나 할당되며 str 변수가 이를 참조하도록 한다.
만약 우리가 Before 객체를 사용하고 싶다면 어떻게 해야 할까? "Before"를 참조하는 변수가 존재하지 않기 때문에 우리는 Before 객체의 주소를 알 수가 없다.
결국 Before 객체는 도달할 수 없는 객체, Unreachable Object가 된다. 이를 제거해서 메모리를 관리해 주는 것이 바로 가비지 컬렉터이다.
그렇다면 JVM의 가비지 컬렉터는 어떤 방법으로 더 이상 참조되지 않는 객체들을 찾아서 제거하고 메모리를 관리하는 걸까? 단순하게 생각해보면 스택 영역에 할당된 모든 변수들을 탐색하면서 힙 영역에 참조하고 있는 객체의 유무를 판단하면 될 것 같다. 한번 알아보도록 하자.
가비지 컬렉터는 Mark, Sweep의 과정을 통해 실행된다.
Mark는 스택 영역에 할당된 모든 변수, Reachable Object들을 탐색하며 어떤 객체를 참조하고 있는지 Mark 하는 작업이다. 이때 GC를 수행하는 스레드를 제외한 모든 스레드들은 중단된다. 이를 Stop the World라고도 부른다.
Sweep은 힙 영역의 객체들 중 Mark 되지 않은 객체들을 제거하는 작업이다.
아무래도 힙 영역이 가비지 컬렉터의 주요 대상인 것 같으니 힙 영역에 대해서 조금 더 자세히 알아보자.
힙 영역은 총 5개의 영역으로 나뉜다. 힙 영역을 여러 개로 나눈 이유는 효율적으로 GC가 일어나게 하기 위함이다. JDK7까지는 Permanent 영역이 존재하였지만 JDK8부터는 Permanent 영역이 사라지고 일부가 Metaspace 영역으로 변경되었다.
GC는 Minor GC, Major GC로 나뉜다.
Minor GC는 Young generation에서 일어나는 가비지 컬렉션이고, Major GC는 Old generation에서 일어나는 가비지 컬렉션을 의미한다. 그럼 하나씩 살펴보도록 하자.
먼저 Minor GC의 동작 과정이다.
1. 우리가 new 연산으로 객체를 생성하면 먼저 Eden 영역에 객체가 할당된다. 계속해서 객체를 생성하여 Eden 영역이 꽉 차게 된다면 Minor GC가 일어난다. Eden 영역의 Unreachable Object들은 제거될 대상이기 때문에 Eden 영역에 남겨 제거되도록 하고 Reachable Object들은 survivor1 영역으로 옮겨준다.
2. 다시 한번 Eden 영역이 꽉 차서 Minor GC가 일어나게 된다면 위의 과정을 반복한다. Eden 영역의 Reachable Object들을 survivor1의 영역으로 옮겨준다. 이때 survivor1의 영역이 가득 차게 된다면 survivor1 영역의 Reachable Object들을 survivor2의 영역으로 옮겨준다. 이때 age 값을 증가시켜주고 옮겨준다. age는 이후에 Old 영역으로 넘어가는 객체를 판단하는 기준이 된다. 이후 Eden, survivor1 영역을 제거해 준다.
3. 다시 한번 Minor GC가 일어나게 된다면 위의 과정을 반복한다. 만약 survivor2의 영역이 가득 차게 된다면 Reachable Object들을 age 값을 증가시켜 survivor1의 영역으로 옮겨주고 survivor2 영역을 정리해 준다.
4. 만약 Minor GC가 계속해서 발생하게 되어 survivor 영역에 있는 Reachable Object들의 age가 일정 기준을 넘게 되면 앞으로 계속해서 사용될 수 있는 객체라고 판단하여 Old generation 영역으로 옮겨준다. 이 과정을 Promotion이라고 한다.
그렇다면 Old generation 영역도 꽉 차게 된다면 어떻게 될까? Old generation 영역의 가비지들을 제거해야 할 것이다. 이것이 바로 Major GC이다.
Major GC는 Old generation 영역이 꽉 차게 되었을 때 발생한다. 다만, Major GC는 Minor GC보다 시간이 훨씬 많이 걸리고 실행 중에 GC를 제외한 모든 스레드가 중지된다.
Old generation 영역에 있는 Unreachable Objcet를 Mark, Sweep 과정을 통해 제거한다. 제거하게 되면 Heap 메모리 영역에 빈 메모리 공간이 생기게 되는데 이 부분을 없애기 위해 재구성을 하게 된다. 메모리를 옮기는 도중에 다른 스레드가 메모리를 사용해버리면 안 되기 때문에 모든 스레드를 중지하는 것이다.
지금까지 GC가 어떻게 메모리를 관리하는지에 대해 살펴봤다. 마지막으로 간단하게 어떤 GC들이 있는지 GC의 종류에 대해서 알아보자.
GC는 Old Generation을 어떤 방법으로 관리하냐에 따라 Serial, Parallel, CMS, G1(Garbage First) 4가지로 나뉜다.
1. Serial Garbage Collector
가장 기본적인 GC이다. 하나의 CPU로 Young 영역과 Old 영역을 순차적으로 처리한다. Old 영역이 꽉 차게 된다면 순차적으로 탐색하면서 Unreachable Object를 표시하여 한 곳으로 모은 후 제거하는 Mark serrp compact 알고리즘을 사용한다.
굉장히 단순한 방법이기 때문에 Minor GC, Major GC 모두 Stop-the-World 방식이다.
2. Parallel Collector
Serail Garbage Collector는 단일 스레드이기 때문에 굉장히 비효율적이다. Parallel Collector는 멀티 스레드를 사용하여 병렬 처리를 통해 성능을 개선한 GC이다.
하지만 이 역시도 Minor GC, Major GC 모두 Stop-the-World 방식이다.
3. CMS Collector
Stop-the-World 방식을 줄일 수 있다면 성능을 개선할 수 있지 않을까라는 생각에서 나온 것이 바로 CMS Collector이다.
이전까지의 GC들은 Old 영역에서 Mark serrp compact 알고리즘을 사용하여 제거하였다. CMS Collector는 Unreachable Objcet들을 제거 후 빈 메모리 공간을 재구성하지 않는다.
따라서 CMS Collector는 히프 영역의 메모리가 클 때 적합하다.
4. G1 Collector
CMS Collector의 단점은 메모리에 빈 공간이 생기면서 메모리 파편화가 발생하게 되는 것이다.
G1 Collector는 이를 개선한 GC이다. G1 Collector는 Young, Old 영역을 구분하지 않고 히프 영역을 여러 개의 Region으로 나눈다.
만약 Old 영역에 해당하는 Region이 꽉 차게 된다면 해당 Region을 돌면서 Unreachable Object들을 제거하고 Reachable Object들은 다른 Region에 저장한다.
이렇게 한다면 현재의 Region은 빈 상태가 되며 다른 Region에 Reachable Object들이 저장되면서 메모리 단편화를 없앨 수 있게 된다.
다만, 별도의 Region들이 필요하기 때문에 마찬가지로 히프 영역의 메모리가 클 때 적합하다.
[ Reference ]
'IT 개인 공부 > Java' 카테고리의 다른 글
[Java] 람다(Lambda) & 스트림(Stream)(2) (0) | 2021.07.12 |
---|---|
[Java] 람다(Lambda) & 스트림(Stream)(1) (0) | 2021.07.12 |
[Java] JVM(Java Virtual Machine) (0) | 2021.07.11 |
[Java] final 키워드 (0) | 2021.07.07 |
[Java] final, finally, finalize (0) | 2021.06.29 |
댓글