자바 가비지 컬렉터 알고리즘
자바의 가비지 컬렉터 알고리즘은 Reference Counting을 하지 않아서 객체간에 Cyclic Reference가 생겨도 가비지 컬렉팅되지 않는 완벽한 방법이지만 일정 시간의 가비지 컬렉팅 시간을 필요로 한다는 단점을 가지고 있다. 즉, 자바 힙의 모든 객체는 현재 사용중인 객체와 사용되고 있지 않은 객체, 2가지로 나뉘어 지며 사용중인 객체에서도 사실상 사용되지 않는 객체가 있을 수 있으며 이는 메모리 누수에 해당한다.
· 현재 사용중인 객체?
· 아래와 같은 루트참조(객체가 아님)들로부터 직간접적으로 참조가 되는 (Reachable한) 모든 객체를 의미
· 나머지 객체는 모두 쓰레기 객체이며 요즘은 JVM이 이러한 쓰레기 객체를 완벽하게 수거하므로 이 단계에서의 메모리 누수는 없다.
· 루트참조?
· Static 변수에 의한 객체 참조
· 모든 현재 자바 스레드 스택내의 지역변수, 매개변수에 의한 객체 참조
· JNI프로그램에 의해 동적으로 만들어지고 제거되는 JNI Global 객체 참조
· 좀더 직관적으로 풀어 설명하자면 가비지 컬렉터 알고리즘에서 현재 사용하는 객체 중에서 현재 이후 참조되어 사용될 가능성이 있는 모든 객체를 의미한다.
· 객체는 직접 참조 되지 않고 항상 변수를 통하여 참조가 가능하다.
· static변수는 프로그램 어디서든 사용할 수 있으므로 static변수에 의해 참조되는 객체와 그 객체로부터 직간접적으로 참조되는 모든 객체는 언제든 사용할 수 있는 객체라서 사용중인 객체이다.
· 자바에서 현재 실행중인 모든 메소드내에 선언된 지역 변수와 매개변수에 의해 참조되는 객체와 그 객체로부터 직간접적으로 참조되는 모든 객체는 참조되어 사용될 가능성이 있으며, Caller메소드로 return된 이후에는 caller메소드에서 참조하고 있는 지역변수 매개변수에 의해 참조되는 객체와 그 객체로부터 직간접적으로 참조되는 모든 객체 또한 사용될 가능성이 있다.
· JNI네이티브 C함수내에서도 JNI함수를 사용하여 자바 객체를 생성할 수 있다. 이때 생성된 자바 객체에 대한 참조를 int값 등으로 변환시켜 C함수내의 지역 변수, 매개변수, 전역 변수로 참조하더라도 이는 JVM의 영역을 벗어나는 것으로서 쓰레기를 수거할 수 없게 되기 대문에 C변수를 통하여 지속적으로 자바 객체에 접근할 수 있도록 JNI C함수를 호출하여 JNI Global reference로 JVM내에 등록시킬 수 있다.
· 따라서, 자바의 쓰레기 객체들을 확인할 때 쓰레기인지 아닌지는 기계적인 검출이 사실상 곤란하여 툴의 도움을 받을 수 있을지라도 프로그래머가 로직을 이해하여 파악하여야 한다.- 그렇지 않으면 어떠한 방법으로도 참조 할 수 있는 수단이 없어서 확실하게 쓰레기 객체라는 것을 의미한다.
메모리 누수 검출을 위한 개발 도구 사용법
· Optimizeit Profiler 도구로 보면 전체 루트 참조 목록을 볼 수 있고 이로부터 참조되는 객체들을 모두 따라갈 수 있다.
· 메모리 누구 검출을 하기 위해서는 메모리 누구 원인이 되는 원인의 대상 객체를 참조하는 객체들을 따라가서 일단 루트까지 따라가야 한다. 루트가 아닌 일반 객체에서 그래프가 끝나는 경우가 많은데 이는 객체참조 그래프에서 Cycle이 생성되어 끝난 것이며 이러한 객체는 루트 참조가 아니므로 메모리누수와 관련이 없어서 무시하면 된다.
메모리 누수 원인과 처방 그리고 개발자들의 오해
· 메모리 누수 원인을 파악할 때 개발자 들이 흔히 잘못하는 실수는 객체와 클래스의 차이를 명확히 구분하는 것이다. 쓰레기 수집은 객체들간의 참조 관계로부터 파악되는 것이므로 클래스 구조나 패키지 구조와는 별 관계가 없다. 또한 이와 같은 메모리 누수가 GUI어플리케이션과 같이 객체들간의 상호 참조가 많은 경우에는 하나의 객체참조를 null처리 해주지 않은 실수가 전체창, 혹은 전체 문서의 메모리 누수로 이어지는 경우가 많다.
· 뿐만 아니라 이벤트 리스너 등록 등으로 인하여 UI컨트롤에서 UI컨트롤이 아닌 객체로의 참조가 남아서 메모리 누수가 더 확대될 수 있다. 비슷한 현상으로 Document, View 구조에서 Document 구조 또한 부모-자식 Element간에 상호 참조되어 전체가 하나의 군집을 이루고 View구조의 각 View객체들이 Document를 참조하므로 이들 중 1개의 객체라도 그 참조가 남아 있으면 전체 Document객체와 이로부터 직간접적으로 참조되는 모든 객체들의 메모리 누수로 이어진다.
· Static변수는 이와 같은 메모리 누수의 원인이 되는 경우가 많으므로 굳이 static변수를 써야하는 상황이 아니라면 인스턴스 변수를 사용해야한다.
· GUI어플리케이션의 경우 창 객체 참조 문서나 객체 참조를 통하여 이러한 인스턴스변수를 접근하도록 하는 것이다. 만약 그 static변수가 필히 사용될 필요가 있다면 참조 경로상의 null을 대입을 통하여 static변수로부터 객체에 이르는 모든 가능한 참조 경로를 끊어 주어야 한다.
· GUI어플리케이션에서 백그라운드 스레드를 사용하는 경우 문제가 될 수 있다. 백그라운드 스레드가 실행은 되지 않더라도 종료되지 않고 waiting하게 되면 스레드 스택이 존재하고 있던 지역변수, 매개변수가 살아 있어서 이로부터 직간접 적으로 참조되는 모든 객체는 사용중이 객체가 되기 때문에 메모리 누수 요인이 될 수 있다.
· 즉, GUI어플리케이션에서 메모리 누수의 원인을 조사하는데 있어서 지역변수와 매개변수에 대해서는 백그라운드 스레드만 조사해보면 된다는 것이다.
· 마지막으로 JNI전역 참조에 의한 메모리 누수는 2가지 요인이 있다.
· 네이티브 코드쪽에서 JNI전역 참조를 해제하지 않은 버그로 인하여 메모리 누수발생
· 네이티브 자원을 반환하는 dispose(), close()메소드 들을 제때에 호출하지 않아서 생기는 메모리 누수 발생
캐쉬와 관련된 메모리 누수 강참조
· 강참조만으로 이루어진 해쉬맵에서는 메모리 부족상태를 파악하기 위해서 캐쉬 엔트리에 대한 참조를 적절히 제거해주면 메모리 누수가 발생하지 않을 수 있으나 메모리 부족 상태를 파악하기 어렵고 추가 작업을 해야 하므로 이와 같이 처리하지 않는 경우가 대부분이다. 이러한 용도로 사용할 수 있는 것이 SoftReference, WeakReference라는 것이 있다. 이를 사용하면 이러한 참조 객체에 의해 참조되는 객체는 메모리가 부족해질때 JVM에 의해 모두 null참조로 바뀌고 쓰레기가 수집된다.
· 하지만 소프트 참조와 약참조 또한 자바의 힙 크기가 부족해 질경우에만 쓰레기 수집을 하므로 메모리 누수는 사실상 없을 지라도 메모리 누수와 유사한 성을을 내는 나쁜효과를 초래할 수 있다.
자바힙 메모리 관리와 시스템 메모리 관리와의 관계
· 일반적으로 윈도우의 메모리사용량은 작업 관리자에서 확인이 가능하며 C프로그램보다 Java프로그램이 훨씬 많은 메모리를 사용하는 것을 확인할 수 있다.
· 그러나 자바에서는 메모리 사용량이 MS오피스와 같은 네이티브 어플리케이션만큼 메모리를 많이 사용한다 해도 그 차이만큼 실제 메모리를 사용하는 것이 아니다.
· 할당된 메모리 일부를 자바 힙이 사용하고 이 힙의 일부는 아직 자바 객체가 할당 되지 않아서 실제 메모리로 할당될 가능성이 적고 프로그램간 공유되는 메모리의 크기는 잘 측정되지 않는다.