2abe8168efc23f8650bbd58b3689746c4c71


GC(Garbage Collection)란 무엇인가?


흔히 메모리 관리 방식은 2가지로 나뉜다


Manual 과 GC이다.


수동 메모리 관리(Manual Memory Management) 와 자동 메모리 관리(Garbage Collection,GC)이다.


수동 메모리 관리는?


개발자가 직접 메모리 할당과 해제를 관리하는 방식이다.


대표적인 언어로 C,C++ malloc/free,new/delete 등등 개발자가 직접 메모리를 할당 해제한다.


장점으로는 정밀한 제어를 통한 메모리 할당 해제 시점을 프로그래머가 완전히 통제 가능하며


예측 가능한 성능이기때문에 GC의 불필요한 오버헤드가 존재하지 않는다.


또한 필요 없는 메모리를 즉시 해제해 리소스를 절약할 수 있다.


다만, 단점은 *메모리 누수(Memory Leak)가 존재하며, *댕글링 포인터(Dangling Pointer)가 존재하며 개발이 복잡해진다는 것이다.


*메모리 누수: 할당된 메모리를 해제 않아서 프로그램이 종료될때까지 누적되는 현상, 시스템 메모리 고갈로 크래시를 발생시킬 수 있다.

**댕글링 포인터:이미 해제된 메모리를 참조할 경우 예측 불가능한 동작이 발생함


최근에는 Rust는 수동 메모리의 관리의 대안으로 소유권 시스템(OwnerShip System)을 통해 메모리를 안전하게 관리하는데, 이는 완전한 수동 메모리 관리라기보다는

수동 메모리 관리의 새로운 대안이라고 여겨진다.


GC는 프로그래밍 언어에서 더 이상 사용되지 않는 메모리를 자동 해체하는 기능인데, 이제 이 글에서 설명할 것이다.


3db2d377abc236a14e81d2b628f17765cdcdb0

자유 저장소 목록 (Free-Storage List)

어느 시점에서든지, 리스트 구조를 저장하기 위해 예약된 메모리의 일부만이 실제로 S-표현식(S-expressions)을 저장하는 데 사용됩니다. 

나머지 레지스터들(우리 시스템에서는 초기에 약 15,000개 정도)은 **자유 저장소 목록(free-storage list)**이라는 단일 목록으로 구성됩니다. 

프로그램 내의 특정 레지스터인 FREE는 이 목록의 첫 번째 레지스터 위치를 포함합니다.

 추가적인 리스트 구조를 구성하기 위해 단어(word)가 필요할 때, 자유 저장소 목록의 첫 번째 단어가 사용되고, 레지스터 FREE의 값은 자유 저장소 목록의 두 번째 단어 위치로 변경됩니다. 

사용자가 레지스터를 자유 저장소 목록으로 반환하는 것을 프로그래밍할 필요는 없습니다.

-Recursive Functions of Symbolic Expressions Their Computation by Machine, Part I John McCarthy


GC의 시작은 1960년 
Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I 에서 리스트 구조와 메모리 사용에 대해 설명하는 파트에서 시작한다.

맥카시는 free Storage List의 개념을 소개하는데, 이는 후에 GC가 되는 가비지 컬렉션의 기초가 되는 아이디어이다.

이 개념이 왜 필요 했을까?

2abe8268efc23f8650bbd58b36807d6f6b2e42


(존 매카시)
LISP(LISt Processor)는 포트란과 같은 High Level 언어였는데, 기호처리(Symbolic Processing), 재귀호출(Recursion)을 지원하는 언어였다.

하지만 당시 컴퓨터 기술로는 LISP의 메모리 관리가 매우 복잡하고 어려운 문제였다.

LISP는 프로그래밍이 샐행되면 끊임없이 객체를 생성했는데, 더 이상 사용되지 않는 객체(Garbage)가 메모리 쌓이면 메모리 누수가 심각했는데, 이걸 프로그래머가 모든 메모리 할당과 해제를 완벽히 추적하는게 어렵다는 생각하에

자동 메모리 관리 기술을 만든 것이다.


자유 저장소 목록(Free-Storage List)은 초기 LISP 시스템에서 자동 메모리 관리를 구현하는 기초적인 개념이었다.


당시 LISP는 동적으로 리스트 구조를 생성하는 방식이었고, 프로그래머가 명시적으로 메모리를 해제하는 것이 어려웠다

