해당 포스팅의 모든 이미지의 출처는 오라클 공식 문서이다.
가비지 컬렉션 (GC: Garbage Collection)
Automatic garbage collection is the process of looking at heap memory, identifying which objects are in use and which are not, and deleting the unused objects. An in use object, or a referenced object, means that some part of your program still maintains a pointer to that object. An unused object, or unreferenced object, is no longer referenced by any part of your program. So the memory used by an unreferenced object can be reclaimed
- oracle.com
정의: 스택 영역으로부터 참조가 끊겨 더 이상 사용되지 않는 힙 영역의 인스턴스의 메모리를 해제하는 프로세스.
왜 사용할까?
C/C++ 에서는 메모리에 직접적으로 접근할 수 있고, malloc()
, free()
메서드를 통해 메모리의 할당과 해제가 가능하다. 하지만 자바에서는 오로지 JVM
을 통해서 간접적으로 메모리에 접근이 가능하다.
메모리 할당과 해제는 개발자의 실수가 프로그램의 치명적이 오류를 발생시킬 수 있다.
이런 크리티컬한 작업을 자동으로 수행해주기 때문에 GC를 사용한다.
장점
메모리의 해제를 자동으로 수행한다는 점에서 안정성이 보장된다는 장점이 존재한다.
가비지 컬렉션 알고리즘에 의해 수행되기 때문에 성능이 좋다.
단점
Major GC를 수행할 때, 가비지 컬렉션과 관련된 스레드를 제외한 모든 스레드가 정지한다. 이에 따른 성능 오버헤드가 발생하기 때문에 이를 개선하는 것이 중요하다.
위와 같은 현상을 stop-the-world
라고 한다. STW는 대략 1초간 모든 스레드가 정지한다. 실시간으로 데이터가 통신하는 서버가 1초간 정지한다면 장애가 발생한 수준이다 라는 말을 들었다. 따라서 STW가 발생하지 않도록 설계하는 것이 가장 이상적이라고 생각한다.
Heap 영역
가비지 컬렉션의 동작방식을 정리하기 전 힙 영역에 대해 정리해야 한다.
힙 영역은 Young Generation, Old Generation, Permanent (JDK 8 이전) 으로 나누어진다.
(위의 이미지는 JDK 7 기준)
Young Generation에는 말그대로 어린 객체(생성 기준)가 저장되며, eden, survivor space로 나뉜다.
여기서 발생하는 가비지 컬렉션 프로세스를 minor garbage collection이라 칭한다.
반대로 Old Generation에는 늙은 객체가 저장된다.
Young Generation에서 가비지 컬렉션을 수행하며 특정 임계값 까지 살아남았을 때 해당 객체가 Old Generation 영역으로 이동된다.
여기서 발생하는 가비지 컬렉션 프로세스를 major garbage collection이라 칭한다.
Why Generational Garbage Collection?
왜 generation별로 가비지 컬렉션이 동작하도록 했을까?
As stated earlier, having to mark and compact all the objects in a JVM is inefficient. As more and more objects are allocated, the list of objects grows and grows leading to longer and longer garbage collection time. However, empirical analysis of applications has shown that most objects are short lived.
역시나 효율성 때문이다.
위의 지표를 보면 지간이 지남에 따라 할당된 객체는 적어지고, 대부분의 객체의 수명은 매우 짧다.
힙 영역의 전역에 해당하는 가비지 컬렉션과 메모리 최적화를 진행하려면 너무 큰 오버헤드가 발생한다.
따라서 위의 지표를 바탕으로 얻은 새로 생성된 객체는 수명이 매우 짧다는 결과를 반영하여 새로운 객체와 오래된 객체를 나누고 이를 구분하여 가비지 컬렉션을 수행한다.
동작 방식
처음으로 마킹이라는 프로세스가 동작한다.
이 마킹 단계에서 어떤 객체를 삭제할지 결정한다. unrechable 상태의 객체는 참조되지 않는 객체(Unreferenced Objects)로 인식하여 이를 마킹한다.
이후 가비지 컬렉터에 따라 Normal Deletion 혹은 Deletion with Compacting 프로세스를 수행한다.
normal deletion 프로세스는 마킹된 메모리를 해제하고 해당 공간을 free space로 놔둔다.
deletion with comapcting 프로세스는 위의 normal deletion 프로세스를 수행하고 난 뒤 compacting 작업을 수행한다.
가비지 컬렉션을 수행하는 동작방식을 힙 영역과 연관 지어 설명한 글은 많다 참고하길 바란다.
간단하게 설명하면 처음 생성된 객체는 YG의 eden영역에 저장된다. eden영역이 가득 차, 저장 공간이 부족하다면 gc가 발생한다.
여기서 수거되지 않은 객체는 '살아남음 카운트' 1이 추가되고, survivor space로 이동한다.
이러한 현상이 반복되다가 survivior space가 가득찬다면 여기서도 gc가 발생한다. 여기서 수거되지 않은 객체는 '살아남음 카운트' 1이 추가되고 비어있는 survivor space로 이동한다.
이러한 현상이 반복되다가 특정 객체들의 '살아남음 카운트'가 가비지 컬렉터에 저장된 특정 임계값에 도달하면 해당 객체들은 OG영역으로 이동한다. OG영역 또한 가득차게 된다면 gc가 발생하는데 이 때, STW가 발생하므로 잘 관리 해주어야 한다.
개인 저장용 참고할만한 포스팅
'JAVA' 카테고리의 다른 글
BigDecimal 생성, 비교시 유의사항 (0) | 2024.01.17 |
---|---|
데몬 스레드 (Daemon Thread) (2) | 2024.01.01 |
[JAVA 객체지향] 비즈니스 로직과 View의 의존성 배제하기 (0) | 2023.11.02 |
[JAVA 객체지향] getter의 사용을 지양하고 객체간 대화로 해결하자 (0) | 2023.10.30 |
[JAVA] 접근자 메서드(getter/setter)를 왜 써야할까? (0) | 2023.05.25 |