메모리 관리
메모리를 직접 관리하는 언어인 C, C++과는 달리 자바는 가비지 컬렉터가 다 쓴 객체를 알아서 회수해준다. 하지만 가비지 컬렉터가 다 쓴 객체를 인식 못하는 경우도 있다.
메모리 누수 - Stack
다음은 스택을 간단한 코드이다. 여기서 메모리 누수가 일어나는 부분을 찾아보자.
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITAL_CAPACITY];
}
public void push(Object e) {
elements[size++] = e;
}
public void pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
}
[해결] pop() 메소드에서 메모리 누수가 발생한다. 정확히 말하면 elements[--size]를 하면 이 객체를 프로그램에서 사용하지 않음에도 불구하고 elements라는 배열이 객체의 참조를 여전히 가지고 있기 때문에 가비지 컬렉터가 동작하지 않는다.
이를 해결하는 가장 간단한 방법은 해당 참조를 다 썼을 때 null 처리를 하는 것이다.
public void pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] == null; // 다 쓴 참조 해제
return result;
}
이렇게 null처리를 하면 이후에 null 처리한 참조를 실수로 사용할 때 프로그램은 즉시 NPE을 던지며 종료된다. 그렇다면 모든 객체를 쓰자마자 null처리를 해야할까?
=> Null 처리는 자기 메모리를 직접 관리하는 클래스인 경우에만 처리해주면 된다. 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이며 이 범위가 최소가 되게 정의했다면 이는 자연스럽게 이뤄진다.
메모리 누수 - 캐시
캐시 역시 메모리 누수를 일으키는 주범이다. 객체 참조를 캐시에 넣고 나서, 그 객체를 다 쓴 뒤로도 한참을 나두는 경우가 자주 있다. 해법은 다음과 같다.
- 캐시 외부에서 키(Key)를 참조하는 동안만 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만든다.
- 백그라운드 스레드를 활용한다.
- 새 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행한다.
=> LinkedHashMap.removeEldestEntry()
메모리 누수 - 리스너와 콜백
클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면, 뭔가 조치해주지 않는 한 콜백은 계속 쌓여갈 것이다. 이럴 때 콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다.
import java.util.logging.FileHandler;
public class FileMonitor {
private File file;
private Set<FileHandler> handlers = new HashSet<FileHandler>();
public FileMonitor(File file) {
this.file = file;
}
public void registerFileHandler(FileHandler fileHandler) {
this.handlers.add(fileHandler);
}
public void unregisterChangeHandler(FileHandler fileHandler) {
this.handlers.remove(fileHandler);
}
}
만약 fileHandler를 등록하고 필요 없을 때 handlers에서 제거해주지 않으면 FileMonitor 객체가 사라지거나, 애플리케이션이 종료 될 때까지 남아 있을 것이다.
public class Client {
File myFile = new File("...");
FileMonitor monitor = new FileMonitor(myFile);
public void sth() {
...
FileHandler myhandler = getChangeHandler();
monitor.registerFileHandler(myhandler);
}
}
이러한 것을 막기 위해 WeakHashMap과 같은 weak reference collection을 사용하는 것이 좋다.