자바 프로그램에서 생성된 객체는 '힙(Heap)'이라는 메모리 영역에 생성된다. 힙 영역에 생성된 객체는 'GC(Garbage Collector)'의 관리를 받으며 사용되지 않는 경우 GC에 의해 다시 회수된다.
1. on-heap 메모리
우리가 일반적으로 알고 있는 '힙(Heap)' 메모리 영역이 on-heap 영역에 해당된다. 자바 프로그램에서 'new' 연산 등을 이용해 생성된 객체는 힙 영역에 생성된다. C, C++의 경우라면 객체의 생성과 소멸을 프로그래머가 관리해줘야한다. 반면 자바의 경우 GC(Garbage Collection) 알고리즘에 의해 자연스럽게 회수된다. 프로그래머는 객체의 메모리 회수, 메모리 릭(Memory Leak) 등을 걱정하지 않아도 된다는 장점이 있다.
하지만 GC 알고리즘 동작시 'Stop the world' 상태로 돌입하는 경우 성능저하가 발생할 수 있다. 즉, 사용되지 않는 메모리가 있는 경우 다시 회수해야하는데, 이 회수 판단을 할 때 잠깐 프로그램의 동작을 멈춰야하는 경우가 발생한다. 특히 커다란 사이즈의 객체를 생성하거나 다량의 객체가 생성되는 경우에 이런 현상이 자주 발생한다. 이 경우 GC 알고리즘이 돌면서 애플리케이션이 버벅일 가능성이 높다. (그래서 자바 애플리케이션의 튜닝에서 GC 튜닝이 차지하는 비중이 적지 않다.)
JVM의 버전이 높아지면서 좀 더 똑똑하고 실행환경에 맞게 알아서 작동하는 GC 알고리즘이 나오고 있지만 최대한 GC가 발생되지 않도록 주의해야하는 점은 변하지 않는다.
2. off-heap 메모리
off-heap이라는 용어는 '힙 메모리 밖에 저장한다'라는 의미를 가지고 있다. 힙 메모리 밖에 객체를 저장하기 때문에 GC의 영향을 받지 않는다. 때문에 GC가 유발하는 성능 문제에서 자유롭다. 이 말은 GC가하는 메모리 회수 동작을 애플리케이션 작성자가 직접 해야하는 것을 의미하기도 한다.
자바에서는 NIO를 통해 off-heap 스토어의 사용을 제공하고 있다. DirectByteBuffer를 이용해 할당받은 버퍼 공간은 GC의 대상이 되지 않는 off-heap 스토어에 저장된다. 문제는 위에서 언급했던 것처럼 GC가 하는 일을 애플리케이션에서 직접해줘야한다는 점이다. 때문에 EHCache, Terrcotta BigMemory 같은 off-heap 스토어를 이용한 캐시 라이브러리를 사용하는 것이 좋다. 라이브러리 단에서 메모리 관리를 해준다.
Off-heap 영역에 객체를 저장하기 위해서는 객체를 직렬화(Serialize) 해야한다. 이런저런 이유로 Allocation / Deallocation 비용이 일반 버퍼에 비해 높다. 따라서 off-heap 영역의 장점을 최대한 이용하기 위해서는 사이즈가 크고(Large), 오랫동안 메모리에 살아있는 (Long-lived) 객체이면서 시스템 네이티브 I/O 연산(Mmap 같은..) 의 대상이 되는 객체를 사용하는 것이 좋다.
Off-heap이 무조건 좋은 것은 아니기 때문에 off-heap 메모리를 사용하는 것이 성능상 이득이 됨을 확인하고 사용해야 한다.
댓글