.

이를 해결하기 위해, 사용되지 않는 메모리 블록을 추적하는 Free-Storage List 개념이 자동 메모리 관리 개념으로 발전했다.


특정 레지스터(FREE)에 가용 메모리 리스트의 시작 위치를 저장하고, 필요할 때 가져오고, 더 이상 사용되지 않는 경우 다시 반환하는 방식이었다.


이 아이디어는 이후 Mark-and-Sweep 방식의 GC의 기반이 되었다.


(GC의 구현방식 및 아이디어 상세설명은 다음섹션에서 이야기하겠다)


2abe8368f5dc3f8650bbd58b368277655260bd

컴퓨터에 리스트를 저장하기 위한 뉴얼-쇼-사이먼(Newell-Shaw-Simon) 방식의 중요한 특징은 

동일 데이터가 여러 번 발생하더라도 컴퓨터 내 한 곳에만 저장될 수 있다는 점입니다. 즉, 리스트들이 "중첩(overlapped)"될 수 있습니다. 

그러나 이러한 중첩은 이후 삭제(erasure) 과정에서 문제를 일으킵니다. 더 이상 필요 없는 리스트가 주어졌을 때, 

다른 리스트와 중첩되지 않은 부분만을 선택적으로 삭제해야 합니다. LISP에서는 매카시(McCarthy)가 이 문제에 대해 우아하지만 비효율적인 해결책을 제시했습니다. 

본 논문은 효율적인 삭제를 가능하게 하는 일반적인 방법을 설명합니다.  -"A Method for Overlapping and Erasure of Lists" (1963) George E. Collins


존 맥카시의 논문이 mark-and-Sweep 방식의 기반이 되었지만, GC실행시 프로그램이 멈추는 Stop - the - World 현상이 발생했다.


이는 실시간 GC를 구현하기 어렵게 만들었고, 콜린스는 Reference Counting(참조 카운팅) 개념을 GC방식으로 제안


각 객체에 참조 횟수(Reference Count)를 저장하여 참조가 0이 되면 즉시 메모리를 회수 하는 방식을 제안한다.


Collins(1960)는 Reference Counting GC를 제안하여 즉시 메모리 회수를 가능하게 했지만, 순환 참조 문제로 인해 완벽한 해결책은 아니었다.

Python, Swift 등 현대 언어에서도 여전히 사용되지만, 보완 기법이 추가되었다


(여담이지만 '우아하지만 비효율적'이다라니 엣날부터 생각하지만 전반적으로 프로그래머의 직업적 특징인지 모르겠으나

프로그래머들은 주로 상대방을 '우아하게' 비꼬는 재주가 있다. 아무래도 실체가 없는 추상적 개념에 대한 이야기를 하니까

실체적 폭력에 대한 존재가 옅어져서 그런게 아닐까 싶기도하다. 한국에서는 이러면 보통 물리적 실체인 주먹으로 응징을 당할텐데 말이지.)



2abe8568f5dc3f8650bbd58b3680706572e5bb



2abe8468f5dc3f8650bbd58b36827d6d56f582


본 논문에서는 매우 큰 가상 메모리 환경에서 동작하는 리스트 처리 시스템을 위한 가비지 컬렉션 알고리즘을 제안합니다. 

이 알고리즘의 주요 목적은 "여유 메모리 확보"보다는 **활성 메모리의 압축(compaction)**에 있습니다.

가상 메모리 시스템에서는 여유 메모리가 실제로 고갈되지 않기 때문에, 가비지 컬렉션 실행 시점을 결정하기가 쉽지 않습니다. 

따라서 본 논문에서는 가비지 컬렉션 트리거 조건에 대한 다양한 기준을 논의합니다. -"A LISP Garbage Collector for Virtual-Memory Computer Systems" (Fenichel & Yochelson, 1969)


(가상메모리(Virtual Memory):물리적 메모리 크기를 넘어서는 대용량 메모리를 논리적으로 관리하는 기술)


이 논문은 Minsky의 복사방식 GC(Copying GC)를 개선하여, 가상 메모리(Virtual Memory)에서 효율적으로 동작하는 방법을 기술한 논문이다.


본 논문은 Misnky의 가비지 컬렉터, 깊이 우선 탐색 (DFS, depth-first-serach)을 이용해 도달가능한 데이터를 보조 저장소에 복사하고, 이를 새로운 연속된 주소에 할당한 후 다시 메모리로 로드 하는 방식을 실제적으로 구현한 논문인데, 완성도가 떨어진다는 이야기가 있지만

