Java

OOM이 발생했을 때 어버버 하지 않게 대비하기!

DEV_GOLF 2023. 9. 27. 01:22
반응형

회사에서 업무 중 OOM이 발생하며 트러블 슈팅을 하면서 배웠던 내용들을 공유드릴려고 합니다. 

 

OOM이란?

out of memory의 앞글자에서 따온 말로 heap 영역에 인스턴스나 더미 데이터들이 정해놓은 heap 크기보다 커졌을 때 보통 발생합니다. 또는 스레드 스택 메모리나 메타 스페이스 등에 데이터가 꽉차더라도 문제가 발생할 수 있습니다. 더 이상 인스턴스에게 새로운 메모리 공간을 할당할 수 없기 때문에 서버는 에러를 내고 서버를 죽입니다.

 

다음은 OOM이 발생하는 코드를 만들어봤습니다. 이 코드에선 계속해서 ArrayList에 데이터를 append하여 추가하다가 어느 순간 메모리 공간이 부족해지면 에러가 발생하여 프로그램이 동작을 멈출 것입니다.

fun main() {

    val list = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val newList = mutableListOf<Int>()

    while (true) {
        newList.addAll(list)
    }
}

보통 OOM의 원인은 발생하자마자 서버가 내려가기 때문에 해당 상황을 다시 재현하기 쉬운게 아니라면 찾기가 힘들 뿐더러 디버깅으로 찾기도 힘들기 때문에 보통 heap dump를 추출하여 분석해야합니다. 

heap dump?

heap dump란 JVM의 heap 공간에 대한 스냅샷 정보입니다. heap 스냅샷 정보엔 보통 메모리 사이즈, 콘텐츠, 가비지 컬렉션 루트 등의 정보들이 포함되어있고 보통 이 내용들을 분석하여 어떤 객체가 heap 공간을 많이 차지하고 있는지 제거되지 않고 계속 남아있는지 찾을 수 있습니다. 

 

보통 hporf 파일 확장자를 갖고 있으며 메모리 상태를 그대로 반영하기 때문에 파일 크기가 큽니다. 다양한 분석 도구가 있는데 대표적으로 MAT이나 VisualVM등을 사용해볼 수 있습니다. 

 

하지만 oom이 발생했을 땐 이미 서버가 내려간 시점이고 heap dump를 분석하기에도 이미 늦은 상황입니다. 그러면 우리는 결국 이 상황을 재현할 때 까지 기다렸다가 서버 메모리나 GC time에 문제가 발생했을 때 heap dump를 분석하여 해결해야합니다. 하지만 마냥 기다리기엔 리스크가 크죠.  그러지 않기 위해선 대비책이 필요해보입니다. 

어버버 하지 않기 위한 첫 번째 방법 (VisualVM 이용하기)

visualVM을 이용하여 외부 서버에 접속 시킨 뒤 해당 서버가 OOM이 발생했을 때 원격 서버에 연결을 해뒀다면 해당 서버 heap dump가 기록 되어있기 때문에 분석하여 OOM의 원인을 찾을 수 있습니다. 

위는 visualVM의 UI 화면입니다. remote 연결도 가능하기 때문에 원격 서버에 heap dump 분석도 가능합니다. 위에 보이는 숫자들이 byte 단위의 용량입니다.

 

하지만 해당 프로젝트는 생각보다 상세하게 분석되는 느낌이 부족해서 사용하진 않았습니다. 또한 원격 서버와 연결하는 것도 불편했습니다. 

어버버 하지 않기 위한 두 번째 방법 (MAT or IntelliJ 이용하기)

필자가 사용했던 방법입니다. UI가 깔끔하고 직관적인 분석이 가능했습니다. 또한 JVM 옵션만 잘 준다면 MAT을 이용하여 정확한 OOM 원인 분석이 가능했습니다. 

 

우선 옵션이 하나 필요합니다. 

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump

해당 옵션은 JVM 실행시 java 명령어 뒤에 넣어주면 됩니다. 이렇게 옵션을 넣어준 뒤 애플리케이션을 실행한다면 OOM이 발생하였을 때 저절로 heap dump가 생성됩니다. 

 

이렇게 만들어진 heap dump를 MAT이나 intelliJ로 열어서 분석하면 되는데 MAT 기준으로 이러한 UI 창을 볼 수 있습니다.

해당 사진은 실제 JDK 17과 scouter간에 호환성 문제로 인하여 OOM이 발생한 프로그램을 heap dump를 떠서 분석한 내용입니다. 보면 scouter.javaasist.ClassPoolTail 이라는 객체가 약 87% 이상을 차지하고 있고 이 인스턴스로 인해 OOM이 발생했다고 알려주고 있습니다. 

 

그러면 해당 라이브러리가 문제되는 것 같으니 검색해보겠습니다. 

구글에 검색해보니 바로 OOM 발생 이슈가 올라와있는 것을 확인할 수 있습니다. (실제로 현재 문제는 이미 scouter 측에서 문제를 해결한 버전을 출시해 놨습니다. scouter 최고 !!) 

 

이러한 직관적인 UI를 기반으로 필자는 비교적 쉽게 OOM이 발생하는 원인을 찾았고 해결하였습니다.

마무리

OOM이 발생했을 때 어버버 하지 않기에 대한 내용을 주제로 글을 적어봤는데요. 가장 중요한 것은 대비 입니다. 필자는 나중에 JVM의 OOM시 heap dump를 뜨는 옵션을 뒤늦게 넣어주고 운영 테스트 해가면서 찾아냈지만 만약 미리 저 옵션을 넣어놓고 언제든 OOM이 발생하더라도 heap dump를 분석할 수 있게 대비해놓았으면 더 빠르게 이슈 트래킹이 가능했을 겁니다. 

 

꼭 여러분들도 이러한 이슈를 그냥 해결하고 넘기지 말고 이러한 문제를 다신 발생하지 않게 관리하는 것이 중요하겠습니다. 

실수는 방지하는 것이 아닌 관리하는 것이다.
- 함께 자라기 (김창준 지음)

마침.