본문 바로가기

Reading Record/이펙티브자바

아이템 [7] - 다 쓴 객체 참조를 해제하라

자바를 사용할 때 "GC"에 의존하여 메모리 자원 관리에 소홀해서는 안된다. GC 의 손이 닿지 않는 자원이 생기지 않도록 주의해야한다. 

GC 관련 포스팅은>  https://javabom.tistory.com/7

 

Memory Leak 예시

다 쓴 참조(obselete reference)

더이상 사용하지 않지만 참조되어있어 수거되지 않음. 뿐만 아니라 그 객체가 참조하고 있는 또 다른 객체까지 수거되지 않기 때문에 큰 문제가 발생할 수 있음.

 

메모리 누수 케이스

1. 자기 자신의 자원을 관리하는 클래스

이펙티브 자바 책에서는 Stack 을 예시로 든다. Stack이 자원을 pop 하기만 한다고 GC 대상이 되지는 않는다. 일반적으로 구현하는 pop 의 로직에는 stack size 를 -1 해줄 뿐이기 때문이다. 

해결방법: null 처리 

주의점: 무분별한 Null 처리는 코드를 지저분하고 복잡하게 만들 수 있다.

 

2. 캐시

캐시에 담아놓기만 하고 정리를 해주지 않는다면? 빠른 속도를 위해 사용하는 캐시에 자원이 계속 쌓이게 되면 결국 이 캐시는 캐시의 역할을 하지 못하게 된다. 

해결방법:

- WeakHashMap 

캐시에 넣어놓고 더이상 쓰지 않아도 계속 들어있을 수 있다. 일반적인 HashMap 은 사용여부에 관계없이 Key - Value 쌍을 지우지 않는다. WeakHashMap 은 특정 key 값이 더이상 사용되지 않는다고 판단되면 해당 Key - Value 쌍을 삭제해준다.

- 캐시의 경우 시간이 지남에 따라 사용되지 않으면 그 가치를 떨어트리는 방법을 사용한다.

(LinkedHashMap.removeEldestEntry -> 사용된지 가장 오래된 엔트리 삭제) 

 

3. 리스너, 콜백

리스너와 콜백을 등록만하고 해지를 안하면 메모리 낭비.

해결방법: 약한 참조로 넣어서 가비지컬렉터의 수거 대상이 되도록 하자.

 

 

콜백 메서드 예시) 

query 를 받아 실행하여 ResultSet 을 반환하는 인터페이스가 있다. 이 인터페이스는 "콜백" 메서드에 의해 작동하며 이 콜백 메서드를 정의하는 방법에 따라 GC 대상의 여부가 결정된다. 

먼저, 필드에 콜백을 할당하는 방법이다.

콜백 인터페이스는 Connection 이다. Connection 인터페이스는 클라이언트 측에서 정의되며 이 CustomDatabaseManager 에 할당된다.

클라이언트는 Connection 을 정의하여 CustomDatabaseManager의 생성자 매개변수로 전달해주었다. 그리고 이 콜백 메서드는 한번 사용되면 다신 필요 없다고 가정하자.

단순하게 생각하면 connection 에 null 을 할당할 수 있을 것이다. 그 뒤 GC 가 수거해가기를 기다려보자.(gc 가 동작할거라고 100퍼센트 확신은 못하겠지만) 이 테스트는 아무리 여러번 돌려봐도 성공이다. 즉, databaseManager 의 connection 은 다신 사용되지 않아도 gc 의 대상이 되지 못한다. 

디버깅을 걸어보면 클라이언트에서 할당한 null 은 databaseManager 에 전혀 영향을 주지 않는다는 것을 볼 수 있다.

 

이 경우에 Memeory leak 을 방지하는 방법은

databaseManager 에게 직접 connection에 null 을 할당하라고 명령하는 것이다.

 

또 다른 방식으로 콜백을 등록할 수 있다. 위에서 잠시 언급된 WeakHashMap 에 저장하는 것이다.

 

WeakHashMap 을 사용하면 클라이언트에서 null 을 할당해도 gc 의 대상이 되도록 할 수 있다.