TIL/CS

[CS-Java]JVM의 GC, Garbage Collector

Dev우키 2023. 7. 13. 01:53
반응형

Garbage Collector

Java를 다뤄봤다면 한 번쯤 들어봤을 법한 언어 GC 이것은 대체 무엇일까?

  • Heap 메모리에 위치한 unreachable한 객체를 삭제시켜주는 것
    • 더 이상 사용하지 않는 객체를 의미한다.
      • 객체가 null인 경우
      • 객체가 블럭 안에서 생성되고 블럭이 종료 되었을 경우
      • 부모 객체가 null이 되었을 경우, 자식 또는 포함된 객체들

C, C++ 등의 코드 레벨에서는 메모리를 직접 할당받고, 헤제해야 지만

//C++
char *s = malloc(sizeof(char)*10);
s = "Hello GC!";
printf("%s", s);

free(s) // <- 메모리 할당 해제
//Java
String s = "Hello GC!";
System.out.println(s);

//s.free(); //잘못됐다.

다만 Java에서는 JVM의 GC가 자동으로 삭제 시켜주며 코드 레벨에서 메모리 관리를 벗어나 편리함을 누릴 수 있게 되었습니다👏

GC의 장단점

장점

  1. Memory Leak이 발생하지 않는다.
  2. 휴먼 에러 발생 가능성을 낮춤
    • 메모리 할당 해제하는 코드를 작성하지 않는 경우
    • 해제한 메모리를 다시 해제하는 경우 등단점
  3. 성능저하
    • 어떤 메모리를 해재해야할 지 검사/삭제 하는 과정에서 추가적인 CPU, 메모리 등 PC 자원을 필요로 한다.
    • Unreachable 객체가 많을 경우 비용은 더욱 증가😂
  4. 메모리 해제 시점을 알 수 없다
    • JVM은 GC를 수행하기 위해 Application 실행을 멈추는데 이 시간을 개발자는 알 수 없다..
    • Application의 중지 시점을 알 수 없으므로 실시간성이 매우 강조될 경우 대처할 수 없다.

JVM의 GC 알고리즘

Mark & Sweep

test : GC Root에서 시작해 참조된 Object Mark

Mark

  • GC Root라는 특정 객체를 기준으로 탐색을 실시합니다.
    1. Stack 영역에서 모든 객체(변수)를 스캔하면서 각각 힙 영역의 어떤 Obejct를 참조 하고 있는지 찾아서 마킹을 수행
    2. Heap 영역에서 reachable한 객체는 마킹, 그렇지 않는 경우 Sweep 실행

test

: Sweep 후 Heap공간 모습

Sweep

  • Mark의 1번, 2번 과정을 통해 Marking 되지 않은 객체에 한해 메모리 할당 해제

Mark & Sweep의 특징

  1. 의도적으로 GC를 실행시켜야한다.
    • JVM나름의 기준이 있어 GC를 실행시키는 타이밍이 존재.
    • 해당 타이밍은 Heap영역을 참고하자..
  2. application 실행과 GC 실행이 병행된다.

의도적으로 GC를 실행시켜야한다??

우선 Heap을 살펴보자

Alt text

Heap은 크게 2가지 영역, New Generation과 Old Generation으로 구분 되어 있다.

  • New Generation : 새로운 객체가 저장되는 영역. 1차 GC인 Minor GC가 발생하는 영역
  • Old Generation : 오래 살아 남은 객체가 저장되는 영역, New 보다 영역이 크기 대문에 GC는 비교적 적게 발생한다
    • 해당 영역의 GC는 Major GC이다.

Application 실행과 GC 실행이 병행된다??

앞에선 GC가 실행될 때 Applicataion 실행이 중단된다 했는데 이제와서 무슨일일까?
사실! 같이 실행되는 것은 아닙니다. GC를 실행하기 위해 Applictaion을 멈추는게 맞아요! 다만 실행을 멈추는 것을 최대하 짧게해 병행되는 것 처럼 보이게 되죠!!

  • Application 실행을 멈춘다 == Stop the world
    그럼 자주 사용되는 방식에 대해 몇 가지만 소개를 더 해보겠습니다.

Parallel GC

  • Java 8 에서 사용되는 기본적인 GC 방식입니다
  • multi Core 환경에서 사용
  • 여러개의 Thread로 GC를 실행하기 때문에 stop the world 시간이 짧다는 장점이 있습니다.

G1 GC

  • Java 9 부터 기본적으로 쓰이는 GC 방식
  • heap을 일정 크기의 지역으로 나눠 New Generation, Old generation으로 구분합니다.
  • 이때 지역을 그 때 상황에 맞춰 개수를 알아서 튜닝을 해줍니다!!!
  • 이런 방식으러 stop the world를 최소화 할 수 있습니다.

=> 그래서 Java8과 Java11의 큰 차이점 중 GC 개선(Paralle -> G1)으로 인한 성능 향상이라고 합니다

왜 Heap을 2가지 영역으로 나눴을까?

Collection distribution

  • 위 이미지를 보면 새롭게 할당된 객체는 오랫동안 참조되지 않는게 다반사 입니다.
    • 전역 변수 선언하는 양보다 지역변수(블록단위) 선언이 많거나 등
    • 생각보다 빨리 garbage 상태가 됩니다.
  • 또 오래된 객체에서 새로 생긴 객체로 참조하는 경우가 거의 없습니다.

=> 따라서 Heap을 스캔하는 효율을 향상 시키기 위해 2개의 영역으로 나누게 됬습니다.
- 오래된 객체는 따로 분리
- 할당된지 얼마 되지 않은 것을 스캔해 GC를 수행하는 것이 효율적

어떻게 Old Genertaion까지 도달하는 걸까?

Java Heap

New Generation은 위 그림과 같이 Eden, survivor0, survivor1 3가지 영역으로 나누어집니다. 모든 객체는 Eden영역 에서 시작해 점차 survivor0 -> survivor1 으로 이동하게 됩니다.

  • 다음 영역으로 이동할 때 age-bit가 1씩 증가하게 됩니다.

이런 상황에서 Eden영역이 꽉 차게 되면 Minor GC가 실행됩니다.
Minor GC로 부터 살아남은 객체들은 위 그림 처럼 survivor0 영역으로 이동하게 됩니다.

이 과정이 반복되어 Minor GC에서 끝까지 살아 남는다면 Old Genertaion 영역으로 넘어가게 됩니다.

  • age-bit가 특정 숫자를 넘기게 되는 경우도 있습니다.

최종적으로 Old Generation도 꽉 차게 되면 Major GC를 통해 앞서 언급한 `Mark and Sweep`` 알고리즘이 수행됩니다.


출처

반응형