Garbage Collection 개념
쓰레기 수집(garbage collection 가비지 컬렉션, GC)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다. 영어를 그대로 읽어 가비지 컬렉션이라 부르기도 한다. 1959년 무렵 리스프의 문제를 해결하기 위해 존 매카시가 개발하였다.
C/C++ 메모리 할당 및 해제
C/C++은 기본적으로 동적 메모리 할당을 위하여 malloc, new, new [ ] 등을 사용하여 메모리를 할당한다. 또한 free, delete, delete [ ]를 사용하여 메모리를 개발자가 직접 해제할 수 있다.
Java 메모리 할당 및 해제
Java는 다른 프로그래밍 언어와는 달리 메모리의 할당은 개발자가 원하는 만큼 할 수 있지만 이미 할당 받은 메모리를 해제하는 것은 개발자의 영역이 아니다. 다시 말해 메모리의 해제는 하고 싶어도 할 수가 없다는 것이다. Java에서의 System.gc()나 close()와 같은 함수는 Garbage Collection을 명시적으로 수행하거나 해당 객체의 사용을 중지하겠다는 의사 표현일 뿐 직접적으로 메모리를 삭제하는 기능이 아니다.
Java에서 메모리 해제란 Heap이나 Method Area에 있는 특정한 Object를 Memory에서 삭제한다는 의미로 생각하여야 한다. Java에서는 할당된 메모리를 Garbage Collector가 Garbage Collection을 통해 메모리를 해제한다. JVM의 Specification에서 GC에 대한 언급은 다음과 같다.
Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated.
힙 스토리지는 object들을 위한 공간이고 자동 스토리지 관리 시스템(GC라고 불림)에 의해 회수된다. object들은 명시적으로 메모리 해제가 되지 않는다.
- The Java® Virtual Machine Specification Java SE 18 Edition Java Virtual Machine (2.5.3 Heap)
JVM 벤더들은 해당 두 줄의 글을 보고 그에 맞게 Object와 Heap의 Layout을 구성하고 Garbage Collection에 대한 Algorithm을 적용하여 우리가 사용하고 있는 Garbage Collection을 제작하였다.
Garbage Collection 대상
Garbage Collection은 말 그대로 Garbage를 모으는 작업이다. 여기서 Garbage란 Heap과 Method Area에서 사용되지 않는 Object를 의미한다. 그런데 여기서 '사용되지 않는다'는 의미를 어디까지 볼 것이냐의 문제가 남아있다.
Garbage Collector는 이 문제를 현재 사용하고 있지 않는 Object로 Garbage를 판단한다. 그리고 현재 사용 여부는 바로 Root Set과의 관계로 판단한다. 다시 말해 Root Set에서 어떤 식으로든 Reference 관계가 있다면 Reachable Object라고 한다. 이것을 현재 사용하고 있는 Object로 간주하게 된다. Root set이 참조하는 정보는 다음과 같다.
- Stack의 참조(Ref) 정보
- Constant Pool에 있는 참조(Ref) 정보
- Native Method로 넘겨진 객체 참조(Ref) 정보
이 세 가지 Ref 정보에 의해 직, 간접적으로 참조되고 있다면 모두 Reachable 객체이고 아니면 Garbage 취급을 한다. 그리고 Reachable 객체에서도 Live 여부에 따라 둘로 나뉘어진다. Live는 실제로 해당 객체를 사용하는 지에 대한 여부이다.
Reachable But not Live(Memory Leak) 예제
다음 예제 코드는 ArrayList에 String 객체를 집어 넣고 바로 제거하는 의도로 만들어졌다. 제거하기 위해 만들어진 removeStr()에서는 해당 객체를 찾아 이것을 null로 치환하는 방식을 사용하였다.
class Main {
public static void main(String[] args) {
leakExecution(); // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
}
static void leakExecution(){
Leak lk = new Leak();
for(int a = 0; a<90_000_000; a++){
lk.addList(a);
lk.removeStr(a);
}
System.out.println("Success");
}
}
class Leak {
ArrayList list = new ArrayList();
public void addList(int a){
list.add("가나다라마바사아자차카타파하" + a);
}
public void removeStr(int i){
Object obj = list.get(i);
obj = null;
}
}
그러나 해당 코드를 실행하면 OutOfMemoryException이 발생하여 프로그램이 비정상으로 종료된다. 그 이유는 obj로 받은 것은 String 객체가 아니라 String 객체로 접근하는 Reference 값이기 때문이다. 주민등록이 말소되었다고 사람이 없어지지 않는 것과 동일하다.
Reference가 null이 치환되었다고 해서 ArrayList에 들어가 있는 String 객체는 사라지지 않게 된다. 하지만 이 객체는 코드 내에서 더이상 사용되지 않을 것이다. 이러한 것을 Reachable but not Live 객체라고 한다. 이러한 객체가 많아지면 Heap에 Memory Leak이 발생했다고 표현한다.
참고
The Java® Virtual Machine Specification Java SE 18 Edition
Java Perfomance Fundamental - 김한도 저
'Dot Programming > Java' 카테고리의 다른 글
JVM 5 - Garabage Colletion에 사용하는 알고리즘 (0) | 2022.06.20 |
---|---|
JVM 3 - Runtime Data Area에서의 객체 생성, 소멸 및 참조 과정 알아보기 (0) | 2022.06.01 |
JVM 2 - Runtime Data Area 구조 (0) | 2022.05.31 |
JVM 1 - Java Architecture, JVM Specification (0) | 2022.05.31 |
[Play with Java] Java 8로 꼬리재귀 함수 만들기 (Tail Recursion) (3) | 2022.04.10 |