Garbage Collection - 2

dragonappear·2022년 7월 31일
0

JVM

목록 보기
3/6

출처

제목: "JGarbage Collector 제대로 알기"
작성자: velog(recordsbeat)
작성자 수정일: 2021년 7월 16일 
링크: https://velog.io/@recordsbeat/Garbage-Collector-제대로-알기
작성일: 2022년7월31일

GC란?

  • 자바 이전의 C,C++같은 언어에서는 개발자가 직접 메모리 할당과 해제를 컨트롤 해야했다.

  • 잦은 메모리 이슈가 까다로운 개발환경에서 벗어나기 위해 GC가 등장하였다.

  • Garbage Collector은 더이상 사용하지 않는 메모리 영역을 알아서 해제해주어서 사용가능한 메모리를 조절해준다.

  • 문제는 GC가 눈치껏 작동하기 때문에 실행 조건이 일정하지 않다. 즉 내 마음대로 실행되게 하지 못한다.

  • 그렇기 때문에 각 GC의 메커니즘을 이해하고 코딩 간 행위를 예측하여 센스 없는 GC가 알아서 잘 치워줄 수 있게 코드를 잘 정리해야 한다.

GC 참고: https://www.yalco.kr/04_garbage_collector

GC Heap 구조

JVM에는 heap 메모리 영역이 존재한다.

간단하게 설명해서 객체가 할당되는 영역인데 이 영역을 위 그림과 같이 쪼개놓는다.

그리고 각 영역을 간단하게 설명하자면 아래와 같다

  • young: 객체가 제일 먼저 생성되면 Young 영역에 할당된다(Minor GC가 발생한다.)

    • eden: 객체가 생성되면 이 영역에 위치한다
    • survivor: eden에서 발생한 GC에서 살아남은 객체들이 잠깐 생존해있는곳
  • old

    • 위 GC 과정을 반복하다가 계속해서 살아남은 객체가 존재하는 곳(Major GC가 발생한다.)
    • 기본적으로 데이터가 가득 차면 GC를 실행한다.
  • permanent

    • Method Area의 메타정보가 기록된 곳

Minor GC

  • Minor GC에서 발동되는 알고리즘을 Stop And Copy 알고리즘 이라고 부른다.
  • stop and copy 알고리즘은 GC의 빈도를 높여서 자잘한 청소작업을 여러번하여 사용자로 하여금 프로그램이 정지되는 경험을 주지 않게 하고 단편화 역시 처리하는 아주 좋은 알고리즘 이다.
    1. 대부분의 객체는 생성되고나서 금방 접근 불가능 상태가 된다
    1. 오래된 객체는 젊은 객체를 적게 참조한다.

Major GC

  • Old 영역에서 발생한다.
  • Old 영역은 메모리 성능 이슈가 발생할 수 있다.

GC 원리 참고


Garbage가 무엇인가?

  • 일단 GC는 뭔지 알겠다. 그런데 뭘 쓰레기로 보고 치울 것인가에 대한 기준이 있어야 GC가 동작할 것이다

Reachable/Unreachable

  • Reachable: 치우면 안되는거
    • 아직 누군가 참조하고 있는데 마음대로 버리면 안된다.
  • Unreachable: 치워야 하는거
    • 아무도 참조하지 않는데 메모리만 차지하고 있는 것들

  • Heap 메모리를 참조하는 영역을 Root Set 영역이라고 하는 것 같다
  • 위 그림에서는 stack,jin,method ared로 나누었다.
  • 그림에 화살표로 이어진 Object들은 참조가 이루어져 있으므로 Reachable 상태이다.
  • 반대로 참조 없이 둥둥 떠다니는 애들이 Unreachable이다.

Garbage Collection
특정 객체가 가비지인지 아닌지 판단하기 위해서 도달성,도달능력(Reachability)라는 개념을 적용한다
객체에 유효한 레퍼런스가 없다면 Unreachable로 구분해버리고, 있다면 Reachable로 구분된다.
참고: https://madplay.github.io/post/java-garbage-collection-and-java-reference

이렇게 Reachable과 Unreachable 상태로 나뉘어진 객체(메모리 영역)들은 GC의 알고리즘과 구현방식에 의해 이동,압축 및 삭제를 거치게 된다.


Reference Type

  • Reachable을 판단하는데 이를 개발자가 컨트롤할 순 없을까? 라고 하면 아래와 같이 Reference Type을 사용할 수 있다.

Strong Reference

Object obj = new Object();

위 처럼 Thread Stack 이 직접적으로 Object에 접근 가능한 상태
null을 대입하지 않는 이상 gc 대상으로 취급되지 않음

Soft Reference

SoftReference<Object> softReference = new SoftReference<>(new Object());

GC 구현에 따라 대상 될 수도 안 될수도 있다.

Weak Reference

WeakReference<Object> weakReference = new WeakReference<>(new Object());

항상 GC의 대상이 된다.

Phantom Reference

올바르게 삭제하고 삭제 이후 작업을 조작하기 위한 타입

위와 다르게 사용자가 finalize 메서드에 의해 컨트롤 할 수 있다.

Object의 finalize 메서드가 말을 안들어 강한 참조가 역으로 일어날 수 있는 경우 위 참조 방식을 사용하면 유용하다.


Reference Counting & Mark and Sweep

Reference Counting

  • 단순하게 참조하는 수를 센다.
  • 그러나 이 방법은 치명적인 단점이 존재한다 바로 순환참조를 해결할 수 없다.
  • 가운데 둥둥 떠다니는 회색 object들은 GC로 해결할 수 없게 된다.

