Garbage Collection

장윤성·2022년 8월 29일
0

GC?

쉽게 말하면 자동으로 메모리를 관리해주는 시스템이다. 기본적인 개념은 메모리에 객체를 스캔하고, 연결되어 있지 않은 객체를 인식하는 것이다. 이후에 연결 안되어있는거 없애던지, 연결된거만 따로 가져오던지 한다.

Reference Counting

어떤 한 동적 단위가 참조값을 가지고 이 단위 객체가 참조되면 참조값을 늘리고, 더이상 사용되지 않게 되면 참조값을 줄인다. 이때, 참조값이 0이 되면 더이상 유효한 단위 객체로 보지 않기 때문에 memory에서 제거해준다.
간단하고, 빠르다는 장점이 있지만, 참조마다 참조값을 검사해야하기 때문에,count==0 인 것이 하나도 없을 때까지 검사를 해야하기 때문에 overhead가 높아질뿐만 아니라 순환참조오류가 발생할 수도 있다.

그림에서 보이듯이 root에 참조되어 있지 않은 객체는 garbage로 인식하고 모두 지운다.

obj *p = null
p = obj1; // obj1->ref_count++
p = obj2; // obj1->ref_count--, obj2->ref_count++

Mark and Sweep

마찬가지로 기본적인 개념은 reachable한 객체를 모두 스캔하는 것이다. 그 다음으로는 root로 시작해서 traverse(dfs, bfs)를 통해서 reachable한 모든 객체를 표시하고 표시되지 않은 객체는 garbage라고 판단하고 지운다.

과정

여기 참조되어 있는 객체들이 있다.
일단 이 상태에서 traverse를 진행해서 방문되지 않는 객체를 지우는 작업을 진행한다.

회색처리 된 객체들이 방문하지 않은 객체이고 이들을 없애준다.

여기서 Thread C의 메모리를 free 한다고 하면, Thread C가 root인 객체들은 방문하지 않기 때문에 garbage가 된다.

그 다음으로 Thread B의 메모리를 마찬가지로 free 해보겠다.

마찬가지로 Thread B가 root인 객체들이 모두 garbage가 된다.

장점

root별로 traverse만 진행해주면 되기 때문에 reference counting의 단점인 overhead를 어느정도 줄일 수 있다.
순환참조오류가 발생하지 않기 때문에 모든 garbage를 free하는 것을 보장해준다.

단점

대신에 traverse를 진행한다는 것은 전체메모리를 검사하는 것과 같기 때문에, paging을 이용하는 os의 경우에는 성능이 떨어진다.
mark하는 단계에서 메모리 내용이 변경되면 안되기 때문에, 전체 시스템 실행이 정지된다.

Copy Collection

mark and sweep의 단점인 heap에 있는 모든 객체를 scan해야하는 것을 해결하기 위해 heap을 start space, end space로 나누고 연결된 객체를 mark하는 대신에 end space로 복사하는 과정을 통해서 mark이후에 sweeping 하는 과정을 없앨 수 있다.

간단하게 말하자면 연결되어 있는 객체를 copy하고난 뒤에 이전의 space를 없애준다고 생각하면 된다.

장점

sweep해주는 과정이 없기 때문에 mark and sweep보다 빠르다.
즉, 해제된 후 재사용 가능한 영역과 사용하고 있는 영역에 대한 추가처리를 해줄 필요가 없다.

단점

데이터가 복사되어야하고, 포인터가 업데이트 되어야하기 때문에 느릴수 있다.
여전히 mark and sweep의 단점인 시스템 실행 정지("stop the world")를 가지고 있다.

Generational Collection

mark and sweep도 그렇고 copy collection도 그렇고, 여전히 느리다는 단점을 가지고 있다. 그래서 객체들의 생성 패턴에 맞게 garbage collecting을 하면 어떨까에서 출발해서 살펴보니 program에서 새로 할당된 영역일수록 금방 free되는 경향이 나오는 것을 확인 할 수 있었다.

그렇다면, 영역을 여러개로 나누어서 따로따로 garbage collecting 해주면 속도가 훨씬 더 빨라질 것이다.

영역을 eden, survivor1, survivor2, tenured 4개로 나누어서 multiple layer copy collection을 진행하는 것이다. eden이 꽉차면 copy collection을 진행해서 연결되어 있는 객체를 survivor1으로 복사, survivor1이 꽉차면 마찬가지로 survivor2로 복사한다. 이렇게 되면, 전체 객체를 검사하는 것이 아니기 때문에 stop the world도 해결할 수 있고, 속도 측면에서 향상시킬 수 있다.

malloc()/free() vs GC

malloc/free

장점

  • 일반적으로 GC보다 빠르다.
  • GC와 다르게 stop the world가 없다.
  • memory 측면에서 훨씬더 효율적이다.

단점

  • 프로그래머에게 좀 더 복잡하다. (free를 해줘야하기 때문)
  • dangling pointers, double-free와 같은 메모리 버그가 발생할 수 있다.
    이 버그들은 나중에 security vulnerabilities를 야기할 수 있다.

GC

장점

  • 프로그래머들이 훨씬 간편하게 메모리를 관리 할 수 있게 한다.

단점

  • malloc보다 느리다.
  • 메모리 효율성이 떨어진다.
  • GC를 언제 해줄지에 따라서 성능차이가 발생한다.

Execution Time vs Memory

앞에서 GC를 얼마나 자주해줘야 할지에 따라서 성능차이가 발생한다고 말했는데, 쉽게 설명하자면, 너무 자주 GC를 하면 프로그램이 stop the world로 인해 당연히 느려질 것이고,
GC를 너무 안해주면 garbage가 많아져 프로그램이 무거워지기 때문에 느려질 것이다.
그래서 보통은 max heap을 어느정도 정해놓고 거기에 넘어가면 GC를 해주는 식으로 진행을 한다. 그런데 max heap은 어느 정도로 정해주는게 좋을까?

보통 프로그램 점유율의 40~70퍼 사이를 유지하도록 max heap을 설정해서 GC를 관리한다. 왜냐하면, 70% 초과는 너무 빈번하게 GC를 하기 때문에 일시정지로 인한 성능이 저하되고, 40% 미만은 너무 드물게 GC를 하기 때문에, 성능이 저하되기 때문이다.

profile
소개를 어떻게 한줄로 해요..

0개의 댓글