Tiny Star

🪄Interview/✏️Study

[CS STUDY INTERVIEW] 10주차 - GC(Garbage Collection)

청크 2024. 5. 4. 19:44

CS 스터디 10주차 

 

GC(Garbage Collection)


GC(Garbage Collection)

 

이름 그대로 쓰레기 수집을 하는데, 메모리 관리 방법 중 하나이다.

프로그램이 더 이상 사용하지 않는 메모리 영역을 자동으로 찾아내어 재활용하는 과정으로

이 기술은 메모리 누수를 방지하고 프로그램의 안정성을 높이는 데 크게 기여한다.

 

개발을 하다보면 사용하지 않는 객체들로 인하여 불필요한 메모리가 사용이되면서 메모리 누수가 발생한다.

C/C++언어의 경우는 개발자가 직접 사용하지 않는 객체들에 대해서 free()함수를 이용하여

메모리를 해제하면서 직접 관리해줘야하지만 Java에서는 JVM의 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주기 때문에

명시적으로 객체를 해제할 필요가 없다.

 

즉, 사용하지 않는 객체를 메모리에서 삭제하는 작업을 GC라고 다른다.


GC(Garbage Collection) 의 동작 시점

이 가비지컬렉션는 JVM의 메모리 영역(class, stack, heap, native mothod, PC)영역 중 heap 메모리만 다루며

가비지 컬렉터가 동작하는 시점은 일반적으로 이렇다.

 

1. 메모리 할당 실패 시

프로그램이 새로운 객체를 메모리에 할당하려 할 때 충분한 공간이 없는 경우에 GC가 동작하여 사용하지 않는

객체들을 정리하고 메모리를 회수한다.

2. 메모리 사용량이 특정 임계값에 도달한 경우

GC는 메모리 사용량을 모니터링하고, 사용량이 설정된 어떤 임계값이 도달하면 GC를 실행하여 메모리를 청소한다.

3. 정기적인 실행

일부 시스템에서는 GC가 주기적으로 실행하여 메모리 누수를 방지할 수 있도록 설정이 가능하다.

4. 명시적 요청

개발자가 직접 GC를 실행시키는 경우

예를들어, 자바에서는 'System.gc()' 메소드가 호출되면 GC의 실행을 제안한다.

비록 메서드를 호출한다고 즉시 실행이 되는건 아니지만 GC실행을 요청하는 힌트로 동작한다.

 

위에 내용은 이론적인 이야기에 가까운 것 같아서 개발을 하는 상황에서의 몇가지 예를 들어보자면

일반적으로 다음과 같은 경우에 가비지 컬렉터가 동작하여 메모리를 해제시킨다.

1. 객체가 Null인 경우 (String str = null)

객체에 null을 할당하는 것은 그 객체에 대한 참조를 제거하는 것이나 마찬가지다.

예를들어 str이 참조하던 문자열 객체는 이제 더이상 어떠한 변수에도 참조되지 않는다면 이는 GC의 회수 대상이 된다.

 

해당 객체가 null이되면 그 객체를 참조하는 다른 참조가 없는 경우에도 메모리에서 제거 될 수 있다.

 

2. 블럭 실행 종료 후 블럭 안에서 생성된 객체

블록(예 - if/else 내부 등)내에서 생성된 객체는 그 블록의 실행이 종료되면 일반적으로 접근할 수 없게된다.

(블록 외부로 객체를 반환하거나 외부 변수에 저장하는 경우 제외)

이렇게 접근이 어려운 객체의 경우는 해당 블록의 스코프를 벗어나는 순간 더이상 참조되지 않기 때문에

GC의 메모리 회수대상이 되는 것이다.

 

3. 부모 객체가 Null인경우,포함하는 자식 객체

1번과 같은 경우지만 이것은 객체가 다른 객체를 포함하는 구조

즉, 부모객체가 자식 객체를 필드로 갖는 경우에 부모 객체의 참조가 null이 되거나 더이상 참조하지 않는경우에

자동으로 부모뿐만 아니라 연결된 자식 객체들도 GC의 메모리 제거대상이 된다.

 

하지만 이것은 부모 객체가 유일하게 자식객체를 참조하는 경우에 해당이되며

만약 다른 어떤 참조가 자식 객체들을 계속 참조하고 있다면, 그 자식 객체들은 메모리에서 제거되지 않는다.


GC(Garbage Collection)의 알고리즘과 동작과정

각 가비지 컬렉션은 여러 알고리즘을 사용하여 구현될 수 있고 각기 다른 상황과 요구사항에 맞추어 선택된다.

