💡가비지 컬렉션(Garbage Collection) 이란?
프로그램 실행 중 동적으로 할당된 메모리 중 더 이상 참조되지 않는 객체를 자동으로 탐지하여 해제하는 메모리 관리 기법으로, 메모리 누수를 방지하고 안정적인 자원 관리를 지원한다. Java, C#, Python 등에서 사용되며, 개발자가 명시적으로 메모리를 해제하지 않아도 되도록 해준다.
왜 필요할까?
프로그램에서 객체 생성 등의 작업으로 메모리를 할당해주다 보면, 메모리를 해제하는 코드를 직접적으로 작성하지 않는 이상 메모리 사용량은 계속 증가하게 되고 결국 메모리 누수(Memory leak)가 발생할 수 있다. 이러한 문제를 해결하기 위해서 가비지 컬렉션은 필수적이다.
가비지 컬렉션은 프로그램에서 사용 중인 메모리 영역에서 더 이상 사용하지 않는 객체(인스턴스)를 탐지하고, 자동으로 해당 객체가 차지하고 있는 메모리를 해제한다. 이 과정에서 프로그램이 일시 중단되는 시간(Stop the World)이 발생할 수 있으며, 이 시간이 길어지면 성능 문제가 발생할 수 있다.
동작 매커니즘
가비지 컬렉션의 매커니즘은 크게 두 가지 방법으로 구현된다.
참조 카운팅(Reference counting) 기법
- 객체가 참조될 때마다 카운트를 증가시키고, 해당 객체를 가리키는 포인터가 없어지면 카운트를 감소시키는 방법이다. 카운트가 0이 되면 해당 객체를 해제하는 방식이다.
마크 앤 스위프(Mark and Sweep) 기법
- 사용 가능한 메모리를 추적하고, 참조되는 객체를 표시(Mark)한다. 그리고 마크되지 않은 객체들은 사용 가능한 메모리로 판단하고, 해당 객체가 차지하는 메모리를 해제한다.
GC 주의 사항
1. 객체 참조 해제 (null 초기화)
- 더 이상 사용하지 않는 객체는 명시적으로 참조를 해제하여 null로 설정하는 것이 좋다.
- 특히 대용량 객체, 컬렉션(List, Map 등)에 저장된 객체, 또는 사용 주기가 명확한 리소스 객체(예: 파일, DB 커넥션 등)는 사용 후 참조를 명확히 해제해야 GC가 객체를 회수할 수 있는 시점이 빨라진다.
myObject = null; // 더 이상 필요 없다면 참조 해제
2. 순환 참조(Reference Cycle) 방지
- 두 객체가 서로를 참조하는 경우, GC가 해당 객체를 수거하지 못할 수 있다. (특히 C++이나 일부 언어의 수동 메모리 관리 환경에서 문제)
- Java의 GC는 순환 참조를 탐지할 수 있지만, 메모리 릭(Memory Leak)을 유발할 수 있으므로 의도적으로 순환 구조를 만들지 않도록 주의해야 한다.
- 해결 방법: 가능하면 한 방향 참조로 구조를 설계, 필요 시 약한 참조(WeakReference)를 사용하여 GC 대상이 될 수 있도록 처리
3. 리소스 해제는 명시적으로 (try-with-resources 사용)
- 가비지 컬렉션은 메모리만 수거하며, 파일, 소켓, DB 커넥션 등의 리소스는 GC로 회수되지 않는다.
- 반드시 try-with-resources 또는 finally 블록에서 명시적으로 닫아야 한다.
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) { // 작업 수행 } // 자동으로 close() 호출
4. 컬렉션에서 객체 제거
- 객체가 List, Map, Set 등에 담겨 있으면 참조가 유지되므로 GC 대상이 되지 않는다.
- 사용이 끝난 경우에는 컬렉션에서도 객체를 remove() 하거나, 전체 컬렉션을 clear() 해야 한다.
5. 정적(static) 필드에 주의
- static 필드는 클래스 로딩 시 생성되어 프로그램 종료 시까지 살아 있으므로, 여기에 저장된 객체는 GC 대상이 되지 않는다.
- 정적 필드에 불필요하게 큰 객체나 수명이 짧은 객체를 저장하지 않도록 주의한다.
6. 리스너(Listener), 콜백(Callback)의 등록 해제
- 이벤트 리스너나 콜백을 등록하고 해제하지 않으면, 참조가 유지되어 GC되지 않을 수 있다.
- 컴포넌트나 화면 전환 시 리스너 제거 코드를 반드시 작성한다.
button.removeActionListener(myListener);
7. 메모리 프로파일링 도구 활용
- GC 관련 문제를 식별하기 위해 VisualVM, Eclipse MAT, YourKit 등의 프로파일러 도구를 활용해 메모리 누수를 감지하는 것이 좋다.
Bad Example) GC로 회수되지 못하고 Momory leak이 발생 가능한 상황
MyClass 인스턴스가 자신의 필드인 list에 대량의 문자열을 계속 추가하면서 list가 모든 문자열을 강하게 참조하므로, 해당 인스턴스가 살아있는 동안 이 객체들이 메모리에서 해제되지 않아 메모리 릭이 발생한다.
public class MyClass {
private List<String> list = new ArrayList<>();
public void addToMyList(String str) {
list.add(str);
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
for (int i = 0; i < 1000000; i++) {
myClass.addToMyList("string" + i);
}
// ...
}
}
Good Example) 메모리 사용 후 명시적으로 참조 해제
List를 main 함수의 지역변수로 선언하여 main 함수와 생명주기를 같도록 하며, list 사용 후 명시적으로 메모리 참조를 해제하여 GC 대상이 될 수 있도록 하여 메모리가 쌓이는 것을 방지할 수 있다.
*ArrayList는 크기가 커질 때 마다 내부 배열을 재할당하기 때문에 초기 용량을 지정해주면 성능에 좋다.
public class MyClass {
public void addToMyList(List list, String str) {
list.add(str);
}
public static void main(String[] args) {
List list = new ArrayList<>(); // 지역변수로 선언
MyClass myClass = new MyClass();
for (int i = 0; i < 1000000; i++) {
myClass.addToMyList(list, "string" + i);
}
// ...
// 참조 해제
list.clear();
list = null;
}
}
reference.
'🗂️ 컴퓨터 과학(CS)' 카테고리의 다른 글
[ColorScripter] 글 작성 시 소스코드 디자인 변경 (8) | 2023.10.26 |
---|---|
[SSO] SSO(Single Sign-On) 개념 정리 (6) | 2023.10.20 |
[디지털 공학 응용] 중간 시험 (6) | 2023.10.18 |
[CS] Regular Expression. 정규 표현식 (0) | 2023.09.23 |