제목: "JGarbage Collector 제대로 알기"
작성자: velog(recordsbeat)
작성자 수정일: 2021년 7월 16일
링크: https://velog.io/@recordsbeat/Garbage-Collector-제대로-알기
작성일: 2022년7월31일
자바 이전의 C,C++같은 언어에서는 개발자가 직접 메모리 할당과 해제를 컨트롤 해야했다.
잦은 메모리 이슈가 까다로운 개발환경에서 벗어나기 위해 GC가 등장하였다.
Garbage Collector은 더이상 사용하지 않는 메모리 영역을 알아서 해제해주어서 사용가능한 메모리를 조절해준다.
문제는 GC가 눈치껏 작동하기 때문에 실행 조건이 일정하지 않다. 즉 내 마음대로 실행되게 하지 못한다.
그렇기 때문에 각 GC의 메커니즘을 이해하고 코딩 간 행위를 예측하여 센스 없는 GC가 알아서 잘 치워줄 수 있게 코드를 잘 정리해야 한다.
GC Heap 구조
JVM에는 heap 메모리 영역이 존재한다.
간단하게 설명해서 객체가 할당되는 영역인데 이 영역을 위 그림과 같이 쪼개놓는다.
그리고 각 영역을 간단하게 설명하자면 아래와 같다
young: 객체가 제일 먼저 생성되면 Young 영역에 할당된다(Minor GC가 발생한다.)
old
permanent
GC 원리 참고
Garbage Collection
특정 객체가 가비지인지 아닌지 판단하기 위해서 도달성,도달능력(Reachability)라는 개념을 적용한다
객체에 유효한 레퍼런스가 없다면 Unreachable로 구분해버리고, 있다면 Reachable로 구분된다.
참고: https://madplay.github.io/post/java-garbage-collection-and-java-reference
이렇게 Reachable과 Unreachable 상태로 나뉘어진 객체(메모리 영역)들은 GC의 알고리즘과 구현방식에 의해 이동,압축 및 삭제를 거치게 된다.
Object obj = new Object();
위 처럼 Thread Stack 이 직접적으로 Object에 접근 가능한 상태
null을 대입하지 않는 이상 gc 대상으로 취급되지 않음
SoftReference<Object> softReference = new SoftReference<>(new Object());
GC 구현에 따라 대상 될 수도 안 될수도 있다.
WeakReference<Object> weakReference = new WeakReference<>(new Object());
항상 GC의 대상이 된다.
올바르게 삭제하고 삭제 이후 작업을 조작하기 위한 타입
위와 다르게 사용자가 finalize 메서드에 의해 컨트롤 할 수 있다.
Object의 finalize 메서드가 말을 안들어 강한 참조가 역으로 일어날 수 있는 경우 위 참조 방식을 사용하면 유용하다.
참고
참고
싱글쓰레드 어플리케이션을 위한 GC이다.
이 중에서 운영 서버에서 절대 사용하면 안 되는 방식이 Serial GC다. Serial GC는 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식이다. Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어진다.
low-latency(pause)를 추구하는 어플리케이션 또한 적당하지 않다.
병렬처리가 가능한 GC이며 Throughput GC라 하기도 한다
멀티스레드로 Young 영역을 처리하고 싱글스레드로 Old 영역을 처리한다
배치 프로젝트와 같은 긴 pause가 허용되는 애플리케이션에 사용할 수 있다.
low pause 의 시초가 되는 GC다.
parellel GC 와 같이 young 영역을 병렬 처리하고 Old 영역 또한 병렬처리한다.
그러나 parellel 과 다른 점은 애플리케이션 백그라운드에서 진행 가능하다는 점이다.
stop the world 발생을 최소화하였다.
그러나 백그라운드로 실행되는 GC의 초기버전이라 메모리 파편화가 발생한다
만약 CPU 리소스가 부족해진다거나, 메모리 파편화가 너무 심해서 메모리 공간이 부족해지면 Serial GC 방식으로 동작한다.
64비트 컴퓨터 최적화된 GC로 4GB 이상의 Heap size 에 적합한 GC다.
메모리 리전 분할 정책으로 리전내부 gc 영역 존재한다.
mark-sweep and compact 알고리즘으로 CMS의 메모리 단편화 이슈를 해결하였다.
compact 알고리즘의 의해 살아남은 객체들을 새로운 Region으로 대피시키는 데 이 과정에서 pause가 발생한다.
low-pause GC 짧은 주기의 GC를 사용하여 최소한의 pause만 발생한다.
강력한 Concurrency 지원으로 heap 사이즈에 영향을 받지 않고 일정한 pause 시간을 보장한다.
아주 짧은 STW 시간으로 거의 대부분의 시간동안 애플리케이션 백그라운드에서 작동하므로, 빠른 응답을 요구하는 프로젝트에 적합하다.
OpenJdk 기반. jdk8부터 개발을 시작하여 jdk12에 release하였지만 oracle jdk는 지원하지 않는다.
G1을 기반으로 개발되었으며,G1의 compact 과정의 pause를 conrruent 하게 해결한 것이 가장 큰 특징이다.
이 역시 low-pause GC 며 heap 영역을 region으로 나누고 concurrent 하게 처리한다는 것 Shenandoah와 같은 개념이다. (10ms 이하의 pause를 목표로 한다.)
Oracle에서 만들었는데 Shenandoah와 라이벌 격인듯 하다.
jdk15에 release 되었다.
작은 프로젝트에 사용하면 된다.
싱글스레드 기반으로 heap size가 약 100MB 정도 되는 애플리케이션이면 적당하다.
GC와의 공존을 버리고 최대치의 애플리케이션이 성능을 이끌어내야한다면 Parallel을 사용하는 것이 좋다.
혹은 Pause 소요 시간에 대해 크게 신경쓰지 않는 프로젝트여도 좋다.
응답시간이 처리량보다 중요한 경우 적합하다.
API서버처럼 약 1초보다 짧은 pause 시간을 계속해 유지해야한다면 해당 GC를 사용 권장한다.
이 두 가지도 응답시간이 우선적일 때 사용하기 적합하다.
그러나 CMS/G1의 경우와 다른 점은 큰 heap size에서도 low-pause를 보장하기 때문에 heap size가 매우 크다면 고려해볼만하다.
GC를 사용하기 위해서는 몇몇 값을 설정해야 한다.
이 값들을 이리저리 설정하여 어플리케이션 성격에 맞게 GC를 최적화하는 것인데, 사실 GC 튜닝은 권장되지 않는다
Gc는 앞서 이야기 하였듯 마음대로 움직인다.
그렇기 때문에 GC 성능을 최적화하려면 상세한 동작원리부터 실질적인 벤치마킹까지 섬세하게 따져봐야 할 것들이 너무 많다
그래서 실상은 GC를 건드리는 것보다 어플리케이션 코드로써 메모리 최적화(공간복잡도를 낮춘다)를 권장한다.
JVM에서 -Xms 와 -Xmx 는 필수로 지정해야하는 값이다.
그리고 NewRatio 값이 GC성능에 많은 영향을 끼친다고 한다.
대부분의 GC는 Old Generation 의 부하가 크기 때문에 어찌보면 당연한 것이기도 하다.
JVM Xms, Xmx default 값은 어떻게 결정될까?
https://sarc.io/index.php/java/1092-jvm-default-heap-size
사실 위의 설정 값 외에도 GC에 따라 많은 설정 값들이 존재한다.
기대성능에 따라 여러 값들을 조정하고 이에 맞게 성능 측정을 하는 과정이 GC 튜닝 이라는 행위로 볼 수 있을 것이다.
GC를 모니터링 하는 방법
https://jjaesang.github.io/gc/2019/04/02/java-gc-monitoring.html
Spring actuator 의 Metric 에서도 gc 모니터링을 가능하게 해주는 설정
https://tomgregory.com/spring-boot-default-metrics/
https://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/html/production-ready-metrics.html
GC튜닝 참고링크
https://wiserloner.tistory.com/554
https://d2.naver.com/helloworld/184615