어떤 알고리즘이 어떻게 메모리를 해제할까?

 

대표적으로는 세 가지 알고리즘이 있다.

1. 마킹-스위핑(Mark-and-Sweep)

가장 널리 사용되는 가비지 컬렉션 중 하나로 알고리즘의 첫 단계는 마킹이다.

프로그램이 활성화된 모든 객체를 표시하면 다음 단계인 스위핑에서는 표시되지 않은 모든 객체는 메모리에서 제거한다.

 

마킹(Marking)

우선 프로세스는 마킹을 호출하는데, 이건 GC가 메모리가 사용이 되는지 아닌지를 찾아내는 과정이다.

모든 전역 변수, 스택에서의 지역변수, CPU레지스터 등을 포함하는 루트로부터 접근 가능한 객체들을 식별한다.

이 과정에서 GC는 어떤 방식으로든 직/간접적으로 루트로부터 접근 가능한 객체를 마킹한다.

 

그림에서 보면 접근 가능한 객체는 파란색, 참조되지 않은 객체는 주황색으로 처리하여

마킹 단계에서 모든 스캔이 완료되고, 이 과정에서 모든 루트를 스캔하기 때문에 많은 시간이 소모된다.

 

스위핑(Sweeping)과 일반 삭제(Normal Deletion)

개념을 찾다보니 마킹 다음 스위핑이라는 글과 일반 삭제라는 글이 겹쳐 따로 정리해보았다.

"스위핑(Sweeping)"과 "일반 삭제(Normal Deletion)"는 메모리 관리에서 사용되는 두 가지 다른 개념으로

이들은 메모리에서 객체를 제거하는 방식과 관리 방법에서 차이를 보인다.

 

일반 삭제(Normal Deletion)

 

그림을 우선 설명해보자면 참조되지 않은 객체를 제거하고 메모리를 반환하는 과정을 거친다.

메모리 Allocator는 반환되어 비어진 블럭의 참조 위치를 저장해 두었다고 새로운 오브젝트가 선언되면 할당되도록 한다.

 

일반 삭제는 프로그래머가 코드 내에서 객체를 명시적으로 삭제하는 행위를 가리키며

예를 들어 C++ 같은 언어에서는 delete 키워드를 사용하여 객체를 메모리에서 직접 제거할 수 있다.

1. 수동 처리

프로그래머는 메모리 해제 시점을 직접 결정하고 코드를 통해 명시적으로 객체를 삭제해야 한다.
2. 즉각적 실행

delete 명령이 호출되면, 메모리에서 해당 객체는 즉시 제거되며 이는 가비지 컬렉션 과정의 지연 없이 바로 실행된다.

3. 메모리 누수 위험

일반 삭제는 개발자가 메모리를 직접 관리하기 때문에 메모리 누수 및 기타 오류가 발생할 가능성이 있어서

객체를 삭제한 후에 해당 메모리 주소에 다시 접근하는 등의 오류가 발생할 수 있다.

 

스위핑(Sweeping)

마킹 단계가 완료된 후 시작되는 단계로 이 단계에서는 마킹되지 않은 객체들

즉 어떤 루트도 접근할 수 없는 객체들을 메모리에서 제거하는 단계로 이렇게 회수된 메모리는 새로운 객체를 위에 사용된다.

 

스위핑은 메모리관리의 자동화된 과정으로

 

1. 자동 처리

스위핑은 프로그래머가 직접 메모리를 관리할 필요 없이, 가비지 컬렉터가 자동으로 수행한다.
2. 마킹-스위핑 과정

스위핑은 마킹 과정이 끝난 후에 실행되며, 마킹되지 않은(즉, 더 이상 필요 없거나 접근 불가능한) 객체들을 메모리에서 제거한다.
3. 메모리 재사용

스위핑으로 제거된 메모리 공간은 프로그램이 새 객체를 생성할 때 재사용될 수 있다.

 

이 두 방법은 메모리 관리 방식에서 명확하게 구분되며, 각각의 방법은 그에 따른 장단점을 가지고 있다.

스위핑은 메모리 관리를 자동화하여 프로그래머의 부담을 줄이는 반면,

일반 삭제는 메모리 사용을 더 세밀하게 제어할 수 있게 해주지만, 관리에 더 많은 주의를 요구한다.

 

압축(Compacting)

GC는 회수된 메모리 공간 사이에 생기는 조각을 줄이기 위해 메모리 압축 단계를 수행한다.

이 압축단계는 사용중인 객체들을 메모리 내에서 서로 가깝게 이동시켜서 메모리의 연속적 사용을 가능하게 하며

