9월 24일 금요일, 모의 면접을 진행할 기회를 갖게 되었습니다.
부족했던 부분을 복기하며 하나씩 채워가고자 합니다.
1. 가비지 컬렉션이 어떤 원리에 의해 동작하는지 설명해주세요.
현재 진행중인 Scope를 벗어난 변수를 JVM에서 자동적으로 메모리에서 할당하는 것을 의미합니다.
(그럼 Class에 field값은 어떻게 되나요? 현재 진행중인 코드가 아니니까 사라지나요?)
음.. 잘 모르겠습니다..
다시 생각해도 정말 끔찍합니다 ;;
모의 면접 과정임에도 5분 정도 지나자 실제 면접과 다름없는 두려움(?)과 긴장감이 몰려왔습니다.
중간중간 몇번이나 마이크를 음소거하고 얼굴을 감싸쥐고 오열했습니다.
형편없다고 느껴지는 저의 실력과 답변에 괴로웠습니다.
그래서 다시 복기하며 채웁니다.
오늘은! 가비지 컬렉션입니다.
2. 학습 출처
2020년 10월 20일에 업로드된 10분 테코톡, 엘리의 GC
Memory Management in the Java HotSpot Virtual Machine
아랫쪽에 위치한 두 링크는 작성된지 좀 오래된 문서인 점 참고부탁드립니다.
3. Garbage Collection의 개념과 대상
1. Heap 메모리에 동적 할당되었으나 참조되지 않는 대상을 탐지하여 메모리에서 해제하는 JVM의 기능입니다.
2. 개발자가 코드로 호출할 필요 없이 JVM이 백그라운드에서 데몬 스레드로 자동적으로 수행합니다.
3. 아래 코드를 예로 들면 map 객체가 생성될 때 heap 영역에 할당되는데
이때는 map 객체를 live object, 또는 reachable object 라고 합니다. 참조되는 객체라는 의미입니다.
마지막 줄의 map = null; 이 수행되면 map 객체는 dead object, unreachable object 라고 할 수 있습니다.
더 이상 참조되지 않는 객체라는 의미입니다.
이처럼 동적으로 생성되었으나, 더 이상 참조되지 않는 객체들은 가비지 컬렉션의 대상이 됩니다.
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>(); // heap에 map 객체 할당.
map.put("name", "richard");
map.put("age", "22");
map = null; // heap에 할당된 map 객체는 더 이상 참조되지 않음.
}
4. Garbage Collection 프로세스
4-1) GC가 일어나는 곳
GC가 일어나는 곳은 JVM Memory 영역 중 Heap 메모리 영역입니다.
4-2) Heap 메모리의 구성과 Promotion, Minor GC, Major GC (full GC)
Heap 메모리는 크게 Young Generation, Old Generation으로,
그리고 다시 Young Generation은 Eden, Survival1, Survival2로 나뉩니다.
객체가 최초 할당될 때에는 Eden 영역에 할당되고,
GC가 거듭되어도 계속해서 참조되는 객체는 Eden에서 Survival 영역으로,
그리고 다시 Survival 영역에서 Old Generation으로 이동합니다.
Survival 영역에서 Old Generation으로 이동하는 것은
Young Generation 에서 Old Generation 으로 이동하는 것이기도 한데,
이를 Promotion 이라고 합니다.
GC는 Young Generation과 Old Generation이 나뉘어서 진행되는데,
Young Generation에서 이뤄지는 GC는 Minor GC,
Old Generation에서 이뤄지는 GC는 Major GC 또는 full GC 라고 합니다.
4-3) Young Generation & Old Generation이 구분된 이유
Heap 메모리가 위처럼 구분되어 나누어진 이유는
더욱 효율적인 메모리 관리를 위해서 입니다.
그것은 객체의 생애주기와 관련이 있습니다.
아래의 내용은 GC가 설계될 때 고려된 점 두 가지입니다.
1. 대부분의 객체는 금방 unreachable 하게 된다.
2. Old Generation의 객체가 Young Generation 객체를 참조할 일은 극히 드물다.
1번을 해석해보자면,
대부분의 객체는 heap 영역에 할당된지 얼마 안되어 곧 GC 대상이 되므로,
GC를 자주 수행하는 것이 메모리 관리에 효율적일 것이라는 의미가 됩니다.
또한 1번을 뒤집어보면,
대부분의 객체가 아닌 소수의 예외 객체들은 아주 오랫동안 참조된다는 의미입니다.
따라서 이러한 객체들은 따로 보관하여 빈번하게 이뤄지는 GC 대상이 되지 않도록 하여
성능을 최적화할 수 있다는 의미가 됩니다.
2번 가설은 Minor GC와 Major GC의 알고리즘 기법에 적용됩니다.
Minor GC는 빈번하게 이뤄지고 적은 공간을 차지하기 때문에 속도에 주안점을 둡니다.
Major GC는 많은 공간을 차지하기 때문에 공간적 효율성에 더 주안점을 둔 알고리즘이 적용됩니다.
4-4) Mark & Sweep
앞서 4-2에서 객체는 Eden에 최초할당되고, 이후 GC를 거듭하며
계속해서 참조되는 변수는 Survival로, 또다시 Old Generation으로 이동된다고 언급했습니다.
이번엔 조금 더 구체적으로 어떻게 참조되지 않는 객체를 구분해내는지 알아보겠습니다.
GC가 실행되면 객체가 참조되고 있는지 여부를 확인해야 합니다.
이를 위해 GC Root 영역으로부터 참조여부를 조회합니다.
참조되고 있는 객체는 Mark됩니다.
Mark 작업이 완료된 이후엔 Sweep 작업이 수행됩니다.
Mark되지 않은 객체가 메모리에서 해제되는 것입니다.
4-5) Compact
이하 내용은 2번 학습출처의 3번째 링크에 명시한 문서의 3번째 페이지의 Compaction 내용 중,
주목할만한 내용을 요약해본 것입니다.
1. 바로 옆에 할당된 객체라고 해서 같은 시점에 메모리에서 해제되리라는 법은 없다.
2. 1은 메모리가 GC를 거치면서 파편화될 수 있음을 의미한다.
3. 파편화란, 메모리 내 비어있는 공간이 서로 떨어져있어 크기가 큰 객체를 할당하기 어려워지는 것을 의미한다.
4. 파편화를 최소화하기 위해 JRockit JVM은 Major GC때마다 Old Generation에 Compaction을 수행합니다.
5. Compaction은, 객체들을 가까운 곳으로 모으고, heap 메모리의 아래쪽으로 보냅니다.
6. 5번 작업으로 인해 heap 메모리의 큰 빈 공간을 상단에 마련합니다.
7. Compaction 영역과 Compaction 방법 등은 어떤 GC가 사용되느냐에 따라 달라질 수 있습니다.
아래 사진은 4번째 링크에 명시한 문서의 9페이지에 있는 이미지입니다.
그림으로 보면 이해가 쉬울 것 같습니다.
4-6) Promotion 과정
이번엔 Eden에 할당된 객체가 Old Generation 영역으로 Promotion 되는 과정을 알아보겠습니다.
- 새로운 객체는 Eden에 할당됨.
- Eden이 가득차면 GC.
- GC에서 살아남은 객체는 Survival 1으로 이동. (현재 Survival 1이 활성화된 상태)
- Survival 1이 가득차면 GC.
- GC에서 살아남은 객체는 Age 값이 증가하며 Survival 2로 이동.
- 이제부터 Survival 1은 비활성화되고, Survival 2가 활성화됨.
- 즉, Eden이 가득차서 GC가 일어나고 이때 살아남은 객체는 Survival 2로 이동됨.
- Survival 2가 가득차면 GC
- GC에서 살아남은 객체는 Age 값이 증가하며 Survival 1으로 이동.
- 이제부터 Survival 2는 비활성화되고, Survival 1이 활성화됨.
- 즉, Eden이 가득차서 GC가 일어나고 이때 살아남은 객체는 Survival 1으로 이동됨.
- 위 내용이 반복되며 계속해서 살아남는 객체는 Age값이 계속 증가됨.
- Age값이 MaxTenuringThreshold 설정을 초과한 객체는 Old Generation 영역으로 옮김.
5. Stop The World
GC가 수행될 때, JVM이 애플리케이션 실행을 멈추는데, 이를 Stop the World라 부릅니다.
GC가 실행되어 Stop the World 가 발생되면 GC를 수행하는 스레드 이외의 스레드는 작업을 중단합니다.
GC가 완료되면 중단되었던 스레드가 다시 실행됩니다.
6. GC 종류
1) Serial GC
단일 스레드로 수행되므로 CPU Core가 한 개인 환경에서도 사용 가능합니다.
Mark-Compact Collection 알고리즘을 사용합니다.
메모리 파편화 방지를 위한 Compact 기능이 추가되었습니다.
2) Parallel GC
다중 스레드로 수행됩니다.
Serial GC 보다 빠릅니다.
Java 1.8의 기본 GC입니다.
3) CMS GC ( Concurrent Mark Sweep GC )
Stop the World 로 인한 중단 시간이 짧습니다.
따라서 응답시간이 중요할 때 사용하기 좋습니다.
단 메모리와 CPU 리소스가 더 많이 소비됩니다.
Compaction 기능이 제공되지 않습니다.
4) G1 GC
바둑판처럼 Heap 메모리 영역을 나누어 관리합니다.
Stop the World 로 인한 중단 시간이 짧습니다.
Compaction 을 기능을 제공합니다.
Java 9 부터 기본 GC로 채택되었습니다.
7. Permanent Generation과 Metaspace
4번에서 다루지 않았던 Permanent Generation에 대해 간략히 알아보고자 합니다.
Permanent Generation은 JVM의 Class Loader Subsystem에 의해 로드된 클래스의 메타 데이터가 저장되던 공간입니다.
Java 8부터는 이것이 Meta space로 바뀌었습니다.
수행하는 내용은 동일하지만, 메모리의 영역이 바뀌었습니다.
Permanent Generation은 Heap 영역이었는데, Meta Space는 Native 메모리 영역에 속합니다.
따라서 JVM이 아닌 OS 레벨에서 관리되기 때문에 개발자가 영역 확보 상한을 의식할 필요가 없게 되었습니다.
8. 요약 및 결론
1. GC는 JVM의 백그라운드에서 자동적으로 메모리 관리를 수행하는 JVM의 기능입니다.
2. 이러한 GC는 JVM의 메모리 영역 중 Heap 메모리에서 이뤄지는데,
그 이유는 Java에서 새로운 객체가 할당될 때, Heap 메모리 영역에 할당되기 때문입니다.
3. GC는, 참조되는 변수를 구별하여(Mark), 참조되지 않는 변수를 해제(Sweep)하는 식으로 이뤄집니다.
4. GC가 수행되는 동안, GC를 수행하는 스레드 이외의 스레드가 중단되어 애플리케이션 중단이 발생하는데,
이를 Stop the World라고 합니다.
5. GC의 성능을 최적화하기 위하여 Heap 메모리 영역이 구분되어 있는데,
크게 Young & Old Generation으로 나눌 수 있고 각 영역에서 Minor GC와 Major GC가 수행됩니다.
6. Java 8부터는 Parallel GC가, Java 9부터는 G1 GC가 기본 GC로 사용됩니다.
다음 포스팅에서는 Java의 애너테이션(Annotation)의 작동 원리에 대해 알아보겠습니다.
'Java & Spring' 카테고리의 다른 글
🤔 객체지향 생활체조 돌아보기 (우테코 레벨2를 마치며) (0) | 2022.06.17 |
---|---|
오라클 클라우드 Autonomous Database DB접속 로컬 + 서버 (0) | 2021.10.18 |
문자열 유효성 검증 유틸 메소드 StringUtils.hasText(String) (2) | 2021.09.04 |
스프링부트가 정적 리소스 제공하는 과정 (0) | 2021.08.30 |
SpringBoot 로그 레벨 동적으로 변경하기 (runtime logging level change) (0) | 2021.06.12 |