끄적끄적

[GC]Garbage Collector에 대한 정리 본문

Back-end/Java

[GC]Garbage Collector에 대한 정리

mashko 2021. 12. 8. 00:57
반응형

개발을 하다보면 필연적으로 많이 듣게 되는.. 또는 많이 겪게 되는 것 중 하나가 가비지 컬렉터일 것이다.


예를들면 jvm out of memory 에러같은?
그래서 오늘은 가비지컬렉터에 대해 정리를 해둘까한다.
먼저 가비지 컬렉터가 뭔지 알아보자.

쓰레기 수집가.. 그렇다 가비지란 원래는 정리되었어야하는 또는 정리되지 않은 메모리를 쓰레기라고 생각하면 될꺼같다.
컬렉터는 수집기.. 한마디로 가비지 컬렉터란 정리되어있지 않은 메모리를 수집하여 해제시키는 것이다.
본격적으로 알아보자.
일단 전공과목이나 수업에서 공부할때 C언어에 대해 배울땐 직접 메모리를 해제 시켜줬었던걸로 기억한다.

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

int main()
{
    int num1 = 20;    // int형 변수 선언
    int *numPtr1;     // int형 포인터 선언

    numPtr1 = &num1;  // num1의 메모리 주소를 구하여 numPtr에 할당

    int *numPtr2;     // int형 포인터 선언

    numPtr2 = malloc(sizeof(int));    // int의 크기 4바이트만큼 동적 메모리 할당

    printf("%p\n", numPtr1);    // 006BFA60: 변수 num1의 메모리 주소 출력
                                // 컴퓨터마다, 실행할 때마다 달라짐

    printf("%p\n", numPtr2);     // 009659F0: 새로 할당된 메모리의 주소 출력
                                // 컴퓨터마다, 실행할 때마다 달라짐

    free(numPtr2);    // 동적으로 할당한 메모리 해제

    return 0;
}

대충 이런식.. 하지만 자바나 자스에서는 가비지 컬렉터가 있어 메모리가 부족할때 이런 가비지들을 해제시켜 여유 공간을 확보한다.
그리고 이러한 불필요한 가비지가 쌓이는 증상을 메모리 누수현상이라고 부른다.
GC가 실행되는 과정을 Mark and Sweep이라고 부르는데, GC가 스택의 모든 변수 또는 Reacheable객체를 스캔하면서 각 각 어떤 객체를 참조하고 있는지 찾는 과정이 Mark라고 하고, 이후 Mark되어있지 않은 객체들을 힙에서 제거하는 과정을 Sweep이라고 생각하면 된다.
영어 그대로 사전을 검색해보면 Mark and Sweep은 표시 및 청소다.
가비지 컬렉터가 실행되기 위해 JVM이 애플리케이션 실행을 멈추는데 이 부분을 stop-the-world라고 부른다.
영어그대로.. GC가 실행되면 GC를 실행하는 쓰레드를 제외하고 나머지 쓰레드는 모두 작업을 멈춘다.
그리고 작업이 끝나면 중단했던 작업을 다시 시작한다.
현재는 실무에서 맡고 있는 담당 서비스에서 메모리 누수 이슈가 없어졌지만, 예전에는 이 메모리 누수 부분에 대한 원인을 찾고 해결하느라고 정말 많이 고생했다.(해결하지 않으면.. 지속적으로 알림이 오고 이슈화가 된다.)

가비지 컬렉터에 접근하다보면 필연적으로 마주치는 Minor GC와 Major GC(Full GC)라는 것에 대해 기본적으로 알고 있어야 한다.

JVM의 Heap메모리 영역에 대해 알아보면 Young, Old 크게 두영역으로 나뉜다는 것을 어렴풋이 기억이 날 것이다..
(이런 이론은.. 사실 또 문제가 잠잠해지면.. 또렷하게 기억안남..)
뭐.. perm영역은 힙 메모리 공간이 아니다. (클래스나 메소드들을 알기위해 메타데이터들이 담는 공간)

이런 영역별로 메모리가 가득차게 되면 GC가 발생하게 되는데 먼저 Young영역에서 일어나는 GC를 Minor GC라고 부르고, Old영역에 대한 GC를 Major GC라고 부른다.
Young영역은 새로운 객체에 대한 내용을 담고있고(Minor GC를 거치면서 Eden이든.. Servivor영역을 거쳐간다.), Old영역은 Minor GC를 여러번 겪고도 살아남는 객체들을 담고있다.
그래서 사실 Minor GC의 경우 수명이 짧고 많은 객체를 검사하지 않기 때문에 속도가 매우 빠른편이라 애플리케이션에 대한 영향이 크지않다.
하지만 Old영역의 경우 살아 남은 모든 객체를 검사하기 때문에 오랜시간이 걸린다.
그래서 Old영역의 Major GC의 횟수를 최소화 해야 한다. 최소화 방법 다양하다.
지속적으로 메모리를 갉아먹는 원인을 찾아 최적화를 해주거나 또는 한번에 요청받거나 응답을 주는 처리에 대한 부분을 적절한 사이즈로 나뉘어 주는 방법등이 있다.
경험적에선 엑셀데이터의 경우이다. 날짜 제한을 너무 길게 잡아 둔 결과 처리시간도 오래걸리며 한번에 내려야하는 양이 너무 방대하다보니 엑셀의 요청이 많아 졌을때 메모리를 굉장히 많이 잡아먹는경우가 생겼었다.
이 부분을 처리 날짜에 대한 제한을 두고 끊어서 처리하는 로직으로 변경하니 해소가 되었던 기억이 있다.
위와 같은 이슈를 다 해결했음에도 불구하고 Full GC가 많아진다면 힙사이즈를 조절해보는것도 좋다.
힙사이즈가 줄어들면 그만큼 GC가 처리량은 적고 처리하는 빈도수는 늘어난다. 또한, 힙사이즈가 늘어나면 처리량은 많아지고 처리하는 빈도수는 줄어든다.
서비스마다 최적의 설정을 하면 될것이다.

결론
GC의 최적화의 방법은 서비스마다 다르다.
개발자가 이리저리 바꿔가면서 테스트해보고 모니터링을 해가면서 찾는 방법이 맞다.
물론, 애초에 코드상 문제 될 만한 소지가 있는 부분은 미연에 방지 했다는 기준이다.

반응형
Comments