엘리
프로그램을 개발 하다 보면 유효하지 않은 메모리인 가비지(Garbage)가 발생하게 됩니다. 가비지 콜렉션(Garbage Collection)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역, 즉 가비지를 해제하는 기능을 말합니다.
동적으로 할당했던 메모리 영역: 런타임에 사용되는 Heap 메모리
필요없게 된 영역: 어떤 변수도 가리키지 않는 메모리
C, C++ 등과 같은 오래된 언어에서는 동적 메모리의 할당부터 해제까지 개발자가 일일이 메모리를 관리해줘야했습니다. 이렇게 개발자가 메모리를 관리하다보면 실수하는 부분들이 생겨납니다.
- 메모리 누수: 더이상 필요하지 않은 메모리가 해제되지 않고 남게되는 문제를 말합니다. 메모리 누수가 반복되면 메모리 고갈로 프로그램이 중단될 수 있습니다.
- 유효하지 않은 포인터 접근: 이미 해제된 메모리에 접근하는 문제를 말합니다. 만약 이 포인터가 해제되고 새로운 값이 할당되었다면, 잘못된 값을 읽어오게 됩니다.
- 이중 해제: 이미 해제된 메모리를 또 다시 해제하는 문제를 말합니다.
또한, 이러한 메모리 관련 문제는 발생 시점이 불분명하고, 문제 상황의 제현이 불가능한 경우가 많아 디버깅의 난이도가 높습니다.
이러한 문제를 해결하기 위해 GC가 만들어졌으며, GC로 인해 개발자의 실수를 줄이고, 관리 포인트를 줄여 안정적으로 프로그램을 개발할 수 있게 할 수 있습니다. 자바, 자바스크립트, C#과 같은 많은 현대 언어들은 처음부터 GC를 염두에 두고 설계되었다고 합니다.
Managed Language: 자동으로 메모리를 관리하는 언어
어떠한 방식의 GC를 사용하든, GC를 실행하기 위해서는 애플리케이션의 실행을 중단하는 stop-the-world
과정을 거쳐야합니다. stop-the-world
가 발생하면 GC를 실행하는 쓰레드를 제외한 다른 모든 쓰레드들을 중단하고, GC 작업을 완료한 후 다른 쓰레드들을 다시 이어서 실행합니다. 따라서 이에 따른 성능 저하가 발생하게 됩니다. stop-the-world
의 시간을 줄이는 것이 GC 튜닝의 관건이라고 합니다.
특히 개발자가 GC를 직접 호출할 수 있기는 하지만 (Java에서는
System.jc()
메서드로 호출할 수 있음), 이러한 행동은 성능에 매우 큰 영향을 줄 수 있습니다.
GC를 사용하더라도 메모리 누수는 여전히 발생할 수 있습니다. GC는 더 이상 접근이 불가능한 객체만을 회수하기 때문입니다. 만약 앞으로 사용하지 않을 객체지만, 개발자의 실수로 해당 객체에 접근할 수 있는 경로를 남겨둔다면 GC는 해당 객체가 사용하는 메모리를 해제하지 않게됩니다.
따라서, GC의 동작 방식을 알고, 이를 신경 써서 프로그램을 개발한다면 성능을 개선할 수 있습니다. (아는 것이 힘이다👍)
GC의 초기 알고리즘으로 구현이 간단합니다. 어느 한 메모리가 다른 메모리를 얼마나 많이 참조하는지 횟수를 세서 접근 가능한지 불가능 한지를 판단합니다. 참조 횟수가 0이 되면 GC 대상이 됩니다. PHP, Swift 등의 언어에서 채택하고 있습니다.
가장 많이 사용되는 GC 알고리즘으로, 프로그램 실행 중 특정 타이밍에 현재 할당된 모든 메모리를 조사하여 Root에서 부터 해당 메모리에 접근 가능한지 불가능한지 분류한 뒤, 접근이 불가능한 메모리를 가비지(Garbage)로 간주하고 해제시키는 방식입니다. Java, Javascript 등의 언어에서 채택하고 있습니다.
- Reachable: Root로부터 연결된 상태
- Unreqchable: Root로부터 연결이 끊어진 상태
- Mark: Root로부터 모든 객체를 스캔하면서 연결되어 있는지 표시하는 과정
- Sweep: Unreachable한 객체를 Heap에서 제거하는 과정
이렇게 메모리를 해제하게 되면 메모리 파편화(Fragmentation) 문제가 생기게 됩니다. 이때 살아남은 객체들을 연속된 메모리 공간에 차곡차곡 적재하는 과정을 추가로 할 수 있는데, 이 과정을 Compact라고 합니다.
Java8 JVM의 GC의 동작 방식을 기준으로 합니다. (어떤 종류의 JVM인지 모름)
JVM의 GC는 기본적으로 Mark and Sweep 방식을 사용합니다. 이때 GC를 언제 실행시킬지를 결정해야하며, 이는 Heap 영역의 구조에 영향을 받습니다.
JVM의 Heap 메모리는 처음 설계될 때 다음의 두가지 가설을 전제로 설계되었다고 합니다.
- 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
즉, 객체는 대부분 일회성이며 메모리에 오랫동안 남아있는 경우는 드물다는 것입니다.
이 가설의 장점을 최대한 살리기 위해 HotSpot JVM에서는 객체의 생존 기간에 따라 Heap 메모리를 Young Generation, Old Generation의 두가지 영역으로 구분하였습니다.(초기에는 Perm 영역이 존재했지만 Java8부터 제거되었다고 합니다.) 각각의 영역이 꽉 찰 때마다 JVM을 GC를 실행시킵니다. 이때 각 객체의 Header에 Age bit를 두고 GC 과정에서 살아남을 때마다 1씩 증가시킵니다.
- Young 영역에 대한 GC를 Minor GC라고 부릅니다.
- 세부적으로 Eden, Servival 0, Servival 1의 세 영역으로 나뉩니다.
- Eden: 새롭게 생성된 객체가 할당됩니다.
- Servival 0, 1: Eden에서 Minor GC 후 살아남은 객체가 할당됩니다. 둘 중 하나의 영역은 비어있어야 한다는 규칙을 가집니다.
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라집니다.
- Old 영역에 대한 GC를 Major GC 또는 Full GC라고 부른다.
- Young 영역에서 계속해서 살아남아 Age bit 값이 일정 수치가 되면 객체를 Old 영역으로 이동시키게 되는 데 이 과정을 Promotion이라고 합니다.
Young 영역은 일반적으로 Old 영역보다 크기가 작기 때문에 GC가 보통 0.5~1초 사이에 끝난다. 하지만 Old 영역은 Young 영역보다 크며, Young 영역을 참조할 수도 있다. 그렇기 때문에 Major GC는 일반적으로 Minor GC보다 오래 걸리며, 10배 이상의 시간을 사용한다고 합니다.
GC를 실행하기 위해서는 stop-the-world
, 즉 애플리케이션의 실행을 중단하는 과정이 필요한데 이 과정은 성능면에서 큰 이슈를 불러일으키는 부분입니다. 특히 Heap의 사이즈가 커지면서 이러한 문제가 더 두드러지게 되었고, 다양한 GC들이 개발되고 있다고 합니다.
- Serial GC: 가장 단순하고 기본적인 GC로, GC를 싱글 쓰레드로 실행하기 때문에 stop-the-world의 시간이 가장 깁니다. 싱글 쓰레드 환경 및 Heap이 매우 작을 경우 사용합니다.
- Parallel GC: GC를 멀티 쓰레드로 실행합니다. Java8의 default GC로, 멀티 코어 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용합니다.
- CMS(Concurrent Mark Sweep) GC: 가비지 수집 작업을 애플리케이션과 동시에 실행해
stop-the-world
시간을 최소화화 시켰습니다. 하지만 메모리와 CPU를 많이 사용하고, Compact 과정을 지원하지 않는다는 단점을 가지고 있어 G1 GC가 등장함에 따라 deprecated 되었습니다. - G1(Garbage First) GC: Heap을 일정 크기의 Region으로 나누어 이를 Young Generation과 Old Generation에 할당해줍니다. Java9 이상에서 default GC로 사용되고 있습니다.
제가 생각하는 바를 말씀드리면, Young Generation을 하나의 영역으로 사용하는 것 보다 3가지 영역으로 분리해서 사용하는 것이 GC가 더 자주 발생할 수 있게 하기 때문이라고 생각했습니다. (아직 이에 대한 자료조사를 한 것이 아니라 추후 변경하겠습니다.)
- JVM의 Memory 할당 방식 (Stack vs. Heap)
- 테코톡: 조엘의 GC
- NAVER D2: Java Garbage Collection
- https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)
- https://mangkyu.tistory.com/118
- https://seongeun-it.tistory.com/134?category=489176
- https://siahn95.tistory.com/entry/Garbage-Collection-1-%EA%B0%9C%EB%85%90-%EC%9C%A0%EB%9E%98-%ED%95%9C%EA%B3%84-GC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0?category=866017