메모리 할당 효율을 높히지만 모든 GC가 거치는 단계는 아니라 선택적이라고 볼 수 있다.

 

2. 참조 카운팅(Reference Counting)

참조 카운팅은 각 객체에 참조의 수를 기록하는 방식이다.

객체가 다른 객체로 참조될 때마다 카운트가 증가하고, 참조가 제거되면 감소하는데,

참조 카운트가 0이 되면 해당 객체는 더 이상 필요 없는 것으로 간주되어 즉시 메모리에서 제거된다.

이 방법은 실시간으로 가비지 컬렉션을 수행할 수 있는 장점이 있지만, 순환 참조를 자동으로 처리하지 못하는 단점도 존재한다.

 

3. 복사 (Copying)

복사 알고리즘은 메모리를 두 부분으로 나누고, 활성 객체만 새로운 위치로 복사한 후, 오래된 부분을 전체적으로 정리하는 방식이다.

이 방법은 메모리 사용의 효율성을 떨어뜨릴 수 있지만, 활성 객체만을 빠르게 복사하므로 가비지 컬렉션 시간을 줄일 수 있고

또한, 메모리 내의 객체들이 연속적으로 배치되므로 캐시 활용성이 좋아져 성능이 향상될 수 있다.


Generational Gabage Collection

"Generational Garbage Collection"은 가비지 컬렉션(GC)의 한 방식이다.

 

GC는 크게 두 가지로 구분할 수 있는데,
1. 전통적인 가비지 컬렉션

이 방식에서는 메모리의 모든 객체를 일괄적으로 검사하여, 사용되지 않는 객체를 찾아내고 메모리를 회수한다.

여기에는 마크-스위핑(Mark-and-Sweep), 참조 카운팅(Reference Counting) 등의 방법이 포함된다.
2. Generational 가비지 컬렉션

 메모리 관리의 효율성을 높이기 위해 객체를 세대별로 구분하여 관리하는 방식이다.

여기서 객체들은 생성된 후 살아남는 시간에 따라 다른 "세대"로 분류되며,

각 세대에서 객체의 생존 시간에 따라 가비지 컬렉션의 빈도와 방법이 다르게 적용되는데

이는 특히 객체의 생존 기간이 짧은 경우가 많은 프로그램에서 효율적이다.


Generational 가비지 컬렉션은 전통적인 방법보다 메모리를 더 세밀하게 관리할 수 있으며, 

프로그램의 실행 속도를 저하시키는 전체 메모리 스캔의 빈도를 줄일 수 있기 때문에

특정 상황에서 더 높은 성능을 제공할 수 있는 고급 기술로 생각할 수 있다.

1. 젊은 세대 (Young Generation)

젊은 세대는 주로 새로 생성된 객체들이 배치되는 곳으로

이 영역은 더 작은 세부 영역으로 나뉠 수 있으며, 일반적으로는 Eden 영역과 두 개의 Survivor 영역(S0, S1)으로 구성된다.

 

새 객체들은 Eden 영역에 생성되며, 가비지 컬렉션이 발생할 때 살아남은 객체들은 하나의 Survivor 영역으로 이동되고

다음 가비지 컬렉션에서 살아남은 객체들은 다른 Survivor 영역으로 이동하고, 이 과정에서 여러 번 살아남은 객체들은

점차 Old Generation으로 승격되는 식으로 작동한다.
이 세대는 Minor GC라 불리는 빈번하지만 빠른 가비지 컬렉션이 가능하고

이 과정은 일반적으로 'stop-the-world' 이벤트를 일으키며, 이 동안 프로그램의 실행이 일시적으로 멈추게 된다.

 

2. 중간 세대 (Old Generation)

중년 세대는 젊은 세대에서 살아남아 여러 차례 가비지 컬렉션을 견딘 객체들이 이동되는 곳으로 이해하면 쉽다.

이 객체들은 일반적으로 더 오래 존재하며, 메모리를 점유하는 크기도 더 큰 편이다.

 

객체들이 점차 이 영역으로 승격되며, 여기서 메모리는 훨씬 더 큰 블록을 형성하게 되고

이 세대의 가비지 컬렉션은 훨씬 덜 자주 발생하지만, 발생 시에는 더 오랜 시간이 소요될 수 있다.

 

이 세대에서는 Major GC 또는 Full GC가 발생하며, 전체 메모리 힙을 대상으로 가비지 컬렉션을 수행하며

이 과정은 프로그램 성능에 상당한 영향을 줄 수도 있다.

 

3.영구 세대 (Permanent Generation) - 최신 JVM에서는 사용되지 않음

