OOME 란?
메모리 부족(Out of memory, OOM)
- Java 애플리케이션 환경인 WAS 기반에서 수행된 서비스에 대해 JVM Heap 메모리 관련 오류
- JVM의 메모리가 부족하여 메모리 할당 실패로 발생한 에러로 종류/원인 다양(ex) Sun HotSpot JVM 3가지 메모리 공간 사용Java Heap = Young Generation + Old Generation 간주 가능
- Permanent Space → Java Heap의 하위 영역
- (cf) JVM은 사용 용도에 따라 메모리를 특정 몇가지 영역으로 구분하여 사용한다
- 대량 애플리케이션 구동으로 인해 시스템 메모리가 부족할 때 OS에 의해 애플리케이션 다운
- 커널은 OOM 킬러 매커니즘으로 하나 이상의 프로세스 종료를 통해 메모리 부족 상황 복구
- 우선순위에 의해 백그라운드 애플리케이션 먼저 닫힘
▣ JVM 구조
Java Heap
사용자가 생성한 Java Object 거주 공간
-Xms와 -Xmx Option에 의해 크기 결정
Permanent Space
Class에 대한 메타 정보 저장 공간.
-XX:PermSize=와 -XX:MaxPermSize= Option에 의해 크기 결정
Native Heap
Java Object가 아닌 Native Object 거주 공간
Native Heap의 크기는 JVM Option 로 지정 불가하며 OS 차원 결정
각 메모리 공간의 용도와 사용 방식이 틀리므로 OOME는 다양한 상황에서 발생한다.
OOME 의 정확한 원인 분석을 위해 각 메모리 공간의 특성을 이해하고 그에 맞는 해결책 찾기
OOM 발생 현상
(현상)
AL 거래 API 호출 후, 서버 OOM 발생
(결과)
서버에서 HTTP 500 발생
클라이언트측 HTTP 500 오류코드 메시지 전달
OOM 발생 시점
(시점)
개발 완료 후 사용자 테스트, 인수 테스트 단계에서 주로 발생
(원인)
개발 단계 단위 테스트 : 목적 기능에 대한 검증 위주
통합 테스트 단계 : 가동 초기 단계와 유사한 상황으로 테스트 진행 시 발생
(대응)
① JVM Option 값 조정
(cf) JAVA로 만들어진 애플리케이션은 JVM이 존재하는 OS/하드웨어 위에서 동작 가능 → 동작 보장 & 성능 보장 X
⇒ JVM 옵션을 통해 OS/HW 별 JVM 설정 변경을 통해 성능 보장
(종류)
⑴ Standard 옵션
- JVM 표준 옵션
- 벤더(JVM 제조사 : Oracle(Sun), IBM, HP) 와 독립적으로 동일한 옵션
- (ex) 32/64bit, 클라이언트-서버 모드 설정 등 환경 위주 설정
⑵ Non-Standard 옵션
- Java 버전, 벤더에 따라 유동적으로 존재하는 옵션
- 성능에 직접적 영향을 줌
- (ex) -X -XX로 시작ex) -X -XX로 시작
② Heap Dump 분석
- 애플리케이션 운영중 장애 또는 성능 문제 발생 시, 애플리케이션 형태를 스냅샷형태 파일 저장
- Dump 파일 분석을 통해 장애 시 애플리케이션의 상태 및 어떤 점이 문제인지 체크
- 과도한 메모리 사용 또는 메모리 누수를 유발하는 로직 체크
- 장애 발생시 사후 대응을 위해 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log 옵션 추가 권장
- (ex)
- VisualVM→ Thread 모니터링 , JVM Memory 모니터링 , Thread Dump 및 Heap Dump 생성
- → JDK 6 부터 제공되는 툴로 실행중인 JVM 에 대하여 자세한 정보를 GUI 로 제공
- Eclipse Mat→ H eap D ump 를 분석하여 각종 도표로 정리
- → Eclipse 기반의 Heap memory 분석 툴
OOM 발생 원인 - 1. Java Heap에서의 OOME
(1) JAVA Heap 사이즈가 작은 경우
① Heap 최대 크기가 Application 메모리 요구량에 비해 작게 설정된 경우
→ Memory Leak이 발생하지 않았음에도 OOME 발생시 Heap 크기 부족 의심 가능
→ -Xmx<Size> 옵션으로 Java Heap 최대 크기 증가 시키기
(예시)
Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space
Exception in thread main: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
- 1안 : Java Heap Space의 부족으로 Object 생성 실패
- 2안 : Java Heap의 최대 크기보다 큰 Array가 요청
(2) Memory Leak이 발생한 경우
- (대부분) ① Application 로직에 의한 Memory Leak→ Application 에서는 사용되지 않지만 GC에 의한 메모리 해제 불가로 OOME 발생
- (cf) Object 참조 관계가 복잡할때 실수로 참조되지 않은 객체를 지속적으로 참조 상태 유지
- ② JDK 버그 또는 WAS 버그에 의한 Memory Leak→ Application 로직 상 문제가 없는 경우 의심
- → 각 벤더가 제공하는 Bug Database를 통해 검색 가능
- (cf) JDK 또는 WAS에서 제공하는 라이브러리에서 로직 오류로 인한 메모리 누수 가능성
- Heap 사이즈와 무관하게 발생
- (cf) Heap 을 키우더라도 Memory Leak에 의해 Collection 되지 않는 Garbage 객체에 의한 메모리 소진
(3) finalize 메소드에 의한 Collection 지연
▷ 특정 Class에 finalize 메소드가 정의된 경우 해당 Class Type의 Object는 GC발생시 즉각 Collection 되지않음.
▷ Finalization Queue에 들어간 후 Finalizer에 의해 정리
▷ Finalizer는 Object의 finalize 메소드 실행 후 메모리 정리 작업 수행
▷ Finalizer의 작업시간이 길어지는 경우, 객체가 오랫동안 메모리 점유 → OOME 발생
(결론) finalize 메소드 사용 비권장
《Object Allocation Profiling》
(1) java Heap의 메모리 부족 문제의 정확한 분석을 위해 Object Allocation Profiling 수행하기
- 어떤 Object가 어느 개수만큼 생성되었으며 얼마나 많은 메모리를 차지하는지 분석
- HProf는 모든 JVM이 표준으로 제공하는 Profiler → 간단한 Object Allocation Profiling 기능 제공
(2) java -Xrunhprof:heap=sites [Main Class] 또는 하단 같이 doe(dump on exit) Option 비활성화로 시간순 Profiling 수행 가능
- java -Xrunhprof:heap=sites,doe=n [Main Class]…Control+Break
- 타 Console에서 " kill -3 [pid] " Java Process로 Signal을 보내 Dump생성법은 Thread Dump 참조
[HProf 을 이용한 Object Allocation Profiling 결과]
byte[] 유형 객체가 20% Heap 공간 사용
percent live alloc’ed stack classrank self accum bytes objs bytes objs trace name1 20.36% 20.36% 190060 16 190060 16 300000 byte[]2 14.92% 35.28% 139260 1059 139260 1059 300000 char[]3 5.27% 40.56% 49192 15 49192 15 300055 byte[]4 5.26% 45.82% 49112 14 49112 14 300066 byte[]5 4.32% 50.14% 40308 1226 40308 1226 300000 java.lang.String6 1.62% 51.75% 15092 438 15092 438 300000 java.util.HashMap$Entry7 0.79% 52.55% 7392 14 7392 14 300065 byte[]8 0.47% 53.01% 4360 16 4360 16 300016 char[]9 0.47% 53.48% 4352 34 4352 34 300032 char[]10 0.43% 53.90% 3968 32 3968 32 300028 char[]11 0.40% 54.30% 3716 8 3716 8 300000 java.util.HashMap$Entry[]12 0.40% 54.70% 3708 11 3708 11 300000 int[]
OOM 발생 원인 - 2. Permanent Space 에서의 OOME
(예시)
Exception in thread “main”: java.lang.OutOfMemoryError: Perm Gen space
(ex)
① 다수 JSP 파일을 로딩하는 웹애플리케이션
→ JSP 파일은 Servlet으로 변환.
→ 하나의 Servlet은 하나의 Java Class에 해당 = 많은 수의 Class 로딩
② ReflectionMechanism 을 사용해 동적으로 Class를 로딩하는 Framework
→ Spring 등 현대적인 Framework은 Reflection Meachanism을 통해 동적 Class 생성.
→ 개발자가 의도하지 않은 많은 수의Class 로딩가능
(해결)
Permanent Space 크기 키우기
-XX:PermSize=, -XX:MaxPermSize= Option 으로 Permanent Space 최소 크기, 최대 크기 지정
《 Class Loading 모니터링》
Permanent Space에 Loading되는 Class 목록 모니터링으로 OOME 발생 원인 간접 분석 가능
- verbose:gc: Loading되는 Class를 Standard Out으로 출력
- Platform MBean: JMX 표준으로 제공되는 ClassLoadingMXBean API를 이용하여 프로그래밍적으로 Class Loading 정보를 얻을 수 있다.
- JConsole: JConsole로 Class Loading 정보 조회 가능. JConsole은 JMX 클라이언트의 표준 샘플로 Platform MBean과 통신해서 Class Loading 정보획득
-verbose:class 옵션에 의한 Class Loading 모니터링 결과
Open된 jar 파일과 Loading된 Class 목록 확인 가능
[Opened c:bea10jdk150_06jrelibrt.jar][Opened c:bea10jdk150_06jrelibjsse.jar][Opened c:bea10jdk150_06jrelibjce.jar][Opened c:bea10jdk150_06jrelibcharsets.jar][Loaded java.lang.Object from c:bea10jdk150_06jrelibrt.jar][Loaded java.io.Serializable from c:bea10jdk150_06jrelibrt.jar][Loaded java.lang.Comparable from c:bea10jdk150_06jrelibrt.jar][Loaded java.lang.CharSequence from c:bea10jdk150_06jrelibrt.jar][Loaded java.lang.String from c:bea10jdk150_06jrelibrt.jar][Loaded java.lang.reflect.GenericDeclaration from c:bea10jdk150_06jrelibrt.jar]…
(cf) IBM JVM은 Thread Dump 도 Class Loading 정보 제공
Class Reloading과 OOME
현대적 대부분의 WAS가 Class Reloading 기능을 제공
Class Reloading이란 Runtime에Class가 재생성되면 이를 JVM을 Reboot하지 않고 Reloading하는 기능을 의미한다. 일부 WAS의 경우Class가 Reloading될 때 이전 버전의 Class를 해제하지 않는 경우가 있다. 따라서 Class Reloading이자주 발생하면 Permanent Space가 금방 꽉차게 되고 OOME가 발생하게 된다. 이와 같은 경우에는 WAS가 제공하는버그 패치를 적용하거나 WAS를 주기적으로 Restart해야 한다.
OOM 발생 원인 - 3. Native Heap에서의 OOME
(cf)
Java Heap , Permanent Space : Java 관련 Object 거주 공간
NativeHeap : OS 레벨 Native Object , JNI Library 레벨 Native Object 거주 공간
(예시)
java.lang.OutOfMemoryError: request bytes for . Out of swap space?
java.lang.OutOfMemoryError: (Native method)’
java.lang.OutOfMemoryError: unable to create new native thread
- 1안 : VM code 레벨에서 메모리 부족 현상 발견
- 2안 : JNI나 Native Method에서 메모리 부족 현상 발견
- 3안 : Thread 생성 불가. Thread는 Native Heap 공간의 메모리를 필요로 하므로 Native Heap 공간의 메모리 부족 시, Thread 생성 시 OOME 발생
Native Heap 에서의 메모리 부족 원인은 다양
(1) Thread Stack Space가 부족한 경우
(cf) Java Thread는 Native Heap 공간에 Stack Trace를 저장할 공간 필요
ThreadStack Space의 크기는 -Xss 옵션으로 지정
-Xss 옵션으로 지정되는 공간은 개별 Thread가 사용하는 공간
(ex) N개 Thread 활성화 시, N* 만큼의 메모리 공간 필요
다수 OS에서 Thread Stack Size 는 512K ~ 1M 사이
다수 Thread 활성화 시, Thread Stack Space만으로 큰 크기의 Native Heap 메모리 공간 소모
(해결) Thread Stack Space 문제에 의한 OOME 해소
① Thread의 수 감소
동시 수십개 이상의 Thread 사용은 메모리 문제 및 과도한 Context Switching으로 성능 저하 요인
⇒ 다수의 WAS가 채택한 Thread Pool 기법으로 동시에 Thread의 수를 줄이기
② Thread Stack Size 감소
다수 OS에서 Thread Stack Size는512K ~ 1M
다수 Thread가 필요한 Application일 경우, Thread Stack Size를 줄임으로써 OOME 방지
⇒ -Xss128k 정도나 -Xss256k 에서도 보통 문제없이 작동
⇒ Stack Size가 줄어든 만큼 Stack Overflow Error 발생 확률 증가
③ Java Heap 크기 감소
- 32bit Process가 사용 가능한 메모리 공간은 OS에따라 2G ~ 4G로 제한
→ 하나의 Java Process가 사용 가능한 공간은 [Java Heap+PermanentSpace+Native Heap]
→ Java Heap이 과도하게 큰 공간 사용 시, NativeHeap에서 사용 가능한 공간 감소
→ Java Heap 크기 감소 시, Native Heap의 메모리 부족에 의한 OOME 문제 해결
→ Java Heap 크기를 지나치게 줄이면 Java Heap 부족에 의한 OOME 현상 발생
(cf) Java Heap 크기를 줄이는 방법은 Thread Stack Space의 부족 문제뿐 아니라 Native Heap 부족에 의한 OOME 문제를 줄이는 공통적인 해결책
④ 64bit JVM 사용
- 64bit JVM에서는 32bit JVM Process가 갖는 2G ~ 4G의 제한 없음
⇒ Native Heap의 메모리 부족 문제 감소
(단)
- 32bit Application의 성능이 64bit Application에 비해 더 나은 성능을 보일때 많음
- 64bit JVM 사용하는 경우 약간의 성능 저하 발생 가능
- 과도한 Virutal Memory의 사용은 Application 성능 저하 주요인
- JavaApplication의 성능은 모든 Object가 Physical Memory에 머무를때 가장 뛰어남
- Physical Memory를 초과하는 크기의 Virtual Memory 사용 시, Physical Memory의 일부를 Disk에 저장하는 Paging In/Out 발생
- Paging In/Out은 Memory Operation에 비해 매우 느리며 Application의 성능도 저하
(2) Virtual Space Address 가 소진된 경우
- 32bit JVM에서 사용가능한 Virtual Address Space의 최대 크기는 OS별로 2G ~ 4G 제한
- Java Process는 Java Heap과 Permanent Space를 제외한 나머지 공간만 NativeHeap 사용가능
- 2G의 Virtual Address Space 사용가능. JavaHeap 1G, Permanent Space 200M 사용. Native Heap사용 가능한 최대 크기 800M. 800M 중 OS가 Process 관리 목적으로 사용하는 기본 메모리가 포함되므로 JavaApplication이 사용 가능한 Native Heap의 크기는 훨씬 감소. Native Heap 공간 부족에 의한 OOME 발생 확률 증가.
(해결)
Java Heap 크기 줄기이
64bit JVM 사용
Thread 수를 줄이기
Thread Stack Size 줄이기
⇒ Native Heap 공간 확보
(3) Swap Space가 모자란 경우
- Physical Memory를 초과하는 Virtual Memory 요청 발생 시, Paging In/Out으로 필요한 메모리 확보(= Paging In/Out을 위한 공간이 Swap 공간 )
- Swap Space가 부족하면 Paging In/Out 실패 → OOME 발생
- 여러 개 Process가 Swap Space 사용 시, Swap Space 부족에 의한 OOME 발생 확률 증가
- OS가 제공하는 툴로 Swap Space와 Paging In/Out을 모니터링 해야하며, SwapSpace가 부족한 경우 크기를 키워야한다.
(4) JNI Library에서 Memory Leak이 발생하는 경우
(5) Thread Stack Space와 OOME
《OOME와 Fatal Error Log》
Native Heap에서 OOME가 발생하면 JVM은 심각한 상황이라고 판단하고 Fatal Error Log를 남긴다. 아래에 OOME가 발생한 상황에서의 Fatal Error Log의 Header 정보에 대한 간단한 Sample이 있다.
An unexpected error has been detected by Java Runtime Environment:## java.lang.OutOfMemoryError: requested 20971520 bytes for GrET in C:BUILD_AREAjdk6_02hotspotsrcsharevmutilitiesgrowableArray.cpp. Out of swap space?## Internal Error (414C4C4F434154494F4E0E494E4C494E450E4850500017), pid=5840, tid=5540## Java VM: Java HotSpot(TM) Client VM (1.6.0_02-b06 mixed mode)# An error report file with more information is saved as hs_err_pid5840.log## If you would like to submit a bug report, please visit:# http://java.sun.com/webapps/bugreport/crash.jsp#Fatal Error Log를 통해 OOME를 유발한 Thread와 Stack Trace를 추적할 수 있다. 이 정보가 곧 해결책을 의미하지는 않지만, 추가적인 분석을 위한 힌트가 될 수 있다.
OOM 에러 종류
《 Java.lang.OutOfMemoryError 》
Java.lang.VirtualMachineError의 Subclass로 JVM의 Heap Memory에 Object 할당 실패 시 발생
1. Exception in thread "main" : Java.lang.OutOfMemoryError: Java heap space
Heap Size 부족. 즉 Java의 Heap Memory 공간 부족으로 인해 Java Object를 Heap에 할당하지 못함. JVM 옵션 설정을 하지 않은 경우 주로 발생
※ 공간 부족 원인
- Heap Memory 크기가 작아서 발생
- 애플리케이션 로직 문제로 발생
(해결)
-Xmx 옵션 조정을 통한 Heap Memory 크기 증가
(한계)
GC Time 증가 동반 → 사전 테스트 필요
OOME 발생 시점에 생성된 Heap Dump 분석을 기반으로 과도한 Memory 사용 또는 Memory Leak유발 로직 수정
2.Exception in thread "main": Java.lang.OutOfMemoryError : PermGen space
Class나 Method 객체를 PermGen spac 에 할당하지 못할 경우 발생.
애플리케이션에서 과도한 class 로드 시 발생 → 잘못된 설계 및 구현
《Java Heap Memory 영역 중 Permanent 영역 》
- String pool, Class Method와 관련된 각종 Meta Data 저장 용도.
- JVM 기동시 로딩되는 Class 또는 String의 수가 과도하면 Java.lang.OutOfMemoryError : PermGen space의 원인
- Classloader Leak에 의해 OOME 발생 가능성
(해결)
-XX:Permsize 또는 -XX:MaxPermSize Option으로 오류 수정
3.Exception in thread "main": Java.lang.OutOfMemoryError : Requested array size exceeds VM limit
사용할 배열의 사이즈가 VM에서 정의될 사이즈를 초과할 때 발생
Java Heap 최대 크기 보다 큰 Array 요청
(ex) Java Heap 최대크기가 256M인 상황에서 300M 크기 Array 생성하는 경우
4**.Exception in thread "main": Java.lang.OutOfMemoryError : Request bytes for .Out of swap space?**
Java는 런타임시 물리적 메모리를 초과한 경우 가상메모리를 확장해 사용하는데 가용한 가상 메모리가 없을 경우 발생
⇒ Permanent 영역은 Heap 영역과는 다르게 일반 비즈니스 프로그램으로 핸들링 불가. JVM Option 튜닝으로 미해결 시, WAS 혹은 JDK 버그 의심
⇒ PermGen 영역은 Class 로딩 후 해제하지 않으면 누수가 발생하며 해당 영역에 OutOfMemory 발생가능성. Class Loading 현황 분석을 위해 컨텍스트 메뉴에서 Java Basics > Class Loader Explorer를 선택해서 확인 가능
Permanent Space 발생 오류
(배경) 잘못된 코딩
(개념)
Perm Generation 영역
- Young와 Old를 구분하는 Generational Collector 방식 HotSpot JVM 의 영역중 하나
- 객체 생명주기가 길다고 판단된 객체를 할당하여 GC대상에서 제외
- 자바 Class 객체 및 String 객체
(클래스동작원리)
※ 클래스 로딩
시스템의 Class path에 의해 로드된 Class 객체들과 애플리케이션 내 구현으로 다이나믹하게 로드되는 Class 존재
→ 애플리케이션 내 로직에서 다이나믹하게 생성되는 Class에 의해 문제 발생
→ Spring, Mybatis 사용 프레임워크의 경우 OOM 발생 주의
→ 잘 설계된 아키텍쳐라도 잘못된 구현으로 인해 OOM 발생 가능→ 잘 설계된 아키텍쳐라도 잘못된 구현으로 인해 OOM 발생 가능
OOM 해결
《요청당 메모리 감소 시키기》
사용 가능한 전체 JVM(Java Virtual Machine) 힙을 요구하는 단일 요청에서 발생 가능
JVM 힙 할당을 위한 요청이 허용되는 최대 메모리 양 제한 설정
《 JVM 메모리 구조에 따른 메모리 옵션 조정 》
JVM Option 값 조정에 따른 각 영역별 메모리 할당
→ OOM 발생 시, 각 영역별 메모리 옵션 늘린 후 대응
- 시스템 물리적 리소스 한정으로 특정 영역 메모리 사이즈 증량 시 타 영역 메모리 감소하여 다른 문제 야기 가능
- (ex) 전체 메모리 2G 사용중인 상황에서 Heap 영역 1.5G , Perm Gen영역 256MB 할당
- → OOM으로 Perm Gen Space 영역 516MB 증량시 Heap 영역 1G 감소
⇒ Dump 파일을 통한 정확한 분석 및 진단 후, 원인 수정 필요
결론
- Java 코드 작성 및 Java 애플리케이션 개발 시, 메모리 관리는 GC에 일임하는 것이 일반적
- JVM 구조 이해 및 메모리 관리에 대해 고려를 덜하는 경향
- 다양해지는 고객 요구사항 및 복잡한 시스템 → 메모리 문제 늘 염두 필요
- GC Collection에 의한 성능 저하, OOME에 의한 애플리케이션 정지, System Crash 문제
- 시스템 사양이 개선되며 애플리케이션의 사용 가능한 메모리양이 늘었음에도 자원 한정
① Java Reflaction 기능 사용
② Spring, Mybatis 등 프레임워크 사용
⇒ OOM 발생 주의
참고블로그
https://www.nextree.co.kr/p3878/
'[Development] > DailyGoogle' 카테고리의 다른 글
【D.D.S :: JAVA】JVM Option (0) | 2021.04.21 |
---|---|
【D.D.S JAVA】Java 메모리 구조 (JVM) 및 런타임 메모리 구조 (0) | 2021.04.20 |
[##190526##] 환경변수의 의미 및 설정 목적 (0) | 2019.05.26 |