[Java] 가비지 컬렉션 , GarbageCollection

DevHwan·2022년 12월 14일
1

자바 개발자라면 누구나 한 번씩 들어봤을 내용이다. 나는 C++ 언어를 가장 먼저 배웠기 때문에 해당 내용을 먼저 배우고 언어를 배운것이 아니라 자연스럽게 자바로 넘어오면서 개발을 이어가기 시작했다. 애플리케이션의 규모가 커질수록, 개발 단계가 늘어갈수록 로우 레벨이 중요하다는 것을 깨닫고 한 번 깊게 알아가고 넘어가고자 한다.

참고 : 해당 GC는 JDK 8을 배경으로 작성되었다. + HopSpotVM

Generational Garbage Collection

가비지 컬렉션 과정이 왜 중요한 것이고, 애플리케이션의 속도에 영향을 끼칠까? 사실 로우 레벨일수록 프로그래머가 관여할 일은 적어진다. 시스템 같은 것들은 기본적으로 정상 작동하는 것이 원칙이기 때문이다. 사실 이 내용들을 알아가더라도 우리가 시스템을 조작할 일은 거의 없게 된다.

자세한 내용으로 들어가기 전에 Java 8에서의 GC에 대해 간략히 살펴보면 GC는 다음과 같은 일을 한다. 크게 3 가지 단계이다.

  • Young Generation으로 객체를 할당, 해당 영역에서 오래 살아남은 객체를 Old Generation으로 이동
  • Marking Phase ( 병렬 마킹 단계 )에서 Old Generation의 살아있는 객체를 찾는다. Concurrent Mark Sweep Collector ( JDK 9 이후로는 사용되지 않음 ), Garbage-First 알고리즘들을 사용
  • Copying ( 병렬 복사 ) 작업을 통해 사용 가능한 메모리를 복구. Parallel Collector와 Garbage-First 알고리즘들을 사용

참고 : 가용할 수 있는 쓰레드의 수는 하드웨어 및 운영체제, 그리고 할당하는 힙 사이즈의 크기에 따라 달라질 수 있다.

Stop-the world

가비지 컬렉션에서 중요한 용어이다. 자바 언어가 개발되면 개발될수록 성능(시간)이 향상 되었다는 스크립트를 여기저기서 많이 보았을 것이다. 여기서 가장 개선되는 점들은 바로 이 과정이 개선되었다는 점들이다. stop-the-world는 가비지 컬렉션이 실행되기 위해 JVM이 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈추는 것이다. 뒤에 여러 알고리즘들이 나오겠지만 어떤 GC 알고리즘을 사용하더라도 해당 과정은 명백히 발생한다.

Java 언어에서는 개발자가 프로그램 코드를 통해 메모리를 명시적으로 해제하지 않는다. 따라서 가비지 컬렉션 과정에서 사용하지 않는 객체를 지우는 작업을 실시한다.

해당 자료는 ORACLE Java 공식문서에 포함된 내용이다. 해당 자료는 다음과 같은 내용을 알린다.

  • 대부분의 객체가 할당되고 얼마 안되어 죽는다. → 사용되지 않는다.

이러한 내용을 문서에서는 weak generational hypothesis라고 표현한다.

이 가설로 하여금 크게 2가지의 물리적 영역으로 나눌 수 있다. 바로 Young generation & tenured generation이다.

Young generation & Tenured generation

  • Young generation은 이름에서도 알 수 있다. 새롭게 생성한 객체의 대부분이 여기에 생성된다. 대부분의 객체는 위의 자료처럼 생성되고 금방 사라지기 때문에 이 영역에서 사라진다. 해당 영역에서 객체가 사라질 때, Minor Collection이 발생한다.
  • Tenured generation은 Young generation에서 살아남은 객체가 복사 이동되는 곳이다. 오래 살아남은 객체가 사라질 때 Major Collection이 발생하고 Young Generation 영역의 객체보다 더 많은 객체와 관련될 가능성이 높기 때문에 훨씬 오랜 시간 지속된다.

Young

Young generation은 Eden과 2개의 Survivor영역으로 구성된다. 대부분의 객체는 초기에 Eden에 할당이된다. 두 개의 생존 영역 중 하나의 공간은 어떤 순간이던지 비어있고, 에덴의 살아있는 객체의 목적지 역할을 하게 된다. 빈 공간을 제외한 다른 생존 영역은 다음 공간으로 복사 컬렉션 동안 있는 공간이다.

HotSpotVM 에서는 더 빠른 메모리 할당을 위해서 두 가지 기술을 제공한다.

  • bump-the-pointer → Eden에서의 마지막 객체 추적
  • TLABs → 멀티스레드 환경에서 Lock을 고려한 각각의 스레드 Eden 공간 할당 ( LocalThread 개념을 생각하면 쉬울 듯 )

해당 내용은 ORACLE 공식문서에서는 찾을 수 없어 아래의 레퍼런스에 링크를 참고했다.

Tenured

해당 영역에서는 주요한 알고리즘이 많이 들어가기 때문에 별도의 목차를 두었다. Collection 에는 다양한 종류가 있는데 기본적으로 Document에서는 애플리케이션의 별도의 요구사항이 있지 않으면, 애플리케이션은 우선 실행하고 VM이 Collection을 선택하도록 권장하고 있다. 가장 첫 번째로 나오는 Collection이 굉장히(?) 좋지 못한 선택지 중 하나이지만 알아두어야 한다.

Serial Garbage Collection