(아래에서 이후에 설명할 것이기때문에 굳이 이해하지 않아도 된다)

이 논문은 Minsky의 방법을 기반으로 "Copying GC"를 현대적인 가상메모리 환경에 적용한 첫 논문이고, 이 논문이 없었다면

자바와 C#의 Generational GC 개념이 등장하지 못했을 것이다.



2abe8668f5dc3f8650bbd58b3680736a68d9bb


"간단한 비재귀적(nonrecursive) 리스트 구조 압축 기법 또는 가비지 컬렉터가 제시됩니다. 

이 알고리즘은 컴팩트(compact)한 구조와 LISP 스타일의 리스트 구조 모두에 적용 가능합니다. 

재귀(recursion)를 사용하지 않고, 복사된 리스트를 추적하기 위해 부분 구조(partial structure)를 점진적으로 활용함으로써 재귀 의존성을 제거했습니다."- (C. J. Cheney, 1970)


이후 깊이 우선 탐색(DFS) 에서 너비 우선 탐색(BFS,Breadth-Frist Search)를 사용한 '비재귀(Nonrecursive)' GC를 제안하는데, 이는 스택 없이도 GC가 가능하도록 설계된다


이 연구의 의의는 오늘날 Jav,C#,Python의 Copying GC 방식에 큰 영향을 주고 BFS, 기반이라 스택 오버플로우 문제를 방지하고, 캐시친화적 구조를 가진다.



BFS가 DFS랑 다르게 비재귀적인 방식으로 구현 된 이유


우선 독자 제언들 대부분 알겠지만 스택은 LIFO(Last in, First Out, 후입선출) 구조를 따르는 구조이다. 가장 마지막에 추가된 요소가 가장 먼저 제거된다.


큐는 FIFO(First in, First Out, 선입선출) 구조를 따르는 자료구조다. 즉 가장 먼저 추가된 요소가 가장 먼저 제거된다.


DFS는 한쪽 방향으로 끝까지 탐색한 후, 돌아오는 방식(백트래킹)을 사용하는데, 이를 위해 스택(Statc)이나 재귀함수(Recursive Call)을 사용해야한다.


DFS의 Stack 기반 원리는 다음과 같다.

1. 현재 노드를 방문 스택(or재귀 호출)으로 저장

2. 다음 방문할 노드가 있으면 스택 호출(or 재귀호출) 하여 계속 진행

3. 더 이상 진행할 곳이 없으면 백트래킹(되돌아가기) 실행


BFS는 Queue 기반이 원리

1. 탐색을 시작할 노드를 큐에 추가(Enqueue)

2. 큐에서 하나씩 꺼내(Dequeue) 방문 처리 후, 인접 노드들을 다시 큐에 추가

3. 모든 노드를 탐색할까지 반복.


큐를 사용하기때문에 별도의 재귀 호출이 존재하지 않는 것이다.


동굴 탐험을 예시로 들어보자


DFS는

동굴을 탐험할 때, 한 길을 따라 끝까지 들어간 후에, 길이 막히면 되돌아가서 새로운 길을 탐색하는 방식이다.

길이 막혔을 때 되돌아갈 경로를 기억(저장)해야 돌아갈 수 있지 않겠는가?


BFS는


동굴 입구에 팀을 배치하여 모든 갈림길을 동시에 탐색하고, 1층의 모든 길을 먼저 확인한 후, 다음 층으로 내려간다.

적절한 팀 배치를 위해서는 먼저 도착한 갈림길부터 인원을 우선 배치해야 하지 않겠는가?


자 그럼 현대적 연표를 요약해보자


연도연구자GC 알고리즘개선된 점문제점
1960John McCarthyMark-and-Sweep최초의 GC 개념 도입Stop-the-World, 단편화 문제
1963Marvin L. MinskyCopying GC (DFS 기반)메모리 단편화 해결, 캐시 최적화순환 참조 문제, 디스크 사용
1969Fenichel & YochelsonCopying GC (Virtual Memory 적용)두 개의 반공간(Semispaces) 사용메모리가 부족하면 프로그램이 느려짐
1970C. J. CheneyNonrecursive Copying GC (BFS 기반)스택 사용 없이 GC 가능BFS 기반이라 메모리 재배치 최적화가 덜 됨



