
자바는 가비지 컬렉션(Garbage Collection, GC)을 통해 자동으로 메모리를 관리한다. 그래서 메모리 관리에 신경 쓰지 않아도 된다고 착각하기 쉽다. 하지만 잘못된 객체 참조를 계속 유지하면 메모리 누수(메모리 낭비)가 발생한다.
예: 메모리 누수가 심각해지면 디스크 페이징(메모리가 부족해 디스크를 사용)과 같은 성능 저하나, OutOfMemoryError가 발생할 수 있다.
디스크 페이징(Disk Paging)이란?
- 시스템 메모리가 부족할 때, 하드디스크를 임시 메모리처럼 사용하는 과정을 말한다.
- 메모리를 많이 차지하는 불필요한 객체가 계속 쌓이면 디스크 페이징이 잦아져서 성능이 크게 저하될 수 있다.
스택(Stack)을 구현한 코드를 살펴보면 문제점을 알 수 있다.
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size]; // ❗ 메모리 누수 위험
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
pop()으로 배열의 끝 원소를 꺼냈지만, 배열의 해당 칸에는 여전히 그 객체 참조가 남아 있다.public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
null 처리리를 해서 참조를 해제게되면 가비지 컬렉터가 객체를 회수할 수 있게 된다.
- 직접 메모리를 관리하는 클래스가 뭐지라고 생각할 수 있다. 직접 메모리 관리를 한다는 것은 내부적으로 배열이나 자료구조를 써서 객체 참조를 수동으로 관리한다는 뜻이다.
- 자바의 표준 컬렉션들은 자동으로 필요 없는 참조를 제거하거나 가비지 컬렉터가 스코프를 알기 때문에 문제가 없다.
- 직접 배열을 사용하거나(스택, 큐 등), 풀을 만들어 재사용하는 경우에 객체가 언제 필요 없어지는지를 개발자가 명확히 알려주지 않으면 가비지 컬렉터가 알 수 없어 메모리 누수가 발생할 가능성이 커진다는 말이다.
“모든 객체 참조를 사용 후 즉시 null로 설정하면 되지 않을까?”
책을 읽으면서 처음 읽을 때 나도 한 생각이다. 하지만 이렇게 코드를 작성하게되면 코드가 지저분해진다. 매번 null을 대입하느라 가독성이 떨어지게되고, 가비지 컬렉터가 크게 좋아지는 것도 아니라고 한다.
예를 들어서 아래의 코드가 메서드 곳곳마다 null을 대입한다고 하면, 보기만 해도 왜 null 처리를 하는건지 지저분해보이는 것 같다.
public void someMethod() {
Object obj = new Object();
// === 객체 사용 ===
obj = null; // 사용 끝났으니 null 처리
// === 또 다른 객체 생성 ===
Object another = new Object();
// ... 객체 사용 ...
another = null; // 사용 끝났으니 null 처리
}
스코프를 벗어나는 등의 자연스럽게 참조가 사라지는 구조를 선호한다.
- 지역 변수는 해당 블록이 끝나면 스택에서 사라지게 된다. 이런 구조가바로 null을 대입하지 않더라도 블록 스코프를 벗어나 참조가 사라졌기 때문에 가비지 컬렉터가 자동으로 회수 할 수 있는 구조이다.(스코프에서 밀어내기)
그러나 스택처럼 객체 참조를 직접 관리하는 구조라면 예외적으로 null 처리가 필요하다는 기 때문에 null 처리를 예외적인 상황에서만 하자라고 말하는 것이다.
Stack처럼 배열로 원소를 관리하는 클래스참고로 실제로 Java.util에 구현되어 있는 Stack은 아래와 같은 방식을 통해서 null 처리를 수행하고 있다.
