JVM의 Execution 영역에 있는 가비지 콜렉터와 가비지 콜렉션에 대해서 알아보자.
C나 C++을 해본 사람들은 다음과 같은 명령어가 낯이 익을 것이다. ~(), mallac(), free() 이 들은 모두 프로그래머가 직접 메모리를 할당해주거나 해제하는 역할을 하는 메서드이다. 하지만 자바는 이와 달리 프로그래머가 직접 메모리를 할당 해제해주는 것이 아닌 GC(Garbage Collector)가 메모리를 자동으로 할당하거나 해제해주는 작업을 해준다.
C나 C++ 처럼 어떠한 가비지 콜렉터가 없이 프로그래머가 직접 메모리를 관리를 해줘야 하는 언어를 비관리형 언어(Unmanaged Language)
Java, GO, C#, Python 같이 가비지 콜렉터가 직접 메모리를 관리해주는 언어를 관리형 언어(managed Language) 라고 부른다.
Garbage Collection이 어떻게 동작하는 지에 대해서 알기 위해서는 우선 Java의 힙 영역이 어떤 것을 전제하여 설계가 되었는지 이해하는 것이 좋다.
자바의 힙 영역 설계의 전제
자바의 힙 영역은 다음과 같은 전제로 설계되었다.
- 대부분의 객체는 금방 접근불가능한(Unreachable) 상태가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
즉, 객체의 대부분은 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다.
실제로도 삼성의 어떤 교육에서도 "객체를 재사용하는 비율은 거의 20%밖에 안된다." 라고 한다.
이러한 설계를 기준으로 힙 영역에 메모리 구조는 다음과 같이 설계되어 있는데 이를 살펴보자.
참고로 접근 불가능한 객체라는 것은 주소를 null 로 잡아준 것을 말한다.
자바 힙 영역의 물리구조
자바의 힙 영역은 크게 Young/new Generation, Old, Permenent Generation(JDK1.8 이전), Meta Space(JDK1.8 이후)로 구성되어 있다. Permenent Generation과 Meta Space는 생성된 객체들의 정보의 주소값이나 클래스 및 메서드 등의 메타 정보들이 있는 공간이고 추가적으로 Meta Space에는 Method Area에 있던 constant pool, static Object 들이 이전되었다.
이는 이후에 GC를 설명하는 과정에 있어서 필요한 부분이라 생각하여 첨언하였다.
Young/New Generation
해당 영역은 다시 크게 2가지로 분류된다. 바로 Eden 영역과 Survivor 영역이다. 그리고 Survivor영역은 Survivor0, Survivor1로 2개로 나뉘어져서 3가지로 구분된다고 볼 수 있다. 새롭게 생성된 객체가 할당되는 영역이며, 이 영역에서의 대부분의 객체들은 금방 접근불가상태가 되기 때문에, 많은 객체가 Young 영역에서 생성되었다가 사라진다. 추가로 Survivor 영역 두 곳 중에서 한 곳은 꼭 비어있어야 한다. 해당 영역에서 발생하는 GC를 Minor GC 라고 한다.
Old 영역
Old 영역은 Young/New Generation 영역에서 살아남은 객체들이 저장되는 공간이다. Young/New Generation 영역과 달리 메모리 영역이 크다. 따라서, Young/New Generation 영역에 비해서 상대적으로 GC가 적게 발생한다.
해당 영역에서 발생하는 GC를 Major GC 라고 한다.
그러면 새롭게 생성되는 클래스는 무조건 Eden 영역에 저장되는 것인가? 라는 생각이 들 수 있다.
Old 영역에는 자주 살아 있는 객체들은 대부분 크기가 큰 객체들인데, JVM 튜닝 옵션을 사용해서 임계치를 설정하여 임계치보다 크면 바로 Old 영역에 적재하게끔 할 수 있다. 해당 명령어는(-XX : PretenureSizeThreshold) 이다.
추가적으로 Old 영역에는 Young 영역에는 없는 512 Bytes로 구성된 카드 테이블이 있다.
Young 영역에 있는 참조불가능한 객체를 메모리 해제할 때, 비율은 적지만 Old 영역에서 Young 영역을 참조하는 객체가 존재 할 수 있다.
이럴 경우 Old 영역에서 현재 참조중인 객체인지 확인을 해야 하는데, Old 영역이 상당히 커서 전부 다 순회하면 많은 시간이 소요되게 된다. 이를 방지하기 위해서 Old 영역에서 Young 영역을 참조 중인 객체의 주소를 카드 테이블에 저장하여 카드 테이블만 확인하면 참조 중인지 확인 할 수가 있어 속도가 많이 개선된다.
그럼 이어서 Minor GC, Major GC가 어떻게 동작하는지 살펴보자.
Garbage Collection 동작 방식
Minor GC와 Old 영역은 GC 동작 방식이 조금 다르지만 공통적으로 동작하는 부분이 있다. 바로 Stop the World와 Mark and Sweep 이다.
stop the world
GC 수행을 위해서 JVM이 애플리케이션의 실행을 멈추는 작업이다. 이때, GC를 수행하는 쓰레드를 제외하고 모든 쓰레드들의 작업이 중단되게 된다. 따라서, GC의 성능 개선을 한다면 stop the world의 오버헤드를 줄이는 것이 관건이다. 이를 해결하기 위한 많은 알고리즘 들이 존재한다.
Mark and Sweep
Mark : 사용되는 메모리와 사용되지 않은 메모리를 식별하는 작업을 말한다.
Sweep : Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업을 말한다.
Minor GC
위와 같은 공통적인 작업을 기반으로 Minor GC를 살펴보자.
1. 새로 생성된 객체는 일반적으로 Eden 영역에 적재된다.
2. Eden 영역이 가득차게 되면 이 때 한 차례 Minor GC를 수행한다.
3. Minor GC 과정에서 참조 불가능한 객체들은 메모리가 해제되고, 이 과정에서 살아남은 객체들이 데이터가 있는 Survivor로 이동을 한다.
4. 1, 2, 3번 과정이 지속적으로 반복이 되다가 어느 순간 Survivor 가득차게 되면 Survivor 영역에서도 GC를 수행한다. 이 과정에서 살아남은 객체들은 다른 Survivor 영역으로 이동된다.
Old 영역으로 이동되는 시기는 각 객체의 생존횟수(age)를 Object Header에 기록을 하고 생존 횟수가 어느정도 임계값을 넘어서게 됐을 때, Old 영역으로 이동시킨다.
Major GC
Major GC 영역은 Minor GC 보다 상당히 간단한 방식으로 진행된다.
단순히 Old 영역이 가득차게 되면 GC가 수행된다. 하지만 Old 영역은 Young 영역에 비해서 메모리 공간이 상당히 크기 때문에 자주 발생하지는 않지만 한 번 Major GC가 수행되면 Minor GC의 수행시간의 약 10배정도 걸린다고 한다.
기본값으로는 비율이 Minor : Major = 1 : 3 으로 메모리 크기가 구성 되어있다.
추가적으로 JDK 1.8 이전에는 static 변수가 Method 영역에 있어서 Memory Leak가 발생하는 문제가 있었지만, 힙 내부에 Meta Space 영역으로 넘어오게 되면서 이제는 참조 불가능한 static Object들도 메모리가 해제 될 수 있다고 한다.
Eden의 성능 향상
Eden에 객체를 적재하는 과정에 있어서 적재 될 공간을 찾아야 하고 멀티 쓰레드 상황에서는 Lock을 통해 동기화를 해주어야 한다. 만약 그러한 과정이 없다면, race condition 이 발생하고 말 것이다. Hotspot JVM은 이러한 과정을 2개의 기법으로 성능을 개선하였다.
1. bump the pointer : Eden 영역에 마지막으로 할당 된 객체의 주소를 캐싱해서 다음 주소에 적재하기 쉽도록 해준다.
2. TLABs(Thread Local Allocation Buffers) : 각각의 쓰레드마다 Eden 영역에 객체를 할당하기 위한 주소를 부여하여 동기화 작업 없이 빠르게 메모리를 할당하는 방법이다.
기존에 자바에서 동시성 문제를 해결하기 위한 ThreadLocal 을 이용한 그 방법이다.
이러한 것 말고도 실제로 stop the world 시간을 줄이기 위한 여러가지 알고리즘들이 있다. 현재 사용되는 것은 G1 GC 이다.
참고 자료
'Language > Java' 카테고리의 다른 글
함수형 프로그래밍과 일급 객체란 (0) | 2023.06.04 |
---|---|
Logger에서 hibernate SQL 로그 출력이 안될 경우 (0) | 2023.05.18 |
JVM (0) | 2023.04.21 |
enum의 Enum 상수 객체의 변수 사용과 생성 (0) | 2023.04.12 |
7-7 내부 클래스 (재업로드), 익명 클래스 (0) | 2023.03.29 |