어딘가(Array, List, Map, Set 등)에 직접 객체를 쌓아두는 공간이 있다는 공통점 존재
해결 방안
빼낼 때 직접 null 처리하는 방법
특정한 자료구조를 사용한는 방법
Weak Reference(WeakHashMap), LRU 등
객체를 넣거나 뺄 때 직접 관리하는 방법
Background thread를 사용해서 주기적으로 clean-up 하는 방법
참조 객체를 null 처리하는 일은 예외적인 경우이며 가장 좋은 방법은 유효 범위 밖으로 밀어내는 것이다.
예시 코드
스택의 예시
아래의 주석 표시된 pop()을 사용한다면 Object[]에 계속 쌓여 언젠간 OutOfMemoryError가 발생할 수 있다.
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;
}
public static void main(String[] args) {
Stack stack = new Stack();
for (String arg : args)
stack.push(arg);
while (true)
System.err.println(stack.pop());
}
}
<br>
### 캐시 (WeakHashMap)
- 주석 참고
- Key를 굳이 객체를 만들어 할당한 이유
* 가령 Integer를 key로 직접 사용한다면 객체 할당 해제가 되지 않는다.
* JVM 내부에 Constant pool 등에 cache되기 때문이다.
``` java
public class PostRepository {
private Map<CacheKey, Post> cache;
public PostRepository() {
this.cache = new WeakHashMap<>();
}
public Post getPostById(CacheKey key) {
if (cache.containsKey(key)) {
return cache.get(key);
} else {
// TODO DB에서 읽어오거나 REST API를 통해 읽어올 수 있습니다.
Post post = new Post();
cache.put(key, post);
return post;
}
}
public Map<CacheKey, Post> getCache() {
return cache;
}
}
class PostRepositoryTest {
@Test
void cache() throws InterruptedException {
PostRepository postRepository = new PostRepository();
// cache() 메소드가 끝날 때까지 key1은 참조가 가능하다. 즉, WeakHashMap을 사용했더라도 해제되지 않는다.
CacheKey key1 = new CacheKey(1);
postRepository.getPostById(key1);
assertFalse(postRepository.getCache().isEmpty());
// CacheKey 유효기간이 끝났다고 가정한
key1 = null;
// TODO run gc
System.out.println("run gc");
System.gc(); // 코드 실행 즉시 gc가 실행한다는 보장은 없지만 테스트는 works
System.out.println("wait");
Thread.sleep(3000L);
assertTrue(postRepository.getCache().isEmpty());
}
}
아이템 7. 다 쓴 객체 참조를 해제하라.
핵심 정리
예시 코드
스택의 예시
아래의 주석 표시된 pop()을 사용한다면 Object[]에 계속 쌓여 언젠간 OutOfMemoryError가 발생할 수 있다.
// public Object pop() { // if (size == 0) // throw new EmptyStackException(); // return elements[--size]; // }
}
Background Thread
리스너, 콜백
유저가 방을 나갈 때 user가 해지되지 않는 경우
완전 공략
완벽 공략 19. NullPointerException
Java 8 Optional을 활용해서 NPE를 최대한 피하자.
완벽 공략 20. WeakHashMap
더이상 사용하지 않는 객체를 GC할 때 자동으로 삭제해주는 Map
완벽 공략 21. ScheduledThreadPoolExecutor
Thread와 Runnable을 학습했다면 그 다음은 Executor.
Runtime.getRuntime().availableProcessors()
를 통해 CPU 개수를 알아낼 수 있다..get()
은 blocking call 이다..submit()
은 non-blocking call 이다.