이 GC 알고리즘 발전의 의미는 무엇일까? Stop-the-World 문제를 줄이고, 실시간 (Concurrent)GC로 발전하는 과정을 우리는 이제 확인해보았다.


그럼 이제 가비지 컬렉터가 어떤 알고리즘이 있고, 그 알고리즘을 요약해보자


들어가기 전 잠깐 간단 지식을 몇개 적겠다

-보통 우리는 힙 메모리와 스택 메모리라는 2가지 메모리 관리 풀로 나눈다.

-Stack 메모리는 주로 작고(지속시간이) 짧은 데이터를 저장하는데 사용한다(주로 값 형식 저장)

-Heap 메모리는 더 크고 오래 지속되는 데이터를 저장하는데 주로 사용된다.(주로 참조 형식 저장)


GC 알고리즘은 크게 2가지 계열로 나뉜다


1. Tracing GC(추적 기반 GC)



-흔히 가장 많이 사용되는 GC이다. 

가비지 컬렉션은 특정 객체가 Garbage인지 아닌지 판단하기 위해서 도달성(Reachability)으로 판단하는데

Root(루트,전역 변수,스택 변수 등) 에서 시작하여 Reachable(도달 가능한 객체)를 찾고, 도달하지 못하는 객체를 해체하는 방식이다.

객체에 유효한 참조가 없을 경우 unreachable로 구분하고 수거한다.

자주 쓰이는 알고리즘은 크게 3가지가 있다.

(첨부 코드는 필자가 공부해서, 핵심 개념만 구현한 의사 코드이므로 실제 동작을 보장하진 않는다.)

1-1. Mark-and-Sweep(마크 스윕)

2abe8768f5dc3f8650bbd58b3688776f0c94

(코드내에서 C++의 RAII을 따라 shared_ptr을 사용할 경우 실제로 GC 동작 관측이 어렵기때문에 원시 포인터를 사용해야한다)


1.Mark 단계->사용중인 객체를 식별

2.Sweep 단계->도달 가능한 객체 삭제

3. 메모리 단편화(Fragmentation)문제 발생

간단히 말해서, Root Space로부터 연결된 객체를 찾아내고, 연결되지 않은 객체는 Heap에서 제거한다라고 요약할 수 있다.

구현이 비교적 간단하고, 메모리 낭비 없이 필요한 객체만 유지 가능하다

단점으로는 Stop-the-World가 발생하고, 메모리 단편화가 발생한다.

*메모리 단편화: 메모리 단위가 힙에서 할당될 때 그 크기는 저장된 변수의 크기에 따라 달라지는데, 힙에서 메모리를 회수할때 힙 메모리가 조각난 셀로 분할됨
- **내부 단편화**: 할당된 메모리 블록 내부의 사용되지 않는 공간 (예: 4KB 할당 중 1KB만 사용).  
- **외부 단편화**: 사용 가능한 메모리가 조각나서 큰 블록을 할당하지 못하는 현상.  


2abe8868e2db3e8650bbd58b3680746d5051bc

