개요
Java를 공부하던 중 Garbage Collection에 대해 작성한 좋은 글들을 발견해서 이참에 GC를 공부해봐야겠다고 생각했다. 마침 Garbage Collection에 대한 Oracle의 원문이 있어, 이를 해석하고 참고하여 글을 작성한다.
Java
자바는 객체 지향 프로그래밍 언어로 다음과 같은 특징이 있다.
- 플랫폼에 종속적이지 않다. 자바 애플리케이션은 클래스 파일에 저장되고 JVM에서 로드되는 바이트 코드에 의해 컴파일이 된다. 그리고 애플리케이션은 JVM에서 실행되기 때문에 다양한 OS와 기기에서 실행될 수 있다.
- 객체지향적이다. 자바는 C와 C++의 많은 특징을 가져오고 발전시킨 객체 지향적인 언어이다.
- 다양한 표준 라이브러리가 있다. 자바는 수없이 많은 미리 만들어진 객체들이 있고, input, output, 네트워크, 날짜 조작 등 여러 분야에 사용될 수 있다.
- Automatic Garbage Collection. 이 글의 주제이자 가장 큰 특징이다. 자바는 자동으로 메모리를 할당/해제 해준다. 따라서 메모리 관리의 부담이 적다.
JVM
JVM은 Java Virtual Machine의 약자로 추상적인 연산 기계(abstract computing machine, 가상머신)이다. JVM은 프로그램을 작성하고 실행할 수 있는 기계 같은 프로그램이다. 특정 OS에서의 각각의 JVM은 모두 자바 코드를 해당 OS에서 실행할 수 있도록 번역해준다. 따라서 Java 프로그램은 어디서든 같은 인터페이스나 라이브러리를 사용할 수 있고, 특정 OS에 종속적이지 않다.
HotSpot JVM은 JVM의 한 종류로써 자바 프로그램의 생산성을 높여주는 JVM이다.
아래 HotSpot JVM의 구조에서 보라색으로 강조된 부분이 HotSpot JVM의 핵심 부분이다.
세가지의 요소가 강조되어있다.
그중 힙은 객체가 저장되는 곳이다. 이 영역은 Garbage Collector에 의해 관리된다. 대부분의 tuning 옵션이 힙(메모리)의 크기와 적절한 Garbage Collector를 고르는 것과 연관되어 있다. JIT 컴파일러는 성능에 큰 영향을 끼치지만 직접 tuning 할 요소는 거의 없다.
위의 설명에서 보았듯이, JVM에서 개발자가 성능을 위해 조절할 수 있는 부분은 바로 Garbage Collector이다.
이제부터 Garbage Collection이 무엇인지 알아보도록 하자.
Stop the world
Garbage Collection을 알아보기 전 미리 알아야 할 용어가 있다. 바로 stop the world인데, 이는 모든 애플리케이션과 스레드가 Garbage Collection을 위해 멈추는 것이다. Garbage Collection 작업이 다 끝나야 중단된 애플리케이션이 다시 시작된다. Garbage Collector의 성능은 이 stop the world event의 시간을 최소화하는 것에 달려있다.
Automatic Garbage Collection
Automatic Garbage Collection은 힙 메모리에서 어떤 객체가 사용 중이고 어떤 객체가 그렇지 않은지 알아내고, 사용되지 않는 객체를 제거하는 과정이다. 사용 중이거나 참조되는 객체는 프로그램의 어느 부분에서 여전히 그 객체에 대한 포인터를 유지하고 있다는 뜻이다. 그러나 사용되지 않거나, 참조되지 않는 객체는 더 이상 프로그램에 필요하지 않다는 뜻이다. 따라서 불필요한 객체에 대한 메모리는 다시 확보되어야 한다.
C와 같은 프로그래밍 언어에서는 메모리를 수동으로 할당하거나 해제해야 한다. 그러나 자바에서는 Garbage Collector에 의해 자동으로 메모리가 해제된다. Automatic GC의 기본적인 과정에 대해 알아보자.
Step 1 : Marking
첫 번째 과정은 마킹으로 Garbage Collector에서 메모리의 어느 부분이 사용 중이고 그렇지 않은지 알아내는 과정이다. 참조되는 객체들은 파란색이고, 나머지는 주황색으로 나타냈다. 모든 객체는 마킹 과정에서 결정을 위해 스캔된다. 이 과정은 시스템에서의 모든 객체를 스캔해야 하기 때문에 시간이 많이 소요되는 작업이다.
Step 2 : Normal Deletion
이 과정은 참조되지 않는 객체를 삭제하고, 참조되는 객체와 free space에 대한 포인터를 남긴다.
memory allocator가 새로 할당될 객체를 위해 free space에 대한 참조를 가지고 있는다.
Step 2a : Deletion with Compacting
객체를 삭제하고 나서 추가적으로, 성능 향상을 위해 남아있는 객체를 압축시킬 수 있다. 객체를 한 곳에 모아둠으로써 새로운 메모리 할당을 더 쉽고 빠르게 할 수 있다. Memory Allocator는 free space의 시작 주소만 가지고 있으면 된다. 그 후 새로운 객체를 순차적으로 할당한다.
Generational Garbage Collection을 쓰는 이유
JVM의 모든 객체를 마킹하고 압축하는 과정은 비효율적이다. 많은 객체가 할당될수록 객체의 수는 더 늘어나고 Garbage Collection에 소요되는 시간 또한 늘어난다. 그러나 분석에 의하면 대부분의 객체의 수명은 짧다.
위의 그래프에서 볼 수 있듯이, 대부분의 객체는 그리 오랫동안 유지되지 않는다.
JVM Generations
위에서 알아낸 사실을 통해 JVM을 발전시킬 수 있다. 힙을 세대에 따라 작은 부분으로 나누는 것이다. 나뉜 부분은 Young Generation, Old Generation, Permanent Generation으로 불린다.
Young Generation은 새로운 객체가 할당되고 나이를 먹는 곳이다. Young 영역은 Eden 영역과 Survivor 영역으로 나뉘는데 Survivor 영역은 다시 두 개로 나뉜다. 객체가 언제 어떤 영역에 할당되는지는 차차 알아보도록 하자.
Young generation이 꽉 차면, minor garbage collection이 작동한다. minor collection은 높은 사망률(객체의 짧은 수명)에 의해 쉽게 최적화된다. 이 중에서 살아남은 객체들은 나이를 먹고 결국 Old Generation으로 옮겨진다.
Old Generation은 오랫동안 살아남은 객체들을 저장하기 위한 공간이다. Young Generation에서 일정 수준 이상의 나이를 먹은 객체들이 이곳으로 옮겨진다. 그러나 어느 순간 Old Generation도 꽉 차게 되고 이는 major garbage collection이 작동하게 한다.
모든 minor, major garbage collection은 stop the world event이다(주로 major GC가 더 느리다). 따라서 애플리케이션의 반응성(responsive)을 위해 major GC 시간이 최소화되어야 한다. 그리고 major GC를 위한 Stop the World event의 시간 길이는 Old Generation 공간 최적화를 위해 사용한 Garbage Collector에 의해 영향을 받음을 알아두자.
Permanent Generation은 JVM에 의해 요구되는 클래스와 메서드를 설명하는 데 쓰이는 메타 데이터들이 포함된다.
Generational Garbage Collection Process
우리는 위에서 왜 Generational GC가 필요하고, 어떻게 구역을 나눌지 알아봤다. 이제 어떻게 그 과정이 이뤄지는지 알아보자.
1. 우선 새로운 객체는 Young 영역의 eden 영역에 할당된다. Survivor 영역은 맨 처음에는 비어있다.
2. eden 영역이 꽉 차면, minor GC이 작동한다.
3. minor GC 이후 살아남은 객체들은 Survivor 영역의 첫 번째 공간(S0)으로 옮겨지고 나이의 초기값 1살을 부여받는다. 살아남지 못한 객체들은 제거된다.
4. 계속해서 eden 영역에 객체들이 할당되고, 다음 minor GC 때도 똑같이 살아남은 객체들이 Survivor 영역에 할당된다. 그러나 이때 Survivor의 첫 번째 영역(S0)이 아닌 두 번째 영역(S1)에 저장된다. 그리고 첫 번째 영역(S0)에 있던 객체들은 각각 나이를 먹고 두 번째 영역(S1)으로 옮겨진다. 모든 객체가 S1으로 옮겨지면 S0와 eden 영역은 비어있게 된다. S1으로 옮겨진 살아남은 객체들은 각각 다른 나이를 가지고 있다.
5. 다음번 minor GC에서 같은 과정이 반복된다. S1에 있던 객체들과 eden에서 살아남은 객체는 모두 S0로 옮겨지고 나이를 먹는다. 이때 S1과 Eden 영역이 비어있게 된다.
6. 이번 단계는 Promotion이다. minor GC 이후 나이를 먹은 객체들이 특정 나이(age threshold, 나이 임계치)에 도달하면 (예시에서는 8살) 그 객체들은 Young 영역에서 Old 영역으로 승격된다.
7. minor GC가 반복되고 객체들은 계속 승격되어 Old 영역으로 옮겨진다.
8. Old 영역이 가득 차면 이제 major GC가 작동하고 Old 영역을 압축한다. 그림에 나온 Tenured는 Old Generation의 다른 이름이다.
(참고로 G1 GC는 JDK 7부터 사용이 가능해졌고, JDK 9 부터 기본 GC로 사용된다.)
마치며
간단하게 Garbage Collection에 대해 알아봤다. Java 개발자라면 GC에 대한 지식이 있어야 함을 알았지만, 막상 공부하기는 쉽지 않았는데, Oracle의 공식 문서와 Naver의 포스트를 정독하니 쉽게 이해됐다. 물론 GC에 대해 공부할 건 더 남아있지만 이 정도 지식은 기본 소양으로 가지고 있어야겠다.
참고 자료
- 네이버 게시글 : https://d2.naver.com/helloworld/1329
- 오라클 원문 : https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
'프로그래밍 > Java' 카테고리의 다른 글
[Java] String pool과 new String (0) | 2020.09.21 |
---|---|
[Java] 자바 가상머신의 메모리 모델 (0) | 2020.05.20 |
[Java] 클래스 변수와 클래스 메소드 (Static) (0) | 2020.05.19 |
[Java] 자바 프로그램의 이해와 실행의 원리 (0) | 2020.05.14 |
[Java] 쓰레드(Thread) (0) | 2020.02.12 |