🚨개요

사내에서 운영 중인 특정 API(tomcat기반 APP)의 JVM Heap 사용률이 지속적으로 70%-80% 이상 유지되며 성능 저하 가능성이 확인되었습니다. 이에 따라 Heap 증설 및 메모리 누수(Memory Leak) 여부를 포함한 분석을 수행했습니다.

 

🛠️ 이슈 사항

  • 4월 중순:
    • JVM모니터링 시작 후 운영 대시보드에서 JVM Heap 사용률이 80% 이상으로 꾸준히 유지됨을 감지
    • 초기 대응으로 개발자와 논의 후 API JVM Heap 사이즈를 1GB → 2GB로 증설
  • 5월 초:
    • 동일 API에서 다시 Heap 사용률이 80% 이상 점유됨
    • Heap 증가 패턴이 동일 API에서 반복됨에 따라 GC 및 Heap 사용 분석 진행

🔍 원인 분석

1. JVM 내부 지표 분석

  • Memory Pool 별 사용률 확인 결과,
    → PS Old Gen 영역의 메모리 사용량이 계속적으로 우상향 (증가-하기이미지참고)
    → Full GC 이후에도 충분히 회수되지 않는 패턴 확인
  • 이는 객체가 장기적으로 메모리에 머무는 현상을 의미하며, Memory Leak 가능성으로 추정

2. Heap Dump 분석

  • 대상 프로세스에서 heap dump 수행 
    ※ -live 옵션은 현재 GC 후 생존 객체만 덤프하며, 내부적으로 Full GC를 트리거
  •  
  • 명령어 : jmap -dump:live,format=b,file=heap_0513.hprof < tomcat PID>
  • Eclipse Memory Analyzer Tool(MAT)로 분석 결과:
    • Problem 1: MySQL Connector 관련 객체가 해제되지 않고 계속 유지


    • Problem 2: SSL Handshake 객체가 누적되어 소멸되지 않음
      
  • 해당 누수 객체들의 Retained Size를 분석한 결과:
    • 총 약 200MB 수준의 메모리 누수 확인
    • 전체 Heap 대비 소규모이나, FullGC 트리거 이후이기에, 누적 시 OutOfMemoryError 가능성 존재



     

3. 누수 규모 판단 관련 유의사항

  • jmap -dump:live는 GC 후 생존 객체만 덤프하므로,
    → 실제로는 GC에 의해 소멸된 일부 객체가 누락될 수 있음
    → 따라서 실제 누수 규모보다 작게 집계될 가능성 존재
    → 추후 heapdump 진행시에는 live 옵션 제외

✅ 조치 내용

  • 개발팀에 Problem 1,2 관련 메모리 누수 공유 및 수정 요청
    • MySQL Connector 재사용 방식 점검
    • SSL Handshake 객체 재사용 또는 해제 로직 적용
  • 모니터링 항목 추가
    • PS Old Gen 사용률 별도 대시보드화
    • Heap 사용량 및 GC 발생 간격 추이 주기적 분석
  • 향후 대응 계획
    • 주기적 HeapDump 스케줄링을 통한 누수 패턴 조기 탐지
    • GC 로그 기반 메모리 증감 패턴 수집 및 자동 알림 설정

 

1. 증상

  • API 서버에서 OutOfMemoryError: unable to create new native thread 발생
  • 해당 서버에서의 장애로 인해 서비스 접속 간헐적 에러 발생

2. 선조치

  • 장애 복구를 위해 배포를 시도했으나, SFTP 접근 중 secure 로그상에 do_exec_no_pty: fork: Resource temporarily unavailable 오류 발생
  • 톰캣 강제 종료 후 커널 파라미터 (open files, max user processes) 조정 및 배포 진행정상화

3. Root Cause 분석

(1) 주요 확인 사항

  1. OOM 발생 로그 분석 결과, 신규 쓰레드 생성이 불가능한 상태 (추가로 secure로그를 통해 파일 디스크립터 부족 가능성 시사)
  2. 동일한 환경의 이중화 서버 중 API 1번에서만 문제 발생

(2) 상세 분석

  • OOM 발생 후 JMX Exporter를 통한 모니터링을 시작한 결과, api 1에서만 
    쓰레드 개수가 비정상적으로 증가하는 현상 확인
  • jstack을 이용한 분석 결과, 아래와 같이 쓰레드풀에서 WAITING 상태의 쓰레드가 지속적으로 생성됨
"pool-xxxx-thread-1" #xxxx prio=5 os_prio=0 cpu=0.12ms elapsed=567.11s tid=0x00007f23e5048000 nid=0x740f9 waiting on condition  [0x00007f215cd92000]
   java.lang.Thread.State: WAITING (parking)
       at jdk.internal.misc.Unsafe.park(java.base@xx.x.xx/Native Method)
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ중략ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

