개요
GC(Garbage Collection)은 에덴 영역에 생성된 객체의 메모리 할당/해제를 자동으로 관리하는 자바의 핵심 기능이다.
GC 덕분에 개발자는 메모리 관리에 대한 걱정 없이 개발에만 집중할 수 있게 됐고, 이러한 장점이 크고 복잡한 애플리케이션을 만들 때 자바를 선택하게 만들었다. 하지만 성능 최적화와 메모리 관련 에러를 방지하기 위해서는 GC의 작동 원리에 대한 이해가 필수적이다.
Java의 GC 작동 원리
자바에서 모든 객체는 heap 영역에 저장된다. heap은 동적 할당을 위한 메모리 영역(runtime data area) 중 하나다.
객체가 더 이상 참조되지 않으면 GC의 수집 대상이 된다.
GC는 heap 영역에서 사용되지 않는 객체를 주기적으로 찾아서 메모리를 해제해준다.
이 과정에서 marking, sweeping, compacting 단계를 거친다. 이를 mark and sweep이라고 부른다.
- Marking: heap 영역에서 참조되는 모든 객체들을 표시해주는 작업을 한다.
- Seeping: Marking 단계 이후, heap 영역에서 참조되지 않는 객체들의 메모리를 해제한다.
- Compacting: 일부 GC에서는 Sweeping 단계 이후 Compacting 작업을 처리한다. Compacting은 Sweeping 이후 메모리의 조각화를 최소화하기 위해 메모리를 재배열한다.
JVM에서 GC는 자동으로 동작하기 때문에 개발자는 메모리를 관리할 필요가 없다.
GC는 별도의 스레드에서 실행되고 일반적으로 백그라운드에서 작동하기 때문에 프로그램에 영향을 미치지 않는다.
JVM Generation
Heap은 세가지 부분으로 나뉜다.
- Young Generation: 객체가 생성되면 할당되는 영역이다. Young Generation이 가득 차면 MinorGC가 발생한다. Minor GC는 매우 빠르게 참조되지 않은 객체를 수집한다. 살아남은 객체(참조되는 객체) age가 올라가고 일정 수준 age가 되면 Old Generation으로 이동한다.
- Stop the World: GC가 실행중인 스레드를 제외한 모든 스레드의 실행이 중지되는 것을 말한다.
- Old Generation: 오래 살아남은 객체를 저장하는 데 사용된다. 일반적으로 Young Generation 객체에 대한 임계값이 설정되고 객체의 age가 임계값에 도달하면 객체가 Old Generation으로 이동한다. Old Generation의 메모리가 가득 차게되면 Major GC가 실행된다. Major GC는 모든 객체를 스캔하기 때문에 Minor GC에 비해 훨씬 느리다.
- Permanent Generation: Permanent Generation에는 JVM이 애플리케이션에서 사용하는 클래스와 메서드의 정보가 저장된다. 애플리케이션에서 사용하는 클래스를 기반으로 런타임에 JVM에 의해 저장된다.
GC 작동 원리
1. 생성된 모든 객체는 Young Generation의 에덴 영역에 할당된다. S0, S1 영역은 비어있다.
2. 에덴 영역이 가득 차면 Minor GC가 실행된다.
3. 참조된 객체는 S0 영역으로 이동한다. 참조되지 않은 객체는 삭제된다.
4. 다음 Minor GC에도 에덴 영역에서는 새로운 객체가 생성된다. 참조되지 않은 객체는 삭제되고 참조된 객체는 S1 영역으로 이동한다.
S0의 객체들도 age 증가 후 S1로 이동한다. 이후 S0과 에덴 영역 모두 지워진다. 이제 Survior 영역(S0, S1)에는 age가 다른 객체들이 함께 있다.
5. 이제 Minor GC에서 위와 같은 프로세스가 반복된다. (S0 -> S1, S1 -> S0)
6. Minor GC 실행된 후, Young Generation에서 age 임계값에 도달한 객체는 Old Generation으로 이동된다.
7. 위와 같은 프로세스가 계속 실행되면서 오래된 객체는 Old Generation에 계속 저장되게 되고 Old Generation 메모리가 임계값을 넘게되면 Major GC가 실행되서 Old Generation의 영역을 정리하고 압축한다.
Java GC 장점
Java Garbage Collection의 장점은 다음과 같다.
- 자동 메모리 관리: GC를 사용하면 개발자는 메모리 할당/해제를 수동으로 관리할 필요가 없다. 즉, 메모리 관리보다는 코드 작성에 집중할 수 있어 개발 효율성을 올릴 수 있다.
- 메모리 누수 방지: GC는 애플리케이션이 더 이상 사용하지 않는 메모리를 해제하지 않을 때 발생할 수 있는 메모리 누수를 방지할 수 있다. 메모리를 제대로 해제해주지 않으면 필요 이상으로 많은 메모리를 소비하게 되고 성능 저하 및 충돌이 일어날 수 있다.(OutOfMemory)
- 동적 메모리 할당: Java의 GC는 동적 메모리 할당이 가능하다. 동적 메모리 할당은 런타임에 필요에 따라서 메모리가 할당되는 것을 말한다.
- 메모리 최적화: GC는 애플리케이션에서 사용되지 않는 메모리를 재사용하기 때문에 메모리 사용을 최적화 할 수 있다.
Java Garbage Collectors 종류
- Serial GC: Java의 기본 GC이며 일반적으로 높은 처리량이 필요하지 않은 작은 규모의 애플리케이션에서 사용된다. Stop the World가 발생하는 것을 방지하는 데 도움이 되는 GC이다.
- 단일 스레드를 사용하는 가장 단순한 GC
- 작은 heap 메모리를 가진 애플리케이션에 적합하다.
- -xx:+UseSerialGC 옵션으로 활성화 할 수 있다.
- Parallel GC: 높은 처리량이 필요한 애플리케이션을 위해 설계된 GC. 여러 CPU를 사용하여 프로세스 속도를 높이기 때문에 큰 heap이 필요한 애플리케이션에서 유용하다. Parallel GC는 GC를 실행할 때 애플리케이션 스레드를 동결 시킨다는 것을 주의해야 한다.
- 여러 스레드를 사용하는 GC
- 중간 규모에서 대규모 데이터를 가진 멀티 프로세서 시스템에 적합하다.
- -xx:+UseParallelGC 옵션으로 활성화 할 수 있다.
- CMS(Concurrent Mark Sweep) Collector: 일시 정지 시간이 짧아야 하는 애플리케이션을 위해 설계되었으며 객체가 많은 애플리케이션에서 유용하다.
- 애플리케이션 스레드와 동시에 작동하여 일시 중시 시간을 최소화한다.
- Java 9부터 deprecated 되었고, Java 14부터는 완전히 제거되었다.
- -xx:+UseConcMarkSweepGC 옵션으로 활성화 할 수 있다.
- G1 GC: 큰 heap 영역을 위해 설계되었으며, Young 객체와 Old 객체를 함께 처리할 수 있다. 여러 스레드를 사용하여 heap을 동시에 스캔하고 압축한다.
- 대용량 힙 메모리를 위해 설계된 GC
- heap을 균등한 크기의 영역으로 나누어 관리한다.
- Java 9부터 기본 GC로 설정되었다.
- --XX:+UseG1GC 옵션으로 활성화 할 수 있다.
- ZGC(Z Garbage Collector)
- 대용량 heap과 짧은 지연 시간이 필요한 애플리케이션을 위해 설계된 GC
- Java 15부터 정식 기능이 되었다.
- -XX:+UseZGC 옵션으로 활성화 할 수 있다.
참조
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
'Java' 카테고리의 다른 글
Java - JVM 구조와 동작 방식 (1) | 2024.11.03 |
---|---|
Mybatis - SAXParseException: The content of elements must consist of well-formed character data or markup 원인과 해결법 (0) | 2024.06.05 |
Java - File로 파일 목록 이름 조회하기 (1) | 2024.01.25 |
Java - @JasonCreator로 DTO에서 유연하게 Enum Type 받기 (0) | 2023.04.26 |
Java - 커스텀 애너테이션으로 유효성 검사하기 (0) | 2023.04.22 |