홈쇼핑 WebLogic 인스턴스 16대가 1~2일에 한 번씩 Hang-up 되던 장애를, GC 로그와 힙 덤프에서 시작해 스레드 덤프까지 파고들어 `javax.activation` 구버전 버그로 규명한 트러블슈팅 기록이다. 오픈 10년째 재기동으로만 넘기던 문제였다.
핵심 포인트- GC 그래프상 가용 힙이 시간에 따라 점차 줄어드는 형태라 "지속적 메모리 누수" 유형으로 판별했다. 대용량 요청 유형과 구분하는 기준을 먼저 세운다.
- 힙 덤프에서 WebLogic 내부 모듈 `RuntimeAccessImpl` 이 전체 힙의 약 57%(1GB)를, 그 안의 `java.lang.Thread` 배열이 약 670MB 를 점유하고 있었다.
- 스레드 덤프에는 총 7,873개 스레드가 있었고 그중 7,651개가 `DataHandler.getInputStream` 이라는 같은 이름이었다. 보통 JVM 전체 스레드는 200~300개다.
- `DataHandler` 는 이메일 발송에 쓰이는 `javax.activation` 패키지 클래스로, 본문 작성 후 종료돼야 할 스레드가 좀비로 남아 누적되고 있었다.
- 원인은 버전이었다. 같은 jar 가 1.1.0 과 1.4.5 두 개 있었는데 실제로 기동되는 것은 구버전 1.1.0 이었고, 운영 담당자도 최신을 쓰는 줄 알고 있었다.
상세 정리- 진단 순서를 원칙으로 먼저 밝힌다. 스레드 덤프는 모든 JVM 이슈의 기본, 힙 덤프와 GC 로그는 모든 메모리 이슈의 기본이다.
- 운영 담당자는 장애 시점에 OutOfMemoryError 가 뜬다는 것과 힙 덤프를 봐도 원인을 모르겠다는 상태였다.
- GC 로그는 WAS 기동 시 JAVA OPTIONS 설정을 추가해 생성하며, 분석 도구로 그래프화해 추이를 봤다.
- 힙 덤프는 과거 Hang-up 이 발생한 3회분을 받아 분석했다. 보통은 애플리케이션 개발자가 만든 클래스가 잡혀야 하는데 WebLogic 내부 모듈이 잡힌 것이 이상 신호였다.
- 여기서 "WebLogic 버그인가" 로 단정하지 않고 스레드 덤프를 추가 분석한 판단이 이 글의 분기점이다.
- 스레드 덤프에서 힙 2GB 중 Free 가 약 2MB(24D7D0)뿐이었고, 스레드 과다로 `Insufficient stack space for native stack collection` 오류가 함께 발생하고 있었다.
- 인과 사슬을 한 줄로 정리한다. DataHandler 스레드가 계속 생성되며 종료되지 못해 누적 → 점진적 메모리 잠식 → WAS 인스턴스 Hang-up.
- 조치: WebLogic 기동 시 1.1.0 대신 1.4.5 jar 를 읽도록 PRE CLASSPATH 로 지정했다.
- 검증 설계가 특히 보수적이다. 개발·검증 서버에서 기능 검증을 마쳤음에도, 운영 서버 4대 중 1대(WAS 4 인스턴스)에만 먼저 적용하고 나머지 3대와 비교했다.
- 결과: 주말 동안 적용한 1대는 메모리 누수도 재기동도 없이 정상 운영된 반면, 미적용 3대는 기존과 같이 누수와 Hang-up 이 발생해 강제 재기동했다. 메모리 사용량 추이 그래프로도 차이가 확연했다.
왜 읽나원인을 못 찾아 재기동으로 넘기고 있는 WAS 장애를 가진 운영자에게 GC 로그 → 힙 덤프 → 스레드 덤프로 좁혀 가는 순서와 A/B 검증 방식을 보여준다.