Skip to content

고맙다 JVM, 사과해라 JVM 크래시

HyoSang edited this page Mar 6, 2018 · 3 revisions

고맙다 JVM, 사과해라 JVM 크래시

http://d2.naver.com/helloworld/1134732

많은 개발자가 버그 원인을 인프라 탓으로 돌리곤 합니다. 하지만 거의 대부분, 아마도 99.9%는, 인프라의 잘못이 아닙니다. 그러나 JVM의 실행이 멈췄다는 것은 0.1%라고 무시할 수 없는 사례입니다.

JVM은 마치 자동차의 엔진과 같은 것이라 인위적으로 시동을 끄지 않는 이상(System.exit 등) 외부적인 요인으로 실행이 멈추면 안 됩니다. JVM이 멈춘다는 것은 단순히 특정 시간의 장애로만 끝나는 것이 아닙니다. 근본 해결이 없다면 상습적인 장애로 연결돼 불안정한 서비스라 낙인이 찍히고 사용자는 이 불안정한 서비스를 외면할 것입니다.

크래시 로그 hs_err_pid의 주요 항목

구성 항목 설명
Header 오류에 관한 간략한 정보가 기술된 영역. 프로세스 아이디, 스레드 아이디, VM버전, 크래시를 일으킨 function frame정보 등이 있다.
Thread 크래시 발생 당시의 스레드 관련 정보
Process 모든 프로세스에 대한 정보. 이 프로세스에는 스레드 목록과 메모리 사용량에 대한 정보가 있다.
System 운영체제 버전, CPU정보, 메모리 설정 관련 정보 등

제일 먼저 Thread 섹션에서 JVM 크래시 순간의 스레드가 무엇인지를 확인해야 한다.

#
# A fatal error has been detected by the Java Runtime Environment:
#  SIGSEGV (0xb) at pc=0xb7961e23, pid=11757, tid=1405516688
      |      |           |             |         +--- thread id
      |      |           |             +------------- process id
      |      |           +--------------------------- program counter
      |      |                                        (instruction pointer)
      |      +--------------------------------------- signal number
      +---------------------------------------------- signal name
#
# JRE version: 6.0_18-b07
# Java VM: Java HotSpot(TM) Server VM (16.0-b13 mixed mode linux-x86 )
# Problematic frame:
# V  [libjvm.so+0x5a2e23]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp

---------------  T H R E A D  ---------------

Current thread (0x09a57400):  GCTaskThread [stack: 0x53be7000,0x53c68000] [id=11760]

스레드 종류

  • JavaThread
  • VMThread
  • CompilerThread
  • GCTaskThread
  • WatcherThread
  • ConcurrentMarkSweepThread

제일 먼저 확인해야 할 부분은 Thread 섹션에서 JVM 크래시 순간의 스레드가 무엇인지다. 위와 같이 가비지 컬렉션(garbage collection) 스레드의 특수한 경우라면 가비지 컬렉션 자체에 문제가 있지 않나 의문을 가질 수 있으나 판단을 내리기에는 정보가 부족하다.

Current thread (0x0805ac88):  JavaThread "main" [_thread_in_native, id=21139]
                    |             |         |            |          +-- ID
                    |             |         |            +------------- state
                    |             |         +-------------------------- name
                    |             +------------------------------------ type
                    +-------------------------------------------------- pointer
스레드 상태 설명
_thread_uninitialized Thread is not created. This occurs only in the case of memory corruption.
_thread_new Thread has been created but it has not yet started.
_thread_in_native Thread is running native code. The error is probably a bug in native code.
_thread_in_vm Thread is running VM code.
_thread_in_Java Thread is running either interpreted or compiled Java code.
_thread_blocked Thread is blocked.
..._trans If any of the above states is followed by the string _trans, that means that the thread is changing to a different state.

http://www.oracle.com/technetwork/java/javase/felog-138657.html#gbwcy

Heap
PSYoungGen      total 282176K, used 275243K [0x9ecf0000, 0xb4240000, 0xb4240000)
eden space 214336K, 100% used [0x9ecf0000,0xabe40000,0xabe40000)
from space 67840K, 89% used [0xb0000000,0xb3b7ad00,0xb4240000)
to   space 67328K, 43% used [0xabe40000,0xadae6ba0,0xb0000000)
PSOldGen        total 699072K, used 299948K [0x74240000, 0x9ecf0000, 0x9ecf0000)
object space 699072K, 42% used [0x74240000,0x8672b218,0x9ecf0000)
PSPermGen       total 524288K, used 42358K [0x54240000, 0x74240000, 0x74240000)
object space 524288K, 8% used [0x54240000,0x56b9d808,0x74240000)

Java의 힙 메모리 영역 중 Eden 영역(eden space)이 100%라는 정보를 볼 수 있다. Oracle JVM 에서는 새로 생성됐거나 생성된 지 얼마 되지 않은 객체는 Eden 영역에 저장된다. Eden 영역이 일정 비율 이상 차면 설정에 따라 가비지 컬렉션이 발생하고, 계속 생성돼 있을 필요가 있는 객체는 S0(from)라는 Survivor 영역으로 옮겨진다. 다시 Eden 영역이 차면 S1(to)이라는 또 다른 Survivor 영역으로 이동한다. 설정에 따라 조금씩 동작 방법이 다르지만 좀 더 오래 생성돼 있어야 하는 객체는 가비지 컬렉션을 거치면서 Old 영역으로 이동한다.

S0(from)와 S1(to) 중 하나의 영역은 반드시 비어 있어야 하는데 크래시 로그에서 메모리 영역의 from 영역과 to 영역은 각각 89%와 43%다. 즉 가비지 컬렉션이 기대대로 동작하지 않은 것이다.

버그 리포트와 릴리스 노트

정석대로라면 다음에 할 작업은 추측한 시나리오를 바탕으로 JVM 버전의 버그 정보를 조회하는 것이다.

문제가 발생한 JVM 버전은 6.0_18(b07) 버전이었다(6.0_18(b07) 버전 버그 리포트 참고). 이 버그와 관련된 버그가 JVM 7.0-b74에서 발생했으며, 자세한 버그의 내용은 JVM 7.0-b74의 버그 리포트에서 확인할 수 있었다

여기까지 왔다면 개발자의 성향에 따라 2가지 방법 중 하나를 선택한다. 버그 리포트에서 설명하는 JVM 옵션을 추가해 사용하거나, 안정된 JVM 버전으로 다운그레이드하는 것이다. 결국 신규 서버를 기존 서버가 사용하는 6.0_14 버전으로 다운그레이드하고 성능 테스트를 다시 실행했더니 가상 사용자 200에서 시스템의 작동이 중단되거나 중지되는 현상 없이 10여 분 동안 서비스가 지속됐다. 추측도 어느 정도 맞았고 성능 테스트도 해결돼 모든 게 잘 완료됐다고 볼 수 있지만 추가로 더 확인할 것이 있다.

추가로 확인할 것은 릴리스 노트다. 6.0_18 버전의 정식 릴리스 노트에서 관련 버그가 소개됐는지 확인하고 6.0_14 버전에도 문제가 없는지 확인하는 것이다. 6.0_18 버전의 릴리스 노트에 관련 내용이 있었다.

Clone this wiki locally