-
이펙티브 자바 Item7 - 다 쓴 객체의 참조를 해제하라언어/Effective Java 2024. 5. 28. 11:29
자바에서 사용하지 않는 객체의 경우 GC에서 알아서 회수를 해주며, 메모리 관리를 해준다.
하지만 GC가 메모리 관리를 해준다고 완전 신경을 꺼야하는 건 아니다!
GC에서는 특정 상황에서 메모리 누구가 발생하고, 우리는 그러한 상황을 인지하고 대응해야한다.
메모리 누수는 겉으로 잘 드러나지 않아 수년간 잠복하는 사례도 존재한다.
철저한 코드리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도한다.
따라서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.
메모리 누수가 발생하는 경우
public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_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()하는 경우 --size하는 부분을 보면, 꺼낸 데이터를 삭제하는게 아니라 단순히 인덱스만 조정한다. (실제 객체 참조값을 스택이 가지고 있다)
- 이와 같은 경우 당연히 GC가 작동하지 못한다.
- 메모리 누수를 관리하지 않을 경우 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 성능이 저하된다.
해결방법
public Object pop() { if (size == 0) throw new EmptyStackException(); Object popObject = elements[size]; elements[size--] = null; // 참조 해제 return popObject; }
- 다 쓴 참조를 null로 변경해주자.
- 다만 NullpointException이 발생할 수 있음으로 잘 관리해주자
null로 항상 바꿔야할까?
사용이 끝난 객체 참조를 null로 변경하는 것은 예외적인 경우에만 사용하는 것이 좋다.
다 쓴 객체 참조를 해제하는 가장 좋은 방법은 해당 객체 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.
null로 변경하는 메모리 관리는 자신의 메모리를 직접 관리하는 클래스를 사용할 때 주의 해서 사용하자
메모리 누수를 발생시키는 3개의 주범
1. Stack과 같이 자신의 메모리를 직접 관리하는 클래스
2. 캐시 메모리
- 객체 참조를 캐시에 넣고 정리를 안해주면 자원은 쌓이고, 캐시의 역할을 못하게 된다.
- 해결방법
- WeakHashMap
- 캐시에 넣어놓고 더이상 쓰지 않아도 계속 들어있을 수 있다. 일반적인 HashMap은 사용여부에 관계없이 Key와 Value 쌍을 지우지 않는다. WeakHashMap은 특정 key값이 더이상 사용되지 않는다고 판단되면 지운다.
- 캐시의 경우 시간이 지남에 따라 사용되지 않으면 그 가치를 떨어뜨리는 방법이다.
- LinkedHashMap.removeEldestEntry -> 사용된지 가장 오래된 엔트리 삭제
- WeakHashMap
3. 리스너, 콜백
- 리스너와 콜백을 등록만하고 해지 안하면 메모리 낭비이다.
- 약한 참조를 넣어서 가비지 컬렉터의 수거 대상이 되도록 하자.
public interface DatabaseManager{ ResultSet execute(final String query); }
- 위 인터페이스는 콜백 메서드에 의해 작동하며, 콜백 메서드를 정의하는 방법에 따라 GC 대상의 여부가 결정됨
public class CustomDatabaseManager implements DatabaseManager{ // 외부에서 정의되서 참조된다. 클라이언트가 더 이상 인터페이스를 사용하지 않는다는게 // gc 대상이 됨을 의미하지 않는다. private Connection con; //생략 public void disconnect(){ this.con = null; } public ResultSet execute(final String query){ DBconnection dbCon = con.getDBConnection(); return ResultSet.createResultSet(dbCon); } } public void memeoryLeak() throws InterruptedException{ Connection con = createConnection(); DatabaseManager dbManager = new CustomDatabaseManager(con); dbManager.execute(); con = null // 참조 해제 시도 //con이 null이어도, CustomDababaseManager안에 con 참조가 남아있다. //따라서 의존 관계 안에서도 참조를 해제 해야한다. ((CustomDatabaseManager)) dbManager.disconnect(); }
다른 방식으로 여기에 WeakHashMap을 사용할 수 있다.
public class CacheDatabaseManager implements DatabaseManager{ private static final String CAHE_KEY = "Connection Interface"; private WeakHashMap<Connection,Object> cons = new WeakHashMap<>(); public CacheDatabaseManager(Connection con) {this.cons.put(connections,CACHE_KEY);} public Connection getConnection(){ if(cons.size() <=0){ throw new RuntimeException(); } return (Connection).cons.get(CACHE_KEY); } //생략 } public void WeakHasTest() { Connection con = createConnection(); DatabaseManager dbm = new CacheDatabaseManager(con); con = null; //참조 해제 //weak Hash Map에 GC작동 }
참고자료
https://javabom.tistory.com/14
https://seongwon.dev/Java/20220304-%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-7/
'언어 > Effective Java' 카테고리의 다른 글
이펙티브 자바 Item 11 - equals를 재정의하려거든 hashCode도 재정의하라 (0) 2024.05.28 이펙티브 자바 Item 9 - try-finally 대신 try-with-resources를 사용하라 (0) 2024.05.28 이펙티브 자바 Item6 - 불필요한 객체 생성을 피하라 (0) 2024.05.27 이펙티브 자바 Item5 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2024.05.27 이펙티브 자바 Item3 - private 생성자나 열거타입으로 싱글톤임을 보장하라 (0) 2024.05.27