2017년 4월 20일 목요일

effective java 규칙 6. 유효기간이 지난 객체 참조는 폐기하라

생애주기 동안 레퍼런스 참조가 없어진 변수는 gc 에 의해 제거된다.
예를 들면
String a = new String(“abc”);
에서 new String(“abc”); 으로 생성된 객체는 메모리의 heap 에 위치하게 된다.
생성된 객체는 a 라는 변수에 의해 참조되어지고 있기 때문에 gc 의 대상이 아니지만
a = null;  로 참조를 끊어버리면 new String(“abc”) 라는 객체는 gc 의 대상이 되어 제거된다.

그러나 자체적으로 메모리를 관리하는 코드가 있을때 의도치 않게 객체를 계속 보유하게 되는 경우가 있다.

public class StackTest {

// 저장공간 풀 레퍼런스 변수
private Object[] elements;
private int pointer = 0;

public StackTest() {
// 저장공간 풀 생성
elements = new Object[10];
}

public void push(Object o) {
elements[pointer++] = o;
}

public Object pop() {
return elements[--pointer];
}
}

위 코드와 같이 StackTest 생성자에는 저장공간을 관리하는 즉 자체적으로 메모리를 관리하는 elements 라는 변수가 있다.
위 객체를 stack 자료구조 의도로 사용했을때
pop() 메소드를 사용하면 마지막에 담긴 object 를 반환되어야 하고 반환된 객체는 이제 StackTest elements 에서 접근할 수가 없다.
하지만 gc는 여전히 해당 객체가 elements 에 담겨있으므로 (논리적으로 접근이 불가능하지만 elements 에 담겨져 있긴하다.)
gc의 대상으로 선정하지 않는다. 이문제를 해결하려면 pop 메소드 호출시 
element[pointer] == null; // pointer 위치의 객체 ‘참조' 값을 null 처리 함
이라는 만기참조 제거 를 명시적으로 해야 gc 는 방금 pop 에 의해 반환된 객체가 더이상 참조되어 지지 않는 것을 알게되 gc의 대상으로 선정한다.
이렇게 자체적으로 관리하는 메모리가 있는 클래스는 메모리의 누수가 발생하지 않도록 주의해야하며
예외적인 조치를 해야한다 (null 할당)

메모리 누수가 발생 할 가능성이 있는 또 다른 경우는
cache 를 구현 할 때이다.
객체참조를 cache 로 사용할 Map 등의 자료형에 넣어 놓고 잊어버리게 되면 - cache 메모리 관리 로직에서 빠지는등 - cache 의 생애주기 동안 map 에 담아놓은 객체는 gc 의 대상이 되지 않는다.
이런 경우를 해결할 수 있는 첫번째 방법은
캐시 바깥에서 키에 대한 참조가 만기 참조가 되는 순간 캐시에서 자동으로 삭제 되게 하는 전략으로
WeakHashMap 을 사용하는 방법이다.

public Map weakMap = new java.util.WeakHashMap();

/*
key 값을 "" 리터럴 스트링으로 생성하거나 기본자료형으로 생성하면
해당 값은 상수풀에 등록이 되어 있게 되고 gc 에 의해 지워지지 않기 때문에
WeakHashMap 의 만기참조시 삭제하는 전략을 사용할 수 없다
*/
// 외부에서 키를 관리한다고 가정한 변수
// private static String key = "a";
private static String key = new String("k"); // or // Integer key = new Integer(1);

public static void main(String[] args) throws InterruptedException {

WeakHashMapTest test = new WeakHashMapTest();

test.weakMap.put(key, 123);

test.someMethod();
key = null;
test.someMethod();

}

public void someMethod() throws InterruptedException {

for(int i =0 ; i<3; i++) {
Thread.sleep(500);
System.gc();
System.out.println(weakMap);
}

}
실행 결과
{k=123}
{k=123}
{k=123}
// 키의 참조값이 제거된 후 cache 에서 해당 키값을 가진 항목이 제거됨
{}
{}
{}

그리고 메모리 누수가 흔히 발견되는 또 한 곳은 callback 함수를 입력받는 서비스로 인해 발생한다.
클라이언트 코드에서 인자로 받은 callback 함수를 명시적으로 제거하지 않을 경우 메모리에 점유된 상태가 된다. 
이런 경우에도 콜백에 대한 약한 참조 (Weak reference)를 사용 하면 메모리 누수 문제를 해결 할 수 있다.
WeakHashMap의 키로 저장하는 것이 그 예이다.

public class CallBackTest {

public static void main(String[] args) {
SomeClass sc = new SomeClass();


SomeClass.CB callback = new SomeClass.CB() {
@Override
public void cbMethod() {
System.out.println("i'm callback function !");
}
};

sc.someMethod(callback);

// 명시적 제거(callback = null;)를 하지 않을 경우 메모리 누수 발생

// 따라서 callback 을 담을 WeakHashMap 을 생성하고 callback 을 담는다. 이때 키에 해당하는 값은 변수에 담고
// someMethod의 사용이 끝나면 키를 제거한다
}

}

class SomeClass {

interface CB {
void cbMethod();
}

public void someMethod(CB callback) {
callback.cbMethod();

}
}


댓글 없음:

댓글 쓰기

Intelij 설정 및 plugin

1. preferences... (settings...) Appearance & Behavior > Appearance - Window Options        ✓   Show memory indicator Editor &...