Garbage Collection(GC)

아몬드봉봉·2023년 12월 27일
0

운영체제

목록 보기
3/3

Garbage Collection

메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다. C, C++ 등의 프로그래밍 언어에서는 수동으로 메모리를 관리해야 하지만 Java나 Kotlin을 이용해 개발을 하면 JVM의 가비지 컬렉터(GC)가 불필요한 메모리를 알아서 정리 해주기 때문에 Java 프로세스가 한정된 메모리를 효율적으로 사용할 수 있게 해주고, 개발자 입장에서 메모리 관리, 메모리 누수 문제를 관리하지 않아도 돼서 개발에만 집중할 수 있는 장점이 있다.

단점으로는 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없어서 제어하기 힘들고, 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생한다. 오버헤드가 자주 발생하게 되면 성능 하락의 문제가 되기도 한다.

Java에서도 System.gc()를 이용해 호출할 수 있지만,
오버헤드가 굉장히 크므로 가비지 컬렉션(GC)에게 메모리 해제를 맡기는 것이 좋다.

장점

메모리를 수동으로 관리하던 것에서 비롯된 에러를 예방할 수 있다.

  • 개발자의 실수로 인한 메모리 누수
  • 해제된 메모리를 또 해제하는 이중 해제
  • 해제된 메모리에 접근

단점

  • 가비지 컬렉션(GC)의 메모리 해제 타이밍을 개발자가 정확히 알기 어렵다.
  • 어떤한 메모리 영역이 해제의 대상이 될 지 검사하고, 실제로 해제하는 일이 모두 오버헤드이다.

Garbage Collection의 대상

가비지 컬렉션(GC)는 객체에 레퍼런스가 있다면 Reachable로 구분하고, 객체에 유효한 레퍼런스가 없다면 Unreachable로 구분 후 수거한다.

  • Reachable : 객체가 참조되고 있는 상태
  • Unreachable : 객체가 참조되고 있지 않은 상태