Java 버전에서는 클래스 로더에 의해 로드된 클래스와 메소드의 메타데이터를 저장하는 영역으로

이 영역은 다른 세대보다 가비지 컬렉션이 적게 발생했지만 Java 8 이후, 이 세대는 Metaspace로 대체되었으며,

Metaspace는 네이티브 메모리를 사용하여 클래스 메타데이터를 저장한다.

 

 

*Weak Generational Hypothesis*

이 가설은 메모리 관리 최적화 전략의 핵심 원칙으로 Generational Garbage Collection의 기본적인 가정 중 하나이다.


1. 대부분의 객체는 생성된 직후에 금방 소멸된다 (Most objects die young)

새로 생성된 많은 객체들은 아주 짧은 시간 내에 사용되지 않게 되고, 이후에는 필요 없어져 메모리에서 제거될 준비가 되기 때문에

이는 프로그램이 많은 일시적 객체를 생성하는 경향이 있음이 반영된다.
2. 젊은 객체들 사이의 참조는 상대적으로 적고, 오래된 객체들이 젊은 객체를 참조하는 경우도 적다

새로 생성된 객체가 다른 젊은 객체에 의해 참조되는 경우가 적고, 오래된 객체가 새 객체를 참조하는 경우도 드물다는 것을 의미한다.


이러한 가정 하에 Generational Garbage Collection은 메모리 관리를 효율적으로 수행하기 위해 설계되었다.


Weak Generational Hypothesis의 효율성은 현대의 많은 프로그래밍 패턴과 일치하며, 

이로 인해 Generational Garbage Collection은 Java와 같은 언어에서 널리 사용되고 있으며

이 가설을 바탕으로 가비지 컬렉터들은 메모리 할당과 회수를 최적화하여 애플리케이션의 성능 개선이 가능하다.


Generational Garbage Collection 과정

 

1. 새 객체 생성

새로운 객체는 메모리의 "젊은 세대"인 Eden 영역에 할당되며 여기는 새 객체들의 시작점이다.

2. 메모리가 가득 차면

Eden 영역이 가득 차게 되면, 'minor garbage collection'이라는 청소 과정이 시작되는데 이 때 프로그램은 잠시 멈춘다.(stop-the-world)


3. 객체 이동

참조되고 있는 (즉, 아직 필요한) 객체들은 첫 번째 'Survivor Space' (S0)로 이동하며

참조되지 않는 (더 이상 필요 없는) 객체들은 메모리에서 제거된다.


4. 반복 과정

Eden 영역에서 비슷한 청소가 다시 필요할 때까지 계속해서 새 객체들이 여기에 할당되고,

다음 minor GC에서는 참조된 객체들이 두 번째 Survivor Space (S1)로 이동한다.

이 과정에서 객체들은 S0와 S1 사이를 오가며, 여러 번 GC를 견딜 때마다 객체의 'age'가 증가하게 된다.


5. Survivor Space 교체

Minor GC가 발생할 때마다, 두 Survivor Space의 역할이 서로 교체된다.

참조된 객체들은 현재 비어 있는 Survivor Space로 이동하고, 나머지 공간은 클리어되는 것이다.

6. 객체 승격

객체가 일정 횟수 이상 (예: 8번) minor GC를 견디면, 그 객체는 '중간 세대' (Old Generation)로 승격되어 관리된다.

이 세대는 보다 오랫동안 메모리에 남아있게 된다.

7. Major GC

결국 Old Generation 영역이 가득 차면, 'major garbage collection'이 실행되는데

이 과정은 Old Generation 전체를 대상으로 하여 더 오래 걸리고, 여기서도 불필요한 객체들을 제거하고 메모리를 정리한다.


면접 예상 질문과 답변

Q1. Generational Garbage Collection이란?
A1. Generational Garbage Collection은 객체를 생성된 시간에 따라 다른 '세대'로 분류하여 관리하는 메모리 관리 기법입니다.

이 방법은 주로 Java의 JVM과 같은 관리형 환경에서 사용되며, 메모리를 젊은 세대(Young Generation), 중간 세대(Old Generation)

및 영구 세대(Permanent Generation, 최신 Java에서는 Metaspace)로 나누어 효율적으로 가비지 컬렉션을 수행합니다.

Q2. 일반적인 가비지 컬렉션(GC)과 Generational Garbage Collection의 차이점은?
A2. 일반적인 가비지 컬렉션은 메모리에서 사용되지 않는 모든 객체를 대상으로 메모리를 정리하는 방법입니다.

이는 참조 카운팅, 마킹-스위핑 같은 다양한 알고리즘을 사용할 수 있습니다.

