유효하지 않은 메모리인 가비지 발생, 그럼 JVM의 가비지 컬렉터가 불필요한 메모리를 정리한다. 대신 자바에서는 명시적으로 불필요한 데이터를 표현하기 위하여 null 선언
JAVA나 Kotlin에서는 메모리 누수를 막기 위하여 주기적으로 메모리를 검사하여 정리해준다.
Major GC - Minor GC
JVM의 Heap 영역은 두가지 전제 조건으로 설계되었다.
1. 대부분의 객체는 금방 접근이 불가능한 상태가 된다.
2. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
⇒ 객체는 대부분 일회성이며, 메모리에 남는 경우는 드물다. 따라서 생존기간에 따라 물리적으로 Young, Old 두 가지 영역으로 나누었다.
Old - Young
Old
•
Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
•
Young 영역보다 초기 크게 할당 되며, 영역이 큰 만큼 가비지는 적게 발생
•
Old 영역에 대한 가비지 컬렉션을 Major GC라고 부른다.
Young
•
새롭게 생성된 객체가 할당되는 영역
•
대부분 객체가 금방 접근 불가 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
•
Young 영역에 대한 가비지 컬렉션을 Minor GC라고 부른다.
Young 보다 Old가 더 큰 이유??
Young 영역의 수명이 짧은 객체들은 큰 공간이 필요하지 않으며 큰 객체들은 Young 영역이 아니라 Old에 할당
드물게 Old영역에서 Young 사라진 객체를 참조한다면?
이러한 경우를 대비하여 Old 영역에는 512bytes의 덩어리로 된 카드 테이블 존재
Card Table?
Old 영역에서 Young 영역으로 참조가 일어날 때 마다 그에 대한 정보를 표시하는 곳
도입 이유는 Old에서 사용되는 Young 영역의 객체를 확인하기 위하여 Old를 전부 검사하는 것은 비효율적
그래서 따로 조회용 테이블을 만듦
Young 영역에 대한 GC가 실행될 때에는 Card Table 조회를 통하여 GC 대상인지 검사
가비지 컬렉션의 동작 방식
기본적으로 Young과 Old 영역은 서로 다른 메모리 구조로 되어 있기 때문에, 세부적인 동작 방식은 다르다.
하지만 기본적으로 가비지 컬렉션이 실행된다 하면 공통적인 2단계를 따른다.
1. Stop the World
2. Mark and Sweep
Stop the World
•
JVM이 Application의 실행을 멈추는 작업
•
GC가 실행될 때는 GC를 실행하는 Thread를 제외한 모든 Thread가 중단, GC 실행 이후 작업 실행
•
GC의 성능 튜닝을 한다고 하면 보통 Stop the World의 시간을 줄이는 작업을 하는 것
Mark and Sweep
•
Mark : 사용되는 메모리와 사용하지 않는 메모리를 식별하는 작업
•
Sweep : 식별 된 메모리 중 사용하지 않는 메모리를 해제하는 작업
•
Stop the World에서 GC가 중지된다면 Mark와 Sweep 단계 실행
Minor GC의 동작 방식 : Young 영역
•
Eden 영역의 메모리가 Full이라면 Minor GC 실행
•
Mark and Sweep 단계 이후 사용되는 메모리로 식별 된다면 Survivor영역으로 옮김
•
Survivor 영역은 1,2로 나누어 있지만 반드시 하나의 영역에만 데이터가 존재
•
동작 순서
1.
새로 생성된 객체가 Eden영역에 할당
2.
객체가 생성되어 Eden 영역이 Full상태 Minor GC 실행
1) Eden 영역에서 사용하지 않는 객체는 해제
2) 사용되는 객체는 1개의 Survivor영역으로 이동
3.
1 ~ 2 과정 반복 이후 Survivor1영역이 가득 차면 다른 Survivor2영역으로 이동(이때 이동 되기 전 Survivor영역 1개는 완전히 비어있는 상태가 된다.)
4.
3번까지의 과정으로 반복한 이후 Old영역으로 이동 실행
Eden에서 Survivor은 참조가 해제 즉, null을 참조할 때 기준이다. 그럼 Survivor에서 Old는 무슨 기준일까?
객체의 생존 횟수를 카운트를 한다. Minor GC에서 객체가 살아남은 획수를 의미하는 Age를 Object headder에 기록한다. 기록된 Age를 보고 Old영역으로 이동한다.
조금 신기한 JAVA의 기능? Hotspot JVM?
- 1.2 버전 이후부터 sun의 기본 JVM이다.
- 말 그대로 Hot한 Spot을 찾아서 해당 부분에서는 JIT 컴파일러를 사용하는 방법
⇒ Hotspot JVM에서는 Eden 영역에 객체를 빠르게 할당하기 위하여 Bump-the-Pointer라는 기술을 사용한다. 이것은 할당된 메모리의 바로 뒤에 메모리를 할당하는 방법으로 Free List를 검색하는 등의 부가적인 작업이 없어 신속하게 할당 가능, 하지만 메모리에 변경을 가하는 것이기 때문에 Lock과 같은 동기화 작업이 수반된다. 그래서 JVM은 기본적으로 Multi-Thread 환경에 맞는 동기화작업을 위하여 TLAB(Thread Local Allocation Buffer)라는 기술을 추가 하였다. 이는 Thread 마다 할당을 위한 주소의 범위를 부여하여 그 범위 내에서는 아무런 동기화 작업 없이 할당을 가능하게 하는 것이다.
Major GC의 동작 방식 : Old 영역
•
객체들이 Eden 부터 Old까지 계속 이동 되어 Old영역이 가득 차면 실행
•
Old영역의 크기는 크고, Young영역을 참조할 수 있기 때문에 Minor GC의 시간에 10배 정도 걸린다(Minor GC 약 0.5~1초).
Old영역에 대한 GC
•
Old 영역은 GC를 실행하면 오래걸린다. 따라서 다양한 GC방식이 존재한다.
•
GC방식은 JDK9+버전 이후로 G1방식이 기본 GC로 자리잡았다.
GC의 종류
1) Serial GC
2) Parallel GC
3) Parallel Old GC(Parallel Compacting GC)
4) Concurrent Mark And Sweep GC(CMS)
5) G1 (Garbage First) GC
이중 G1 GC에 대하여 알아보자
G1 GC
G1의 경우 Young, Old 영역을 잊고 생각해야 한다.
- 바둑판 형식의 메모리에 객체를 할당하고 여기에 GC를 실행
- 만약 메모리가 꽉차면 다른 바둑판형 메모리에 할당
- CMS를 대체하기 위해 만들어짐
Never D2 기술블로그, 출처
변경 개요
•
Humongous,Available/Unused 두가지 영역이 존재
◦
Humongous : Region의 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간. 이 Region에서는 GC동작이 최적으로 동작하지 않는다.
◦
Available/Unused : 아직 사용되지 않은 Region을 의미한다.
•
Garbage First 라 명명된 이유는 회수가능한 영역 , 즉 쓰레기가 많을 것으로 예상되는 영역에 대한 수집 및 압축이 주가 되기 때문이다.
G1 GC의 효율성
Region으로 나누어 제거
G1 GC에서는 이전의 GC들처럼 일일히 메모리를 탐색해 객체들을 제거하지 않는다. 대신 메모리가 많이 차있는 영역(Region)을 인식하는 기능을 통하여 메모리가 많이 차있는 영역을 우선적으로 GC한다. 즉, G1 GC는 Heap Memory 전체를 탐색하는 것이 아닌 영역(Region)을 나눠 탐색하고 영역별로 GC가 일어난다.
또한 이전의 GC들은 Young Generation에 있는 객체들이 GC가 돌때마다 살아남으면 Eden → Survivor1,2으로 순차적으로 이동했지만, G1 GC에서는 순차적으로 이동하지 않는다. 대신 G1 GC는 더욱 효율적이라고 생각하는 위치로 객체를 재할당 시킨다. 예를 들어 Suvivor1영역에 있는 개체가 Eden 영역으로 가는 것이 더 효율적이라 판단될 경우 Eden 영역으로 이동시킨다. 즉, G1 GC 부터는 더이상 Young Generation에서 영역의 순서가 보장되지 않는다.
G1 GC 동작 방식
•
G1 GC에서 Full GC 가 수행될 때는 Initial Mark -> Root Region Scan -> Concurrent Mark -> Remark -> Cleanup -> Copy 단계를 거친다.
•
추가적으로, STW를 줄이기 위해서 병렬로 GC작업을 실행한다. 각 Thread별로 자신만의 Region을 잡고 작업하는 방식
1.
Initial Mark
Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다. 이 과정에서는 STW 현상이 발생한다.
2.
Root Region Scan
이전 단계에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 진행한다.
3.
Concurrent Mark
전체 힙의 Region에 대해 스캔 작업을 진행하며, GC 대상 객체가 발견되지 않은 Region 은 이후 단계를 처리하는데 제외되도록 한다.
4.
Remark
애플리케이션을 멈추고(STW) 최종적으로 GC 대상에서 제외될 객체(살아남을 객체)를 식별해낸다.
5.
Cleanup
애플리케이션을 멈추고(STW) 살아있는 객체가 가장 적은 Region 에 대한 미사용 객체 제거 수행한다. 이후 STW를 끝내고, 앞선 GC 과정에서 완전히 비워진 Region 을 Freelist에 추가하여 재사용될 수 있게 한다.
6.
Copy
GC 대상 Region이었지만 Cleanup 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운(Available/Unused) Region 에 복사하여 Compaction 작업을 수행한다.
더 자세한 사항은 아직 이해하기 어려워서 남겨두었다.
- GC Cycle