#Automatic Memory Management
명시적(explicit) 메모리 관리(예: malloc() 및 free()를 통한)는 오류가 발생하기 쉽다. 이에 반해, 대부분의 현대 언어들은 자동 메모리 관리또는 가비지 컬렉션이라고도 불리는 "암묵적 메모리 관리" 형태를 제공한다
- 명시적 메모리 관리는 복잡하며, 다양한 오류가 발생할 수 있다:
- 메모리를 너무 일찍 해제하면 use-after-free 오류가 발생할 수 있다.
- 메모리 해제를 너무 늦게 하거나 전혀 하지 않으면, 메모리 누수(leak)의 위험이 있다.
자동 메모리 관리 시스템은 객체의 소유권과 수명을 식별하는 원칙적인 설계를 필요로 한다. 이는 API 설계를 복잡하게 만들 수 있으나, 프로그래밍에서 발생할 수 있는 여러 문제들(메모리 누수, 과도한 메모리 할당과 반환(churn) 및 메모리 팽창(bloat) 등)을 해결할 수 있는 방법을 제공한다. 이번 포스트에서는 가비지 컬렉션, 참조 카운팅 접근법과 이와 관련된 프로그래밍 문제들에 대해 살펴보자
#Explicit vs. Implicit Memory Management
#Garbage Collection
가비지 컬렉션은 메모리 관리의 한 형태로, 프로그래머가 동적으로 할당한 메모리 중 더 이상 사용되지 않는 부분을 자동으로 탐지하여 해제하는 기능이다. 이 개념은 1959년 존 매카시가 LISP 언어의 메모리 관리를 위해 처음 도입했다고 알려져 있다. 가비지 컬렉션의 기본 알고리즘은 사용 가능한 객체를 식별하고 보존하는 동시에 사용되지 않거나 사용 가능하지않은 나머지를 회수하는 것이다.
잘 설계된 프로그램에서는 유효한 포인터나 참조가 없는 객체에는 접근할 수 없다. 이는 포인터와 정수 간의 변환을 허용하지 않는다고 가정할 때 더욱 명확해진다. 프로그램은 도달 가능한 객체만을 연결할 수 있으며, 이후에 접근할 수 있는 객체에 대해서는 알 수 없다. 접근할 수 없는 메모리는 누수된 것으로 간주된다.
이 과정에서 필수적인 추상화는 '도달 가능성 그래프'(Reachability Graph)이다. 이 그래프는 시스템이 메모리를 얼마나 효율적으로 관리할 수 있는지를 나타내는 중요한 도구로, 어떤 객체가 아직 유효하게 사용되고 있는지, 또는 가비지 컬렉션을 통해 회수될 준비가 되어 있는지를 시각적으로 표현한다.
#Reachability Graph in Java
class B {
int x, y;
B(int x, int y) {
this.x = x;
this.y = y;
}
}
public class A {
static A S;
B f;
public static void main(String[] args) {
S = new A();
A local = new A();
B b = new B(1, 2);
set(local, b);
b = null;
local = null;
}
static void set(A t, B b) {
t.f = b;
}
}
#Mark and Sweep Garbage Collection Algorithm
자바에서의 'Mark and Sweep' 알고리즘은 가비지 컬렉션을 수행하는 데 사용되는 기본적인 메커니즘 중 하나이다. 이 알고리즘은 두 주요 단계로 이루어져 있으며, 메모리 관리에서 더 이상 사용되지 않는 객체를 효과적으로 제거하는 데 중요한 역할을 한다.
# 루트 식별
- 가비지 컬렉션 프로세스의 첫 번째 단계는 '루트'를 식별하는 것이다. 여기서 루트는 가비지 컬렉터가 참조를 시작하는 진입점을 의미한다. 자바에서는 다음과 같은 소스에서 루트를 찾는다:
- Static fields: 정적 필드는 클래스가 로드될 때 생성되고, 프로그램 실행이 종료될 때까지 유지되므로, 이들은 루트로 간주된다.
- 모든 스레드의 진행 중인 메서드 호출의 JavaLocal 변수: 각 스레드가 실행 중인 메서드 내부의 로컬 변수들은 활성 루트로 작용한다.
- JVM 내부 루트: 자바 가상 머신 자체 내에 존재하는 참조들도 루트로 포함된다. 이들은 JVM이 관리하는 리소스와 관련된 참조들이다.
# 마킹
루트가 식별되면, 깊이 우선 탐색(DFS)과 같은 알고리즘을 사용하여 힙 전체를 통해 도달 가능한 모든 객체를 표시한다. 이 단계에서 가비지 컬렉터는 루트에서 시작하여 참조되는 모든 객체에 접근하며, 각 객체에 '방문됨' 표시를 한다. 이 과정은 모든 도달 가능한 객체가 식별될 때까지 계속된다.
# 스위핑
마킹 단계가 완료되면, 표시되지 않은, 즉 참조되지 않는 모든 객체를 메모리에서 회수하는 스위핑 단계가 시작된다. 이 단계에서 가비지 컬렉터는 힙을 스캔하며 '방문됨' 표시가 없는 객체들을 찾아 메모리에서 제거한다. 이 과정을 통해 메모리 공간을 회복하고, 새로운 객체 할당을 위한 공간을 확보할 수 있다.
# 요약
'Mark and Sweep' 알고리즘은 효율적인 가비지 컬렉션 방법을 제공하여 자바 프로그램의 성능과 안정성을 높이는 데 기여한다. 루트 식별에서부터 객체 마킹, 그리고 불필요한 객체의 스위핑에 이르기까지, 이 알고리즘은 자바 가상 머신의 메모리 관리의 핵심 구성 요소이다.
#Reference Counting Garbage Collection Algorithm
참조 횟수 계산 방식은 각 객체가 자신을 참조하는 포인터의 수를 계속해서 추적하는 방법이다. 객체에 새로운 참조가 추가될 때마다 참조 카운트가 증가하고, 참조가 제거될 때는 감소한다. 참조 횟수가 0에 도달하면, 해당 객체는 즉시 메모리에서 해제된다. 이 방식은 지연 없이 메모리를 관리할 수 있다는 장점이 있다.
#Manual Reference Counting
수동 참조 횟수 관리에서는 프로그래머가 직접 참조 카운트를 업데이트하는 책임을 진다. 예를 들어, Microsoft의 COM(Component Object Model) 시스템에서는 이러한 접근 방식이 사용된다. 이 경우, 프로그래머는 정해진 규칙을 따라야 한다. 예를 들어, 새로운 참조를 설정할 때는 AddRef()를 호출하고, 참조를 해제할 때는 Release()를 호출한다. 이 방식은 여전히 오류 발생 가능성이 있지만, 규칙을 통해 프로그램의 정확성을 보장할 수 있다. 이는 복잡한 전역 컨텍스트 없이도 로컬에서 프로그램의 정확성을 단언할 수 있다는 장점이 있다.
#Reference Counting using Smart Pointers
일부 프로그래밍 언어와 라이브러리는 참조가 생성, 할당, 복사 또는 덮어쓸 때 자동으로 참조 카운트를 관리할 수 있는 지원을 제공한다. 예를 들어, C++에서는 std::shared_ptr가 이 역할을 한다. 러스트(Rust)에서는 std::rc::Rc가 비슷한 기능을 수행한다. 이러한 시스템의 공통적인 한계는 객체 도달 가능성 그래프 내의 순환 참조를 자동으로 처리할 수 없다는 것이다. 순환 참조는 두 객체가 서로를 참조하고 있어서 참조 카운트가 절대 0에 도달하지 않는 경우를 말한다. 이로 인해 메모리 누수가 발생할 수 있다. 따라서 참조 횟수 계산 방식을 사용할 때는 순환 참조를 감지하고 해결하는 추가적인 메커니즘이 필요할 수 있다.
'Computer Science > Computer Systems' 카테고리의 다른 글
[Lecture 7] 멀티쓰레딩 - Introduction to Multithreading (0) | 2024.04.11 |
---|---|
[Lecture 9] 멀티쓰레딩 III - Semaphores / 세마포어 (1) | 2024.04.11 |
[Lecture 21] HTTP (0) | 2024.04.03 |
[Lecture 17] Automatic Memory Management, Performance (0) | 2024.04.03 |
[Lecture 24] Virtualization (0) | 2022.12.08 |