일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- JPA
- springboot
- deleteById
- 데이터베이스
- @MockBean
- java
- Service Locator 패턴
- SQL 삽입 공격
- 트랜잭션
- Firebase
- restTemplate
- @ControllerAdvice
- NotEmpty
- @SpyBean
- @Valid
- 디자인 패턴
- Connection Pool
- Spring Boot
- 플라이웨이트
- Web
- FCM
- Effective Java
- NotBlank
- Service Locator
- Effetive Java
- multi module
- db
- 이펙티브 자바
- Proxy Patter
- Item04
- Today
- Total
NoTimeForDawdling
Item07 다 쓴 객체 참조를 해제하라 본문
GC는 다 쓴 객체를 알아서 회수해 가기 때문에 메모리 관리에 더 이상 신경 쓰지 않아도 된다고 오해할 수 있다. GC가 처리할 수 없는 자원이 생기지 않게 주의해야 한다.
메모리 직접 관리 예제
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
this.ensureCapacity();
this.elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return this.elements[--size]; // 메모리 누수의 원인, size만 작아질 뿐 elements의 크기는 그대로
}
private void ensureCapacity() {
if (this.elements.length == size) {
this.elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
이 코드에서 스택이 커졌다 줄어들었다 해도 스택에서 꺼내진 객체들은 GC가 회수하지 않는다. 왜냐하면 스택이 객체들의 다 쓴 참조를 여전히 갖고 있기 때문이다.
'활성 영역'의 범위는 size보다 작은 부분이고, size값보다 큰 부분의 값들은 필요 없이 메모리를 차지하고 있는 부분이다.
해결 방법으로는 해당 참조를 다 썼을 때 null 처리(참조 해제)하면 된다. 다음과 같이 수정하면 된다.
...
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object value = this.elements[--size];
this.elements[size] = null; // 다 쓴 값 null 처리
return value;
}
...
스택에서 꺼낼 때 꺼낸 객체의 자리를 null로 설정한다. null 처리한 참조를 실수로 사용하려 프로그램은 즉시 NullPointerException을 발생시킨다. 다 쓴 객체를 돌려주는 것보다 예외를 던져주는게 잘못된 일의 수행을 예방할 수 있어서 더 낫다.
하지만 이와 같이객체 참조를 null 처리하는 일은 예외적인 경우여야 한다. 모든 객체를 다 쓰자마자 일일이 null 처리하는데 혈안이 되기도 하고, 프로그램을 필요 이상으로 지저분하게 만들기 때문이다.
다 쓴 객체를 해제하는 가장 좋은 해결책은 그 참조를 가리키는 변수를 특정 범위(스코프) 안에서만 사용하는 것이다.(지역 변수는 그 영역이 넘어가면 GC의 대상이 된다.) 변수의 범위를 최소가 되게 정의하면 자연스럽게 이뤄지는 일이다.
그렇다면 객체의 null 처리는 언제 해야 할까? 메모리를 직접 관리할 때이다. Stack 클래스는 elements 배열로 자기 메모리를 직접 관리하기 때문에 배열의 활성 영역에 속한 원소들만 사용되고 비활성 영역은 쓰이지 않는걸 GC가 알 수 없다.
자기 메모리를 직접 관리하는 클래스는 프로그래머는 항시 메모리 누수에 주의해야 한다.
캐시
캐시 역시 메모리 누수를 일으키는 주범이다. 객체 참조를 캐시에 넣고, 이 사실을 잊은 채 그 객체를 다 쓴 후 비우지 않는다.
해법은 여러 가지가 있지만, 캐시의 키(key)에 대한 참조가 캐시 외부에서 필요 없어지면 해당 엔트리를 자동으로 제거해주는 WeakHashMap을 쓸 수 있다.
WeakHashMap은 특정 key 값이 더 이상 사용되지 않는다고 판단되면 그 값을 제거해준다.
리스너와 콜백
리스너와 콜백 또한 메모리 누수의 주범이다. 클라이언트가 콜백을 등록만 하고 해지하지 않는다면, 콜백은 계속 쌓일 것이다. 이때 콜백을 약한 참조(weak reference)로 저장하면 GC가 처리해 준다.
캐시와 같은 방법인 WeakHashMap에 키로 콜백을 저장하면 된다.
'Effective Java' 카테고리의 다른 글
Item06 불필요한 객체 생성을 피하라 (0) | 2021.05.27 |
---|---|
Item05 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2021.05.25 |
Item04 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2021.03.31 |
Item03 private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2021.03.09 |
Item02 생성자 매개변수가 많은 경우에 빌더 사용을 고려해 볼 것 (0) | 2021.03.04 |