이름에서 알 수 있듯이 직렬 방식의 Collection이다. 해당 Collection은 데스크톱의 CPU 코어가 하나만 있을 때 사용하는 방식이다. 요즘 컴퓨터에 싱글코어가 어디있어? 하겠지만 AWS 인스턴스를 프리티어로 이용하게되면 싱글코어이다. 해당 의문을 해결하기 위해 StackOverflow나 Document를 많이 살펴보았는 데, 해결하기 어려웠다. 따라서 Openai-gpt-3의 도움을 받았다.

싱글 코어라고 무조건 Serial Collection이 맞는 것은 아니지만 일반적으로 힙 크기가 작고 메모리가 적은 애플리케이션에 적합하다고 한다. 해당 Collection에서는 MarkSweep을 사용한다. Tenured Generation의 살아 있는 객체를 식별하고 힙의 앞 부분 부터 확인하여 살아있는 것을 남기는 방식이다. 따라서 힙의 사이즈에 따라 성능이 영향을 받는다.

특징

  • 싱글 스레드를 사용하여 통신 오버헤드가 없다.
  • 멀티 프로세서 하드웨어에서는 부적절하다.
  • 메모리가 적을 때, 적합한 방식이다.
  • -XX:+UseSerialGC 옵션으로 활성화할 수 있다.

Parallel Garbage Collection

Serial과 기본적인 알고리즘은 동일하다. 그러나 이름에서 알 수 있듯이 병렬로 처리가 가능하다. 병렬로 Major GC를 수행한다. 따라서 메모리가 충분하고 코어가 많을수록 유리하다. Parallel Collector는 Throughput Collector라고 부르기도 한다.

특징

  • Serial GC와 다르게 멀티 스레드를 사용한다.
  • 메모리가 충분하고, 코어가 많을수록 유리하다.
  • -XX:+UseParallelGC 옵션을 쓰면 기본값으로 Parallel Compaction을 사용한다. ( 기존 알고리즘과 다르게 약간 더 복잡한 단계를 지닌다. )

Concurrent Collectors

여기부터는 동시성 Garbage Collection을 지원한다. 동시성 GC는 메이저 Garbage Collection이 발생했을 때, stop-the-world를 줄이기 위해서 프로세스 자원을 사용하는 방식이다. 동시성 작업을 위해 오버헤드가 발생하는데 stop-the-world시간은 줄어들지만, 그만큼 애플리케이션에 가용하는 자원이 줄어들기 때문에 처리율이 다른 Garbage Collection에 비해 떨어진다. ( 하지만 자원이 넉넉하다면 해결되는 문제이다. )

G1 Garbage Collection

아래 이미지는 ORACLE DOCUMENT의 공식 이미지이다.

기존의 Garbage Collection방식과는 전혀 다르다. 각 영역에 객체를 할당하고 각각 Garbage Collection을 수행한다. 많은 양의 메모리가 있는 멀티 프로세서 환경에서 유리한 방식이다. 높은 처리율과 일시 정지 시간이 압도적으로 줄어드는 Garbage Collection방식이다.

특징

  • 빠르다.
  • 어떤 방식보다 자원이 많이, 좋아야 한다.
  • -XX:+UseG1GC 옵션으로 활성화된다.

Concurrent Mark Sweep Garbage Collection

줄여서 CMS Collector라고도 한다. 아래 이미지는 ORACLE Document에 있는 공식 이미지이다.

Serial 방식에서는 살아있는 객체들을 식별하는 동안 stop-the-world가 발생했다. 그러나 CMS 초기단계에서는 가장 가까운 살아 있는 객체만을 식별한다. 따라서 멈추는 시간이 이미지에서 보이듯 굉장히 짧아진다. 그 다음 Concurrent Mark 단계에서 멈춰있는 상태가 아닌 다른 스레드가 실행중인 상태에서 살아있는 객체들을 탐색해간다.

그 다음 Remark 단계에서 추가되거나 참조가 끊기는 객체를 확인하고 마지막으로 Concurrent Sweep 단계에서 사용하지 않는 객체를 정리하는 작업을 실행한다.

stop-the-world 작업 시간이 짧은 것이 특징이며, 애플리케이션 응답속도 개선에 도움을 줄 수 있다.

특징

  • 메모리와 CPU 사용량이 크다.
  • 애플리케이션 응답속도가 매우 빠르다. → stop 시간이 짧다.
  • -XX:+UseConcMarkSweepGC 옵션으로 활성화할 수 있다.
  • JDK 9부터는 지원하지 않는다.

Z Garbage Collection도 있지만 해당 부분은 JDK 11부터 도입된 내용이기 때문에 본 글에서는 다루지 않았다.


정리

사실 Document를 살펴 보더라도 가장 핵심은 명시적 GC를 비활성화 할 것. 즉 로우 레벨은 시스템에게 맞기고, 애플리케이션 제작에 힘 쓰라는 내용 같다. GC 로깅을 통해 stop 시간이나 힙 등을 살펴볼 수 있는데, 공간에 문제가 있거나 실행시간의 5%를 GC 시간이 넘어가는 경우 GC 튜닝이 필요할 수 도 있다. GC 옵션을 통해 튜닝을 진행할 수 있는 데 어떤 서비스에서 해당 옵션이 잘 작동한다고 해서 다른 서비스에서도 최적의 효과를 볼 수 있는 것은 아니다.

각 서비스에 적용되는 객체의 형태, 리소스의 형태에 따라 적합한 값을 찾으려고 노력해야 한다. 아직까지 GC를 최적화 할 정도의 애플리케이션을 운영, 개발해 본 경험은 없지만 프로젝트의 규모가 점점 커지고, 사용자가 많아질수록 고려해야 될 문제가 아닐까 싶다.

레퍼런스
Generations
Java GC 튜닝
NAVER D2

profile
달리기 시작한 치타

0개의 댓글