Java Virtual Machine의 메모리 관리
전통적인 C언어나 C++언어와는 달리 자바에서는 메모리 관리는 프로그래머가 일일이 하지 않고 JVM에서 알아서 관리 해 줍니다.(물론 닷넷의 경우엔 자바와 비슷한 방법으로 하지만)
메모리 관리란 크게 보면 생성/소멸(삭제, 반납)의 과정으로 생각 할 수 있는데 우리가 자바 프로그램에서 Hello h = new Hello(“방가방가”) 라고 하든지 아님 int i=10; 이라고 할 때 메모리의 일정 부분을 할당 받게 되는 것입니다. 메모리에서 할당이 필요해지면 JVM은 변수에 대해 실제의 내용을 포인터로써 관리를 해주게 됩니다. JVM이 모든 객체의 레퍼런스를(참조) 카운트하고 관리하기 때문에 메모리 영역의 충돌이나 삭제 하지 않은 메모리 때문에 memory leak등이 발생 할 가능성이 거의 없어 졌습니다.
HOT SPOT이전의 JVM(JIT기반, JDK1.3 이전)에서는 메모리에 대한 간접 참조 방식을 이용하였는데 아시는 것 처럼 간접 방식이므로 실제 내용을 찾아 가기 위해서는 두번이상의 참조가 일어나게 됩니다. 물론 조금은 느려질 소지가 있었겠지요~ 이 경우엔 오히려 Garbage Collection시에는 참조만 지우면 되므로 오히려 간단했다고 볼 수 있습니다.
간접 참조란 힙에 객체의 내용이 있다면 중간에 그곳을 가리키는 메모리 영역이 있고(핸들 메모리) 스택에 있는 변수가 핸들 메모리를 참조해서 실제 힙의 주소를 얻은 후 힙의 실제 내용을 참조하게 되는 방식 입니다. 반면 직접 참조의 경우 스택에 있는 변수가 힙메모리의 실제 주소를 가지고 있어 직접 참조 하게 되는 방식 입니다. 그러므로 자주 참조되는 변수나 객체가 있다면 직접 참조 방식이 훨씬 뛰어난 성능을 보입니다.
반면에 가비지컬렉션의 경우(메모리를 지우거나 참조값을 바꾸는 경우) 직접 참조 방식에서는 값이 없어지거나 사라져야 하는 변수에 대해 일일이 포인터를 지워줘야 합니다. 반면 간접 참조 방식의 경우 핸들 메모리만 수정을 해주면 되므로 오히려 간단해 지는 것입니다.
Virtual Machine에서는 스택과 힙 메모리를 사용하며 스택에는 프로그램에서 사용하는 변수, 함수명등이 수행 순서에 맞게 적재 되어 있고 힙 메모리에는 실제 스택에 있는 변수나 함수의 내용이 적재 됩니다. 즉 스택에는 실제 값이 존재하는 곳의 포인터를 가지므로 크기가 크지 않으나 힙의 경우 사용량이 커지게 됩니다. 만약 힙 메모리를 다써버린 다면 java.lang.OutOfMemory 오류가 발생 할겁니다.
이번에는 메모리의 해제에 대해 보도록 하겠습니다. 아까 위에서 만든 객체 참조변수에 대해 h = null; 이라고 하는 경우나 어떤 메소드 안에 쓰인 변수가 메소드의 실행이 종료하게 되는 경우에 메모리에서 해제 될 것 입니다.
JVM의 힙메모리에는 프로그램 실행시 생성된 여러 값들이 존재 합니다. 가비컬렉터가 하는 일은 이러한 힙 메모리중 사용하지 않는 메모리를 반환 시키는 역할을 하는 것입니다. 위의 경우 처럼 h = null; 이라고 명시적으로 지정 하는 경우 객체 헤더에 존재하는 GC(Garbage Collector) 필드가 값이 설정 됩니다. 이렇게 지정 된 객체에 대해 우선적으로 메모리에서 삭제를 하게 됩니다.
반면 스코프를 벗어나는 경우 Local 변수는 다음과 같은 조건에 따라 삭제 여부를 판단하게 됩니다. 스택 메모리에 있는 변수 중 해당 객체에 대한 참조가 있는 경우 또는 객체가 메모리에 있다는 이야기는 다음에 있을 확률이 높다는 판단(이게 LRU방식 맞나여??)을 JVM에서 해(일반적으로 프로그래밍에서 사용된 객체의 95% 이상이 생성된 후 곧 삭제된다는 가정 하에 아직까지도 있는 객체이므로 필히 중요한 것 일꺼야 하는 기술을 Generation Copying Collection이라 합니다) 가급적 가비지 컬렉션을 수행 하지 않습니다. 또는 정적 혹은 현재 스코프의 객체가 해당 객체를 참고 하고 있는 경우, JVM내부에서 사용하는 네이티브 메소드에 사용되는 객체에 해당 하는 경우 등은 가비지 컬렉션에서 제외를 하며 기타의 경우엔 메모리에서 삭제를 하게 됩니다. 대부분의 프로그래밍 언어에서 사용된 객체는 거의 금방 소멸 된다는 것 때문에 Java에서는 메모리를 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역으로 나눕니다. Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성된지 오래된 객체를 저장하는 장소 입니다. 각 영역의 성격이 다른 만큼 GC의 방법 또한 다릅니다. 또한 Perm 영역이라고 불리는 곳이 있는데 이곳에는 클래스나 메소드의 실행 코드가 저장 됩니다. 그래서 GC가 일어나는 곳은 old, new 영역이며 Perm영역은 GC가 수행 되지 않습니다.(소스 코드가 있으므로)
JDK1.3 이상에서 탑재된 HOT SPOT VM의 경우(병목 현상이 일어날 확률이 높은 부분을 먼저 컴파일) 가비지 컬렉션에 따른 성능 저하를 막기 위해 Generation Copying Collection 기술을 이용하여 가비지 컬렉션 대상을 줄였으며 Incremetal Pauseless 기법을 이용하여 가비지컬렉션 대상을 여러 개의 작은 그룹으로 나누어 확실히 삭제 가능한 객체부터 지워나가는 방식을 채택 했습니다. Incremetal Pauseless 기법에서는 1차 가비지 컬렉션 대상은 객체의 헤더에 GC 필드가설정된 것이고 2차 대상은 위의 4가지 유형에 속하지 않은 것 입니다. 2차 대상의 경우 일단 CG필드를 설정 하고 다음번 가비지 컬렉션 주기에 삭제 되는 것 입니다.