토스ㅣSLASH 22 - Java Native Memory Leak 원인을 찾아서

유재희·2023년 5월 1일
1

Conference

목록 보기
3/9

위 영상을 보고 정리한 포스트
[영상 시청]


문제 상황

OOM Killer

  • 시스템에 여유 메모리가 없을때 시스템에서 과도하게 메모리를 사용하고 있는 프로세스를 희생하고 OS를 정상화한다. 이 역할을 하는것이 OOM Killer

  • Java로 서비스하고 있는 서버에서는 Java 프로세스가 가장 많은 메모리를 차지하므로 대부분 Java 프로세스를 죽일것이다.

  • Max Heap Size를 1.5GB로 설정했는데 OOM Killer가 죽인 Java 프로세스가 사용한 메모리는 4GB, 2.5GB의 알 수 없는 메모리 사용

  • Java 구동시 NativeMemoryTracking 옵션을 주면 jcmd 명령을 통해 자바 가상머신의 사용되는 메모리양을 측정할 수 있다.

  • RSS 수치가 3.1GB로 나오지만 OOM Killer가 죽인 Java 프로세스는 4GB -> 2GB의 정체모를 메모리

Native Memory Leak

원인을 찾아보자

Native Memory Leak이 쉽게 나타날 수 있는 부분

  1. JNI & JNA
  • C로 구현한 모듈을 자바에서 호출하는 기능이다. C구현체에서 메모리 누수가 생길경우 자바머신에서는 인식할 수 없기 때문에 누수가 발생할 수 있다.
  • 확인 결과 문제 없음
  1. Direct Buffer
  • 자바 가상머신에 GC로 관리되지 않는 네이티브 메모리가 할당된다.
  • 확인 결과 버퍼의 사용량은 매우 적었다.
  1. JavaAgent 기반의 APM툴
  • JavaAgent로 연동하며 instrument를 변경하는 형태로 동작하니 혹시모를 네이티브 메모리를 사용하지 않을까?
  • Pinpoint만 사용했는데 문제 없었다.

Linux OS의 프로세스 레벨로 살펴보기

  • 프로세스를 dump하고 파일내에 문자열을 조회해서 문제를 찾아보려고 시도
  • 이렇다할 단서가 없었다.

  • 두 번째 시도 방법 memory profile툴을 적용해보기
  • 리눅스에서 프로세스에 메모리를 할당할때 malloc 라이브러리를 사용하는데 jemalloc을 쓰면 프로파일 툴을 통해 모듈간 메모리 사용량을 확인할 수 있다.

  • 이상 발견 -> C2 컴파일러에서 1.9기가의 메모리를 사용중
  • openjdk의 bug tracking system을 살펴보니 C2 Compiler의 memory leak 이슈가 리포팅된 건 확인

C2 Compiler?

JIT Compiler

level 0 : 최적화 없이 기계어로 해석
level 1 : 최적화가 불필요하다고 느끼는 간단한 코드들, 프로파일링 정보 수집 x
level 2 : 제한된 최적화
level 3 : 프로파일링 모드로 정보 수집 후 최적화 진행
level 4 : 최대 최적화 진행

jvm의 컴파일러인 JIT컴파일러의 5단계중 마지막 단계의 컴파일러

C1 (Client Compiler) : 컴파일 시간은 빠르지만 최적화는 줄인다.

C2 (Server Compiler) : 컴파일 시간은 느리지만 최적화를 많이하여 연산이 빠르다. (서버에 적합)

  • CPU 사용률이 40 -> 70으로 상승, 최적화가 상대적으로 약하기 때문에
  • CPU 사용률은 줄이고 싶은데..

  • Graal Compiler를 사용하기 위해선 UnlockExperimentalVMOptions라는 실험적 옵션을 사용하는 옵션을 줘야한다.

  • CPU 사용률이 적어지고 메모리 문제도 해결

  • JDK 버전에 릴리즈노트를 확인하며 실마리를 찾고 있지만 아직은 Graal을 사용중

트러블 슈팅
정답보단 문제 원인의 실마리를 찾아가는 과정

C1 컴파일러, C2 컴파일러에 대해 찾아보다가 궁금증이 생겼다.
C2 컴파일러만 최적화를 위해 코드캐시를 사용할까??
간단한 테스트코드를 만들고 -XX:+PrintCompilation 명령어와 -XX:+PrintCodeCache 명령어를 사용해 결과를 확인해봤다.

  • 확인해 보면 C1 컴파일러를 사용할때도 코드캐시에 코드를 저장하는걸 알 수 있다.

컴파일 로그에 대해 하나의 행을
n b c [message] 라고 가정해보면
n : 시작 부터 컴파일동안 걸린시간
b : 컴파일한 코드블록 정보
c : compile Tiere 정보

그리고 중간에 %가 보인다. 이는 코드캐시에 코드를 저장했다는 정보다. 아래 코드캐시 사용정보를 보면 사용량을 알 수 있다.

코드 캐시 이용 정보

'non-profiled nmethods': JIT 컴파일러가 프로파일링 정보를 이용하지 않은 메서드 코드를 저장하는 공간
(c2 Compiler)

'profiled nmethods': JIT 컴파일러가 프로파일링 정보를 이용한 메서드 코드를 저장하는 공간 (c1 Compiler)

'non-nmethods': JVM 내부 관련코드.

profile
몰라요

0개의 댓글