(이미지 출처 : https://deepu.tech/memory-management-in-v8/)


1-2. Mark-Compact(마크 컴팩트)

Mark 단계 이후 살아남은 객체들을 메모리의 한쪽으로 이동하여 정리(Compaction)하여 단편화 문제를 해결한다.

단점은 이렇게 Compaction하는 과정에서 이동에 대한 오버헤드가 발생하고 GC 시간이 오래걸린다.

1-3. Copying GC(복사 기반 GC)

2abe8968e2db3e8650bbd58b3687736473


2abe8176abd531a04e81d2b628f172698095



2abe8177abc236a14e81d2b628f1756fadb64f66


메모리를 2개의 반 공간(Semi-Space)으로 나눈다. 각 스페이스는 From-Space &To-Space 로 명해지는데

Reachable 객체만 새로운 공간(To-Space)로 복사 후 기존 공간을 해체한다.

장점으론 메모리 단편화가 없고, 할당속도가 빠르지만 메모리 사용이 비효율적이라는 단점이 있다(전체 공간의 50%만 사용하기때문)


2. Reference Counting(참조 횟수)

2abe8174abc236a14e81d2b628f1716adca245

(전역 참조 테이블 unordered_map을 사용하여, 객체 생성시 참조 카운트를 1로 초기화하고, addRef와 releaseRef 를 통해 참조횟수 조절하고, 참조 카운트가 0일 경우 delete 호출)


각 객체에 Reference Count(참조 횟수)를 저장하고, 참조가 0이 되면 즉시 해제한다.

즉시 메모리 해제 가능 하기에 Stop-the-World가 없고, 객체의 사용량을 명확하게 파악 가능하나, 순환 참조의 문제와 대형 객체를 다룰 때 성능 저하가 있다.

Swift에서는 이를 개선한 ARC(Automatic Reference Counting) 방식을 사용한다.

자 그럼 핵심을 요약해보자

구분Tracing GC (Mark & Sweep)Reference Counting GC
기본 개념루트에서 추적하여 객체를 검사 후 정리객체의 참조 카운트를 기준으로 메모리 해제
메모리 누수 가능성순환 참조(Strong Reference) 가능 (GC가 자동 해결)순환 참조 시 메모리 누수 발생
GC 실행 비용객체 수와 참조 그래프 크기에 비례객체가 많아질수록 계산 부담 증가
성능객체 수가 많을수록 성능 저하 가능참조가 즉시 0이 되면 빠르게 해제 가능
Stop-the-World(STW)GC 실행 시 멈춤 발생즉시 해제 가능하므로 멈춤 없음
대표적인 사용 사례C#, Java, PythonObjective-C (ARC), Swift

그리고 기본 GC 알고리즘에 더하여 최적화 전략이 있는데

이 최적화 전략은 다음과 같다.

GC 최적화 전략은 무엇일까?


2abe8175abc236a14e81d2b628f1756afe6367

일반적으로 가장 유명한 것은


세대별 GC, Generational GC 일 것이다

1.Generational GC(세대별 GC)

객체의 생존 기간을 기반으로 Young & Old Generation으로 구분하여 GC를 수행하는데, 새로운 객체(Young)는 빠르게 GC, 오래된 객체(Old)는 천천히 GC를 한다.

우선 Young 과 Old를 나누는데, 여기서 Young과 Old를 나누고 어떻게 영역을 나누는지 한번 살펴보자

1-1. Young Generation(Eden+Survivor Space)

에덴(Eden) 영역
-새롭게 생성된 객체들이 최초로 할당되는 곳
-대부분의 객체들이 이 영역에 생성되며, 대부분은 곧 소멸하기 때문에 GC가 자주 발생

서바이버(Survivor) 영역
-에덴 영역에서 GC를 통해 살아남은 객체들이 이동하는 공간
-일반적으로 Survivor 영역은 2개가 있다. 객체가 여러번 GC를 견디면 결국 Old Generation으로 승격(Promotion)된다.
-Survivor 영역이 일반적으로 2개로 나뉘는 것은 앞서 Copying GC를 설명했는데, Surivor0 (From Space), Survivor1(To Space)로 일반적으로 사용되어진다.

새로 생성된 객체가 저장되고 대부분의 객체는 이곳에서 빠르게 소멸된다. 이 곳에서는 Copying GC를 사용하여 빠르게 제거한다.

이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.

1-2. Old Generation

노년(Tenured) 영역
-Young Generaion에서 여러번 GC를 견디며 살아남은 객체들이 이동 되는 곳이다.
-이 영역은 GC가 상대적으로 덜 발생하지만, 한번 GC가 발생하면 많은 객체를 대상으로 하므로 처리비용이 클 수 있다.

오래된 객체가 저장되고, Mark-Sweep 또는 Mark Compact 방식으로 정리된다.

객체 수명 패턴을 활용하여 성능 최적화를 한다. 이 영엑에서 객체가 사라질때 major GC(또는 Full GC)가 발생한다고 한다.

Young GC는 빠르게 수행, Old GC는 천천히 수행하지만 Old Generation에서 GC 발생시 Stop-the-World 가능성이 있다.

일반적으로 

일반적으로 Old 영역은 크게 할당하며, 크기가 큰 만큼 Young 보다 GC가 적게 발생한다.

사람의 일생을 예시로 해서 이야기해보자.


1. 에덴(Eden) 동산에 영혼 상태로 있다가

2. 세상의 부름을 받고 태어난 후,

3. 세상의 풍지 평파를 견디면서 살아가며(Survivor), 사고와 객사 없이 무사히 버텨서

4. 노인이 되어 사회적 존경을 얻고(Promotion)

5. 사회적 존경을 받는 노인이 죽어, 사회적으로 많은 사람이 더 애도하게 되는 것이다.(젊은이 보다 더 큰 애도 비용이 발생한다)


2abe8172abc236a14e81d2b628f1716f719c24c3

2.Parallel GC(병렬 GC)

여러개의 쓰레드(Thread)를 사용하여 GC를 병렬 실행하여 GC 수행속도를 높이는 방식이고, GC 속도 향상 및 대형 애플리케이션에 효과적이나, 쓰레드 동기화 오버헤드가 발생할 가능성이 있다. Java HotSpot JVM 에서 Parallel GC를 구현한 것으로 유명한다.

참고로 세대별 GC와 병렬 GC는 상호 배타적이지 않으므로 당연히 같이 사용 될 수 있고, 실제로 오히려 JVM HotSPot과 .NET에서도 사용되고 있다.


그럼 이제 GC는 어떻게 실행될까?

2abe8173abd828a14e81d2b628f1716f7300ff

1. STOP-the-World(STW)

GC 실행시 프로그램이 완전히 멈춘다.

전통적인 방식의 GC로써 GC 구현이 상대적으로 단순하며, 정확한 메모리 회수가 가능하다.

하지만 역시 응답시간의 증가로 사용자 경험 저하가 있다.


2.Incremental GC(점진적 GC)


39afd96be6dd34a97cf1d1bc10f11a392976fc73c0924871ee

이미지출처(https://databases.systems/posts/visualizing-the-go-gc)


GC를 여러 작은 단계(Small Steps)로 나누어 실행, Dijkstra의 Tri-Color Marking Algorithm을 기반으로 구현되었다.


이 알고리즘을 요약하면 3색을 어떻게 효율적으로 칠하느냐의 문제인데 


이것이 GC와 어떻게 연결되는지 설명하겠다.


객체를 처리되지 않음(unprocessed) , 처리중(processing), 처리됨(processed) 3가지로 나누고


흰색 : 처리되지 않음 unprocessed


회색: 처리 중 processing


검은색: 처리 됨 processed


로 나눈 후, 노드를 색칠하는 알고리즘이다.


1. 모든 노드는 흰색으로 시작


2. 객체에 방문하면 회색


3. 방문이 끝났으면 검은색 


이렇게 3가지 개체 집합이 생기고, 객체는 흰색->회색->검은색으로 이동하는데, 여기서 알 수 있는 사실은 흰색과 검은색이 직접 연결되지 않는 것이다.



3. Concurrent GC(병렬 GC)


GC와 애플리케이션 실행이 동시에 수행되고 STOP-the-World 시간을 최소화한다.


GC 스레드가 별도로 동작하고, 애플레케이션의 다른 스레드와 동시에 메모리 탐색(mark)나 복사 같은 작업을 수행한다.


이로 인해 GC 수행중에도 애플리케이션이 동작하고,다중 코어를 활용하지만, 메모리 관리가 더욱 복잡해진다.



2abe8170abd828a14e81d2b628f17065dfa721


조기 최적화는 모든 악의 근원이다- 도널드 커누스(Donald Knuth)


이 글에서는 GC의 역사와 현재 쓰이는 GC 알고리즘, 최적화 전략과 실행 방법을 썼다. 이후에 GC를 피하는 ZoC(Zero Allocation) 등도 있지만


이 글은 초보자를 대상으로 하기때문에, 초보자가 파악하기 어려워서 아쉽게도 생략하고, 일부 최적화 전략등을 생략했다.


직접 메모리를 다루는 것을 좋아하는 사람들은 GC를 악으로 여기며, 프로그래밍의 불확실성을 늘려준다고 말할지도 모르지만,


도널드 커누스가 말했듯 메모리 관리와 같은 '조기 최적화'에 매몰되기 보단, GC는 개발자로 하여금 '문제 해결'에 집중할 수 있게 해준다.


프로그래머의 역할은 단순히 기술을 숙지하는 것이 아니라, 당면한 문제를 해결하는 것이다.


GC는 많은 프로그래머들에게 '메모리 관리'라는 부담을 덜어주는 중요한 도구이다.


중요한 것은 'GC가 좋은가 나쁜가'가 아니라, '어떤 도구가 나의 문제를 해결하는 데 가장 적합한가'를 고민하는 것이라고 나는 생각한다.