Mark and Sweep

  • 그래서 나타난 것이 Mark and Sweep이다.
  • 각 Thread의 stack,method area 등 heap 영역 참조가 가능한 영역을 Root Set 이라고 한다. 이 Root Set 부터 시작하여 하나씩 따라가며 참조 상태를 확인하는 것이다.
  • 분면 Reference Counting 의 순환참조는 해결하였지만, Root Set의 크기에 따라 Marking 시간이 늘어남은 어쩔수없다.

참고


GC 알고리즘

참고

Serial

싱글쓰레드 어플리케이션을 위한 GC이다.
이 중에서 운영 서버에서 절대 사용하면 안 되는 방식이 Serial GC다. Serial GC는 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식이다. Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어진다.

low-latency(pause)를 추구하는 어플리케이션 또한 적당하지 않다.

Parallel

병렬처리가 가능한 GC이며 Throughput GC라 하기도 한다
멀티스레드로 Young 영역을 처리하고 싱글스레드로 Old 영역을 처리한다

배치 프로젝트와 같은 긴 pause가 허용되는 애플리케이션에 사용할 수 있다.

CMS

low pause 의 시초가 되는 GC다.

parellel GC 와 같이 young 영역을 병렬 처리하고 Old 영역 또한 병렬처리한다.
그러나 parellel 과 다른 점은 애플리케이션 백그라운드에서 진행 가능하다는 점이다.
stop the world 발생을 최소화하였다.

그러나 백그라운드로 실행되는 GC의 초기버전이라 메모리 파편화가 발생한다
만약 CPU 리소스가 부족해진다거나, 메모리 파편화가 너무 심해서 메모리 공간이 부족해지면 Serial GC 방식으로 동작한다.

G1

64비트 컴퓨터 최적화된 GC로 4GB 이상의 Heap size 에 적합한 GC다.
메모리 리전 분할 정책으로 리전내부 gc 영역 존재한다.
mark-sweep and compact 알고리즘으로 CMS의 메모리 단편화 이슈를 해결하였다.

compact 알고리즘의 의해 살아남은 객체들을 새로운 Region으로 대피시키는 데 이 과정에서 pause가 발생한다.

Shenandoah

low-pause GC 짧은 주기의 GC를 사용하여 최소한의 pause만 발생한다.

강력한 Concurrency 지원으로 heap 사이즈에 영향을 받지 않고 일정한 pause 시간을 보장한다.
아주 짧은 STW 시간으로 거의 대부분의 시간동안 애플리케이션 백그라운드에서 작동하므로, 빠른 응답을 요구하는 프로젝트에 적합하다.

OpenJdk 기반. jdk8부터 개발을 시작하여 jdk12에 release하였지만 oracle jdk는 지원하지 않는다.

G1을 기반으로 개발되었으며,G1의 compact 과정의 pause를 conrruent 하게 해결한 것이 가장 큰 특징이다.

참고: https://velog.io/@recordsbeat/Low-Pause-Shenandoah-GC

ZGC

이 역시 low-pause GC 며 heap 영역을 region으로 나누고 concurrent 하게 처리한다는 것 Shenandoah와 같은 개념이다. (10ms 이하의 pause를 목표로 한다.)

Oracle에서 만들었는데 Shenandoah와 라이벌 격인듯 하다.

jdk15에 release 되었다.


효율적인 GC

Serial

작은 프로젝트에 사용하면 된다.
싱글스레드 기반으로 heap size가 약 100MB 정도 되는 애플리케이션이면 적당하다.

Parallel

GC와의 공존을 버리고 최대치의 애플리케이션이 성능을 이끌어내야한다면 Parallel을 사용하는 것이 좋다.
혹은 Pause 소요 시간에 대해 크게 신경쓰지 않는 프로젝트여도 좋다.

CMS/G1

응답시간이 처리량보다 중요한 경우 적합하다.
API서버처럼 약 1초보다 짧은 pause 시간을 계속해 유지해야한다면 해당 GC를 사용 권장한다.

Shenandoah/ZGC

이 두 가지도 응답시간이 우선적일 때 사용하기 적합하다.
그러나 CMS/G1의 경우와 다른 점은 큰 heap size에서도 low-pause를 보장하기 때문에 heap size가 매우 크다면 고려해볼만하다.


GC 튜닝

GC를 사용하기 위해서는 몇몇 값을 설정해야 한다.

이 값들을 이리저리 설정하여 어플리케이션 성격에 맞게 GC를 최적화하는 것인데, 사실 GC 튜닝은 권장되지 않는다

Gc는 앞서 이야기 하였듯 마음대로 움직인다.

그렇기 때문에 GC 성능을 최적화하려면 상세한 동작원리부터 실질적인 벤치마킹까지 섬세하게 따져봐야 할 것들이 너무 많다

그래서 실상은 GC를 건드리는 것보다 어플리케이션 코드로써 메모리 최적화(공간복잡도를 낮춘다)를 권장한다.

설정

  • 힙(heap)
    • Xms JVM 시작 시 힙 영역 크기
    • Xmx 최대 힙 영역 크기
  • New 영역의 크기
    • XX:NewRatio New영역과 Old 영역의 비율
    • XX:NewSize New영역의 크기
    • XX:SurvivorRatio Eden 영역과 Survivor 영역의 비율

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


결론

  • GC 세부동작원리까지 컨트롤하기에는 너무 복잡하고 어려울 것 같다.
  • 지금 내가 개발하고 있는 어플리케이션에서 근본적으로 힙 영역에 메모리를 차지하는 객체들을 최적화를 먼저 해보자
  • 그리고 공부할 게 많다는 것을 느꼈다. 하하

0개의 댓글