1. Heap
Runtime Data Area는 JVM이 프로그램을 수행하기 위해 OS로부터 별도로 할당 받은 메모리 공간을 말하며 크게 5가지 영역으로 나눌 수 있다. 그 중 Heap
은 프로그램 상에서 런타임시 동적으로 할당하여 사용하는 영역이다. class를 이용해 instance를 생성하면 Heap에 저장된다. (ex. ClassA a = new ClassA();)
2. 메모리 누수(Memory Leak)
자바 프로그램의 실제 메모리 사용량은 시스템의 작업 관리자에서 나오는 메모리 사용량으로는 측정의 정확도가 매우 떨어진다. 따라서 개발자가 메모리 사용량을 측정하고 개선하기 위해서는 totalMemory() - freeMemory()를 통해 자바 어플리케이션의 메모리 사용량을 디버그 출력하거나, Optimizelt과 같은 개발도구로 측정해야 한다.
자바에서는 GC에 의해 메모리가 자동관리되지만 memory leak
는 발생할 수 있다. 자바의 GC 알고리즘은 루트 참조들로부터 직간접적으로 참조가 되는(reachable)되는 모든 객체를 현재 사용중인 객체라고 판단하고, 나머지는 쓰레기 객체라고 판단하여 JVM은 이러한 쓰레기 객체를 수거한다. 그러나 실제로 사용되지 않는 객체의 reference를 프로그램에서 잡고 있으면 그 객체는 GC에 의해 처리되지 않고 프로그램 내에서도 접근하여 사용될 수 없는 쓰레기 객체로서 메모리를 점유하게 된다. 이런 이유로 인해 메모리 누수 현상이 발생하게 되고, 창을 열고 닫을 때마다 그리고 문서를 열고 닫을 때마다 지속적으로 메모리가 증가되어 성능 저하 뿐만 아니라 결국에는 메모리 오류 발생
으로 프로그램이 종료되는 심각한 현상이 발생한다.
3. OutOfMemoryError
OutOfMemoryError
는 메모리 누수(Memory Leak) 상황이 발생했을 때 일어난다. 자바에서는 java.lang.OutOfMemoryError
예외(Exception)이 발생한다. 자바는 객체를 힙(Heap) 공간에 생성하고 이 생성위치에 대한 주소를 가지고 개체 참조(Object reference)하는 방식으로 사용한다. 객체를 생성하는 과정에서 힙 공간에 객체를 할당하기 위한 공간이 부족한 경우 발생한다.
이 경우 가비지 컬렉터는 새로운 객체를 생성할 수 있는 공간을 확보할 수 없다. 또는 GC를 수행하는데 과도한 시간이 소비되어 메모리를 사용하지 못하는 상황에서 발생할 수 있다. 기본 할당조건을 충족하지 못하는 경우 네이티브 라이브러리 코드에 의해 발생할 수 있다(예 : 스왑공간 부족).
이때 java.lang.OutOfMemoryError 예외가 발생하고 스택트레이스(StackTrace)가 출력된다.
4. OutOfMemoryError 예외 종류 및 원인
1. java.lang.OutOfMemoryError: Java heap space(Java 힙 공간)
- 원인 : 자바 힙 공간에 새로운 객체를 생성할 수 없는 경우에 발생한다. 이 오류가 반드시 메모리 누수를 의미하는 것은 아니다. 지정한 힙 크기(혹은 기본 크기)가 애플리케이션에 충분하지 않은 경우 발생한다. 혹은, 생명주기가 긴 애플리케이션의 경우 finalize를 과도하게 사용할 때 발생하기도 한다. JVM 옵션 설정을 하지 않는 경우 많이 발생한다.
- 조치 : JVM Option
JVM Option
- 힙 옵션
- Xms : jvm의 최소 힙 사이즈(ex) 256mb로 설정 -Xms256m)
- Xmx : jvm의 최대 힙 사이즈(ex) 1gb로 설정 -Xmx1G)
- Xss : 쓰레드마다 할당되는 stack size 설정(ex) 256kb로 설정 - Xss256k)
- Xmm : 힙의 young 영역의 초기 사이즈와 최대 사이즈를 설정. young은 힙의 일부이므로 힙 사이즈보다 작아야 한다. young 영역 사이즈가 너무 작으면 빈번하게 minor gc가 발생할 것이고 너무 크면 full gc만 일어나므로 적절히 설정해야 하며 Oracle의 추천은 힙 사이즈의 1/2 ~ 1/4로 설정하는 것이다. (ex) 256mb로 설정 - Xmn256m)
- Garbage Collector 옵션
- XX:+PrintGC : 가비지 콜렉션 시의 메시지를 출력한다.
- XX:+PrintGCDetails : 가비지 콜렉션 시의 상세한 메시지를 출력한다. (자바 1.4 이상)
- XX:+PrintGCTimeStamps : 가비지 콜렉션 시의 타임스탬프를 출력한다. (자바 1.4 이상)
- XX: +DisableExplicitGC : 강제로 GC를 발생시키는 것(ex) System.gc())을 무시한다.
- XX:NewRatio : New 영역과 Old 영역의 크기 비율을 정한다. 숫자가 클 수록 Old 영역의 비율이 높아진다. (-XX:NewRatio=1이면 1:1의 비율 -XX:NewRatio=2이면 New 영역 1, Old 영역 2의 비율을 갖게 된다.)
eclipse에서 JVM heap size를 늘려주는 방법
프로젝트 > properties > Run/Debug Settings
Arguments 탭에서 VM arguments 란에 원하는 heap size를 추가
totalMemory()를 사용하여 JVM heap size를 구할 수 있다.
| public class HeapSize{ public static void main(String[] args){ // get the jvm heap size long heapSize = Runtime.getRuntime().totalMemory(); System.out.println("Heap Size : " + heapSize); System.out.println("Heap Size(M) : " + heapSize / (1024 * 1024) + " MB"); } } | cs |
설정 전, 후 Heap Size를 찍어보면 다음과 같다.
설정 전 |
설정 후 |
Heap Size : 244MB |
Heap Size : 982MB |
2. java.lang.OutOfMemoryError: GC Overhead limit exceeded(GC 오버헤드 한도 초과)
- 원인 : 메모리가 부족하여 가비지 컬렉션이 이루어졌지만, 새로 확보된 메모리가 전체 메모리의 2% 미만일 때 발생한다. 한마디로 더 이상 가비지 컬렉션을 할 수 없을 정도로 메모리를 사용한다는 것이다.
- 조치
- 힙 크기를 늘린다.
- XX:-UseGCOverheadLimit 선택사항을 추가하여 java.lang.OutOfMemoryError가 발생하는 초과 오버헤드 GC 제한 명령을 해제할 수 있다.
3. java.lang.OutOfMemoryError: Requested array size exceeds VM limit(요청 배열 크기가 VM 제한을 초과한다.)
- 원인 : 애플리케이션(혹은 애플리케이션을 사용하는 API)이 힙 공간보다 큰 배열 할당을 시도하는 경우 발생한다. 예를 들어, 애플리케이션이 512MB 크기의 배열을 할당하려하지만 힙의 최대 크기가 256MB인 경우 요청 배열 크기가 VM 제한을 초과하면서 java.lang.OutOfMemoryError를 던진다.
- 힙 공간 사이즈가 너무 작은 경우
- 배열 요소를 계산하고 더하는 등 배열을 키우는 경우
- 원인 : 자바 클래스 메타데이터는 원시 메모리(=메타 공간)에 할당된다. 클래스 메타데이터가 할당될 메타 공간이 모두 소모되면 java.lang.OutOfMemoryError: Metaspace가 발생한다. 클래스 메타데이터가 할당될 공간은 MaxMetaSpaceSize 매개변수로 제한된다.
- 조치 : MaxMetaSpaceSize 값을 늘려 설정한다. MaxMetaSpaceSize는 자바 힙과 동일한 주소 공간에 할당된다. 자바 힙의 크기를 줄이면 더 많은 공간을 확보할 수 있다. 자바 힙 공간에 여유가 있는 경우에 고려해 볼 수 있다.
5. java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?
- 원인 : 자바 HotSpot VM 코드가 네이티브 힙 고갈이 되어 네이티브 힙에 할당할 수 없는 경우 발생한다. 이 메시지는 실패한 요청의 바이트 크기와 메모리 요청의 이유를 나타내며 대개의 경우 할당에 실패한 소스 모듈의 이름을 출력한다.
- 조치 : 네이티브 힙 고갈의 경우는 힙 메모리 로그 및 메모리 맵 정보를 분석하는 것이 유용하다. 이런 유형은 운영체제의 문제 유틸리티를 사용하여 문제를 진단할 수 있다.
6. java.lang.OutOfMemoryError: Compressed class space(압축된 클래스 공간)
- 원인 : 64비트 플랫폼에서 클래스 메타데이터 포인터는 32비트 오프셋(UseCompressedOops)으로 표현된다. 이 방식은 UseCompressedClassPointers(기본값 활성화, on)으로 제어할 수 잇으며 활성화되면 클래스 메타데이터가 사용할 수 있는 공간의 크기가 고정된다. UseCompressedClassPointers에 필요한 CompressedClassSpaceSize를 초과하면 java.lang.OutOfMemoryError: Compressed class space를 던진다.
- 조치 : CompressedClassSpaceSize 크기를 키우거나 UseCompressedClassPointers를 비활성화 시킨다.
7. java.lang.OutOfMemoryError: reason stack_trace_with_native method
- 원인 : 이 메시지가 출력되는 것은 원시 메서드에서부터 스택 트레이스가 출력되었다는 것을 의미하며, 네이티브 메서드에 할당 오류가 발생했음을 의미한다. 이 메시지가 위의 메시지들과 다른 점은 JVM 코드가 아니라 Java Native Interface(JNI) 또는 원시메서드에서 할당실패가 감지되었다는 것이다.
- 조치 : 이 예외가 발생하면 운영체제가 제공하는 유틸리티를 이용해서 문제점을 진단해야 한다.
참고