(이미지 출처 : https://velog.io/@gayeong39/Garbage-Collection%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98)

객체들은 실질적으로 Heap 영역에서 생성되고 Method Area이나 Stack Area에서는 Heap Area에 생성된 객체의 주소만 참조하는 형식으로 구성된다. 하지만 이렇게 생성된 Heap Area의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 Heap Area 객체의 메모리 주소를 가지고 있는 참조 변수가 삭제되는 현상이 발생하면, 위의 그림에서 빨간색 객체(Unreachable)와 같이 Heap 영역에서 어디든 참조하고 있지 않은 객체들이 발생한다. 이러한 객체들을 주기적으로 가비지 컬렉터가 제거해준다.

Garbage Collection의 알고리즘

Reference Counting

Garbage Detection에 초첨이 맞추어진 초기 알고리즘이다. 각 객체마다 Reference Count를 관리하여 Reference Count가 0이 되면 가비지 컬렉션(GC)을 수행한다.
이 방식은 각 객체마다 Reference Count를 변경해 주어야 하기 때문에 그에 대한 관리 비용이 크고, 참조를 많이 하고 있는 객체의 Reference Count가 0이 될 경우 연쇄적으로 가비지 컬렉션(GC)가 발생할 수 있는 문제가 있다. 또한 메모리 누수가 발생할 가능성도 크다.

Mark and Sweep

Reference Counting 알고리즘의 단점을 극복하기 위해 나온 알고리즘이다. 가비지 컬렉션(GC)는 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각 어떤 객체를 참고하는지 탐색한다. 그리고 사용되고 있는 메모리를 식별(Mark)하고 식별되지 않은 객체들을 메모리에서 제거(Sweep)한다.

  • Mark : 사용되는 메모리와 사용되지 않는 메모리를 식별
  • Sweep : Mark 단계에서 사용되지 않음으로 식별된 메모리를 제거

별도의 오버헤드가 없어서 성능이 비교적 좋지만, 가비지 컬렉션(GC)가 수행되는 도중 Mark 작업의 정확성과 Memory Corruption을 방지하기 위해 Heap의 사용이 제한되기 때문에 지연(Suspend) 현상이 발생한다. 그리고 객체들이 지워진 공간이 분열된(fragmentation) 것으로 남게 되어 메모리 할당이 불가능한 상태가 된다.

Mark and Compact

Mark and Sweep 알고리즘의 fragmentation 약점을 극복하기 위해 나온 알고리즘이다. Sweep이 사라진 것이 아니고 Compact 안에 포함되어 있다. Mark and Compact 알고리즘은 기존의 Sweep 과정까지는 동일 하지만 Compact 과정을 더 거치게 된다.

Sweep 후에 분산된 객체들을 Heap 영역의 시작 주소로 모아 메모리가 할당된 부분과 fragmentation한 부분을 압축해서 메모리 공간의 효율을 높일 수 있다. 하지만 Compact 작업 이후 할당된 사용되는 메모리들의 Reference를 업데이트 하는 작업이 필요하기 때문에 부가적인 오버헤드가 발생한다.

Heap 메모리 구조


(이미지 출처 : https://coding-factory.tistory.com/829)

JVM의 Heap 영역은 동적으로 레퍼런스 데이터가 저장되는 공간으로, 가비지 컬렉션(GC)에 대상이 되는 공간이다. Heap 영역은 처음 설계될 때 다음의 2가지를 전제(Weak Generational Hypothesis)로 설계 되었다.

  • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
  • 오래된 객체에서 새로운 객체로 참조는 아주 적게 존재한다.

즉, 객체는 대부분 일회성되며, 메모리에 오랫동안 남아있는 경우는 드물다. 그렇기 때문에 객체는 생존 기간에 따라 물리적인 Heap 영역을 나누게 되고 Young, Old 총 2가지 영역으로 설계되었다. 초기에는 Perm 영역도 존재했지만 Java 8부터 제거되었다.

Young Generation
  • 새롭게 생성된 객체가 할당(Allocation)되는 영역
  • 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
  • Young 영역에 대한 가비지 컬렉션(GC)을 Minor GC라고 부른다.

Young 영역은 더욱 효율적인 GC를 위해 3가지 영역으로 나눈다.

  • Eden
    • new를 통해 사로 생성된 객체가 위치
    • 정기적인 쓰레기 수집 후 살아남은 객체들은 Survivor 영역으로 보냄
  • Survivor 0 / Survivor 1
    • 최소 1번의 가비지 컬렉션(GC) 이상 살아남은 객체가 존재하는 영역
    • Suvivor 영역에는 특별한 규칙이 있는데 Suvivor 0 또는 1 둘 중 하나는 꼭 비어 있어야 한다.
Old Generation
  • Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
  • Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
  • Old 영역에 대한 가비지 컬렉션(GC)을 Major GC 또는 Full GC 라고 부른다.

Old 영역이 Young 영역보다 크게 할당되는 이유는 Young 영역의 수명이 짧은 객체들은 큰 공간을 필요로 하지 않으며 큰 객체들은 Young 영역이 아니라 바로 Old 영역에 할당되기 때문이다.

Garbage Collection 동작 과정


(이미지 출처 : https://wooody92.github.io/java/GC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC/)

Young 영역은 일반적인 Old 영역보다 크기가 작기 때문에 가비지 컬렉션(GC)가 보통 0.5초에서 1초 사이에 끝난다. 그렇기 때문에 Minor GC는 애플리케이션에 크게 영향을 주지 않지만 Old 영역의 Major GC는 일반적으로 Minor GC 보다 시간이 오래 걸리며, 10배 이상의 시간을 사용한다.

Minor GC

  1. 새로 생성된 객체가 Young 영역의 일부인 Eden에 age-bit 0으로 할당 된다. age-bit는 Minor GC에서 살아 남을 때마다 1씩 증가한다.
  2. 새로운 객체가 계속 생성되어 Eden 영역이 꽉차게 되면 Minor GC 가 실행된다.
  3. Mark 동작을 통해 Reachable 객체를 탐색한다.
  4. Reachable 객체는 Suvivor 영역으로 이동되며 age-bit 값이 1씩 증가되고, Unreachable 객체는 메모리가 해제된다.
  5. Eden 영역에 신규 객체들이 생성되어 다 쌓이게 되면 Young 영역(Eden + Survivor)에 있는 Reachable 객체들은 비어있는 Survivor 영역으로 이동하고 age-bit 값이 1 증가 된다.
  6. 위 과정을 반복한다.

Major GC or Full GC

  1. Minor GC가 발생하며 age-bit 값이 JVM에서 설정해놓은 임계치에 도달하게 되면 Old 영역으로 이동된다. 이 과정을 Promotion 이라고 한다.
  2. 1번의 동작이 반복되어 Old 영역에 할당된 메모리가 부족하게 되면 Major GC가 발생한다.

Minor GC / Major GC 차이점

GC 종류Minor GCMajor GC
대상Young GenerationOld Generation
실행 시점Eden 영역이 꽉 찬 경우Old 영역이 꽉 찬 경우
실행 속도빠르다느리다

Garbage Collection 실행 방식

JVM에서는 애플리케이션과 가비지 컬렉션(GC)을 병행하여 실행할 수 있는 여러 옵션들을 제공한다. 다양한 가비지 컬렉션(GC)가 어떠 방식으로 애플리케이션 실행과 병행되는지 살펴 보기 전에 Stop The World(STW) 개념을 알아야 한다. Stop The World란 가비지 컬렉션(GC)을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 말한다.

Serial GC

  • 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 가비지 컬렉션(GC)
  • 싱글 스레드이기 때문에 Stop The World 시간이 가장 길다.
  • Minor GC 에는 Mark and Sweep를 사용하고, Major GC에는 Mark and Compact를 사용한다.

    (이미지 출처 : https://steady-coding.tistory.com/584)
실행 명령어
  • 자바 프로그램을 실행할 때 -XX:+UseSerialGCGC 옵션을 지정하여 해당 가비지 컬렉션 알고리즘으로 힙 메모리를 관리하도록 실행할 수 있다.
java -XX:+UseSerialGC -jar Application.java

Parallel GC

  • Java 8의 디폴트 가비지 컬렉션(GC)
  • 멀티 코어 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용(Minor GC는 멀티 스레딩을 수행하지만, Major GC는 싱글 스레드로 수행)
  • Serial GC에 비해 Stop the World 시간 감소

    (이미지 출처 : https://steady-coding.tistory.com/584)
실행 명령어
  • GC 스레드는 기본적으로 CPU 개수만큼 할당된다.
  • 옵션을 통해 GC를 수행할 스레드의 갯수 등을 설정해줄 수 있다.
java -XX:+UseParallelGC -java Application.java
# -XX:ParallelGCThreads=N -> 사용할 스레드 갯수

Paraller Old GC

  • Parallel GC 를 개선한 버전
  • Young 영역 뿐만 아니라, Old 영역에서도 멀티 스레드로 가비지 컬렉션(GC) 수행
  • 새로운 가비지 컬렉션 청소 방식인 Mark Summary Compact 방식을 이용(Old 영역도 멀티 스레드로 처리)

    (이미지 출처 : https://steady-coding.tistory.com/584)
실행 명령어
java -XX:+UseParallelOldGC -jar Application.java

CMS GC

  • 애플리케이션의 스레드와 가비지 컬렉션(GC) 스레드가 동시에 실행되어 Stop The World 시간을 최대한 줄이기 위해 고안된 가비지 컬렉션(GC)이다.
  • 가비지 컬렉션(GC) 과정이 매우 복잡해진다.
  • 가비지 컬렉션(GC) 대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 가비지 컬렉션(GC) 대비 CPU 사용량이 높다.
  • 메모리 파편화 문제
  • Java 9 버전부터 Deprecated 되었고 Java 14 에서는 사용이 중지 되었다.

    (이미지 출처 : https://steady-coding.tistory.com/584)
실행 명령어
# Java 9 버전부터 Deprecated 되었고 Java 14 에서는 사용이 중지
java -XX:+UseConcMarkSweepGC -jar Application.java

G1 GC

  • Java 9 이상부터 G1 GC를 기본 GC 실행 방식으로 사용
  • 4GB 이상의 힘 메모리. Stop The World 시간이 0.5초 정도 필요한 상황에서 사용(Heap 메모리가 너무 작을 경우 미사용을 권장함)
  • 가비지로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로, 결국 가비지 컬렉션(GC) 빈도가 줄어드는 효과를 얻게 된다.
  • 기존 GC 알고리즘에서 Heap 영역에 물리적으로 고정된 Young/Old 영역으로 나누어 사용했지만, G1 GC는 Region이라는 개념을 새로 도입하여 필요에 따라 영역별 Region 개수를 튜닝함으로써 Stop the World를 최소화 할 수 있다.
  • Heap 영역을 Resion이라는 영역으로 체스같이 분할하여 상황에 따라 Eden, Survivor, Old 등 역할을 고정이 아닌 동적으로 부여한다.

    (이미지 출처 : https://steady-coding.tistory.com/584)
실행 명령어
java -XX:+UseG1GC -jar Application.java

출처

위키백과 가비지 컬렉션
https://mangkyu.tistory.com/118
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC
https://coding-factory.tistory.com/829
https://steady-coding.tistory.com/584
https://yoon1fe.tistory.com/152

profile
성장을 즐기는 백엔드 자바 개발자

0개의 댓글