(3) 원인 분석

  • API 소스 내 Java 쓰레드 생성 관련 코드를 점검한 결과,
    ExecutorService executor = Executors.newFixedThreadPool(5);
    특정소스의 위 코드에서 생성된 쓰레드풀을 명시적으로 종료 (shutdown)하지 않는 문제를 확인
  • 추가로 개발 담당자와의 프로세스 검토 결과, 로드밸런싱이 되는 서비스이기에, 1번 서버에 요청이 집중되는 현상의 원인 파악이 확인 필요
    •  VIP의 L4 로드밸런싱 설정이 Hash 기반 → 동일 클라이언트 IP에서 들어오는 요청이 특정 서버에 집중될 가능성이 있음
    •  이를 검증하기 위해 L4 로드밸런싱 메트릭을 Round Robin 방식으로 변경 후 테스트 진행
    •  그 결과, 1번과 2번 서버 모두에서 쓰레드풀 WAITING 증가 현상 확인 → 원인 파악 완료

(4) 주말간 OOM 발생 원인

  • 쓰레드풀 생성 후 회수가 이루어지지 않아 ulimitmax user processes 임계치 도달
  • 임의의 환경(max user processes 최소값)에서 테스트 코드 실행 시 동일한 OOM 발생 재현 확인

4. 조치 사항

  1. 소스 수정
  2. 커널 파라미터 조정
  3. 어플리케이션 모니터링 강화

5. 결론:

  • 쓰레드풀 미회수로 인한 max user processes 임계 도달이 OOM 발생의 직접적인 원인, 이를 해결하기 위해 소스 수정 및 커널파라미터 튜닝 진행

0. 들어가며

프록시툴(Burp Suite)을 통해 tomcat으로 구동되고 있는 웹어플리케이션에 대하여 웹취약점에 대한 검사를 하였다.
특정 페이지의 파라미터에 특수문자(>")를 삽입한뒤 수정하여 보낼 경우, 아래 이미지와 같이 web.xml에 설정되어 있는 페이지가 아닌 기본 톰캣 에러페이지가 리턴되었다. 

 

원인은 아래와 같고, 원인에 대한 조치방법을 알아보자.

 

1. 원인


1) tomcat URL PATCH( RFC7230,RFC3986 )
아래는 버그 리포트에 남겨진 정보이며, 이미지는 그와 관련된 패치 내역이다.
https://bz.apache.org/bugzilla/show_bug.cgi?id=62273

 

tomcat의 8.5.31 , 9.0.8 버젼 이상부터는 위 패치로 인해 URL 파라미터에 특수문자가 들어갈 경우 웹애플리케이션으로 REQUEST를 전달을 하지 않게끔 패치가 되었다. web.xml의 에러페이지의 경우 웹애플리케이션이 처리할 때의 에러페이지를 나타내므로, tomcat 자체에서 에러를 리턴할 경우 기본 페이지가 노출되는 것으로 확인할 수 있다.

 

2. 조치방안

 

조치방법은 총 2가지인데, 아래와 같다.

 

1) relaxedQueryChars 설정

 

해당 설정의 경우 server.xml의 connect에 설정을 하면되는데, 패치로 인해 막힌 특수문자에 대한 설정을 풀어주는 옵션이다.  아래이미지는 해당옵션에 대한 설명이다.

 

<Connector port="8079" 
           protocol="HTTP/1.1" 
           connectionTimeout="20000" 
           redirectPort="8443"
           relaxedQueryChars="[]()^|&quot;"
           />

선언된 특수문자는 예외처리되어 웹어플리케이션에서 처리되기 때문에 web.xml에 선언되어 있는 에러페이지에서 처리가 된다. 

* relaxedQueryChars 선언시 <,>를 추가한 뒤 tomcat을 재구 동하면 에러가 발생하는데 아래 URL의 코드를 참고하여 설정하면 정상적으로 적용된다.

https://www.codetable.net/unicodecharacters

 

Unicode character list - Code Table.NET

 

www.codetable.net

ex )

특수문자 Unicode Character Hex Code
< &#x3C;
> &#x3E;

 

2) org.apache.catalina.valves.ErrorReportValve 설정

   
1)의 조치방법은 잡히는 특수문자에 대해 전부 필터를 걸어야하는 번거로움이 있다. 이 방법은 

tomcat의 기본 에러 페이지를 별도로 선언하여, 조치하는 방법이다 

<Valve className="org.apache.catalina.valves.ErrorReportValve"
       showReport="false"
       showServerInfo="false"
       errorCode.0="/home/tomcat_svr/webapps/WEB-INF/classes/static/error.html"

 

errorCode 선언을 통하여 각 HTTP상태코드에 따른 페이지를 설정할 수 있지만,. 0 설정 시 기본 에러페이지이므로 해당 페이지로 설정함이 편의성이 좋아 보인다. 추가로 showReport는 false설정 시 에러에 대한 상세한 내용을 표시하지 않는 옵션이며, showServerInfo는 false설정 시 서버정보를 반환하지 않는 옵션이다. 아래 이미지는 해당 옵션들에 대한 설정이다.

+ Recent posts