반면, Generational Garbage Collection은 객체를 세대별로 구분하고 각 세대에 대해 다르게 가비지 컬렉션을 수행하여,

전체적인 메모리 관리 효율을 향상시킵니다.

Q3. 가비지 컬렉션의 주요 알고리즘에는 어떤 것들이 있나?
A3. 가비지 컬렉션의 주요 알고리즘으로는 마킹-스위핑(Mark-and-Sweep), 참조 카운팅(Reference Counting), 그리고 복사(Copying)가 있습니다. 마킹-스위핑은 활성 객체를 마킹하고 마킹되지 않은 객체를 제거하고 참조 카운팅은 객체가 참조되는 횟수를 카운팅하여

참조 횟수가 0이 될 때 객체를 제거합니다. 복사 알고리즘은 활성 객체만을 새로운 영역으로 복사하고 나머지는 제거하는 방식입니다.

Q4. Minor GC와 Major GC의 차이점은 무엇인가?
A4. Minor GC는 젊은 세대에서 주로 발생하며, 새로 생성된 객체 중에서 빠르게 소멸하는 객체를 대상으로 메모리를 정리합니다.

이 과정은 비교적 빈번하고 짧습니다. Major GC는 중간 세대를 대상으로 하며, 비교적 덜 자주 발생하지만,

실행 시간이 길고 프로그램에 더 큰 영향을 미칩니다. 때로는 전체 힙 영역을 대상으로 할 수도 있습니다.

Q5. System.gc() 메서드는 무엇이며 어떻게 작동하는가?
A5. System.gc() 메서드는 Java에서 가비지 컬렉터의 실행을 제안하는 메서드입니다.

이 메서드를 호출하면 JVM에 가비지 컬렉션을 수행하도록 요청하게 되지만, 가비지 컬렉션이 즉시 실행된다는 보장은 없습니다.

JVM은 리소스 상황과 가비지 컬렉션 알고리즘에 따라 이 요청을 처리할 시기를 결정합니다.

Q6. 가비지 컬렉션에서 'Stop-the-World' 현상이란?
A6. 'Stop-the-World' 현상은 가비지 컬렉션이 실행될 때 발생하는 현상으로, 가비지 컬렉션을 수행하는 동안

JVM 내의 모든 애플리케이션 스레드가 일시 정지됩니다. 이는 가비지 컬렉터가 메모리를 안전하고 일관되게 정리할 수 있도록 하기 위함입니다.

이로 인해 애플리케이션의 응답성이 일시적으로 저하될 수 있습니다.

Q7. 객체가 가비지 컬렉션 대상이 되는 조건은 무엇인가?
A7. 객체가 가비지 컬렉션의 대상이 되려면 더 이상 그 객체를 참조하는 변수나 포인터가 없어야 합니다.

즉, 어떤 루트 집합(활성 스레드, 스택 프레임 등에서 접근 가능한 객체들)에서도 접근할 수 없는 상태여야 합니다.

Q8. Generational Hypothesis란 무엇이며, 왜 중요한가?
A8. Generational Hypothesis는 대부분의 객체가 생성된 직후에 금방 소멸된다는 가설입니다.

이 가설에 따라 Generational Garbage Collection은 메모리를 더 효과적으로 관리할 수 있습니다.

젊은 객체가 빈번하게 소멸되므로, 젊은 세대에 더 집중적으로 가비지 컬렉션을 수행함으로써 전체 성능을 향상시킬 수 있습니다.

Q9. 가비지 컬렉션의 성능 영향을 최소화하기 위한 전략은 무엇인가?
A9. 가비지 컬렉션의 성능 영향을 최소화하기 위해서는 객체 할당과 생명주기를 최적화하고,

가비지 컬렉션 튜닝 옵션을 적절히 설정하여 사용하는 것이 중요합니다.

예를들어, 객체의 크기와 생명주기에 따라 적절한 세대에 할당하고, 필요할 때 적절한 시간에 가비지 컬렉션을 수행하도록 스케줄링하는 것이 포함됩니다.

Q10. 참조 카운팅 가비지 컬렉션 방식의 장단점은 무엇인가?
A10. 참조 카운팅 방식의 장점은 객체의 참조 횟수가 0이 되는 즉시 메모리에서 객체를 제거할 수 있다는 점입니다.

이는 실시간 시스템에서 유용할 수 있습니다. 단점은 순환 참조 상황을 자동으로 처리하지 못해 메모리 누수가 발생할 수 있다는 점입니다.

이를 해결하기 위해 추가적인 알고리즘이 필요할 수 있습니다.