언어/Effective Java

이펙티브 자바 Item7 - 다 쓴 객체의 참조를 해제하라

now0204 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 -> 사용된지 가장 오래된 엔트리 삭제

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

 

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

자바를 사용할 때 "GC"에 의존하여 메모리 자원 관리에 소홀해서는 안된다. GC 의 손이 닿지 않는 자원이 생기지 않도록 주의해야한다. GC 관련 포스팅은> https://javabom.tistory.com/7 Memory Leak 예시 다

javabom.tistory.com

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] Item7. 다 쓴 객체 참조를 해체하라

이 글은 Effective Java 3/E의 내용을 요약한 글입니다. 자세한 내용은 책을 참고하시기 바랍니다. 자바에서는 사용하지 않는 객체의 경우 GC(Garbage Collection)에서 알아서 회수를 해주며 메모리 관리를

seongwon.dev