Tomcat Connector와 JVM Thread
Tomcat HTTP Connector, thread pool, timeout, JVM 옵션을 Web-WAS 운영 관점에서 정리한다.
Tomcat 내부 요청 처리
Web Server / Reverse Proxy
-> Tomcat Connector
-> request processing thread
-> Servlet / Application
-> DB / external API
Tomcat에서 Connector는 요청을 받는 입구다. HTTP Connector는 보통 8080, AJP Connector는 보통 8009에서
요청을 받는다. 요청이 들어오면 Connector의 thread가 애플리케이션 코드를 실행한다.
왜 thread를 보는가
동기 요청 하나는 처리되는 동안 thread 하나를 잡고 있는 것으로 생각하면 이해가 쉽다. 애플리케이션이 DB 응답이나 외부 API를 오래 기다리면 thread도 같이 묶인다. 그래서 Tomcat thread 고갈은 앞단에서는 504, 사용자 입장에서는 느린 응답으로 보일 수 있다.
Connector 핵심 값
| 속성 | 의미 | 운영에서 보는 질문 |
|---|---|---|
maxThreads | 동시에 요청을 처리할 thread 상한 | 요청 처리량과 backend 용량에 맞는가 |
minSpareThreads | 항상 준비해둘 thread 수 | 급격한 요청 증가에 너무 늦게 반응하지 않는가 |
maxConnections | Connector가 수용할 connection 상한 | 연결은 많은데 thread가 부족한 상태인가 |
acceptCount | 처리 한계 이후 OS queue 길이 | queue가 차서 연결이 거부되거나 지연되는가 |
connectionTimeout | 연결 후 요청 라인을 기다리는 시간 | 느린 client에 오래 묶이지 않는가 |
keepAliveTimeout | 다음 요청을 기다리는 시간 | keep-alive idle 연결이 너무 오래 남지 않는가 |
maxKeepAliveRequests | 연결 하나에서 처리할 요청 수 | 너무 낮아 재연결이 많지 않은가 |
maxThreads를 키우면 동시에 실행되는 요청은 늘 수 있다. 하지만 DB connection pool이 작거나
외부 API가 느리면 thread만 많이 대기하게 된다. Tomcat, DB pool, upstream timeout을 같이 맞춰야 한다.
HTTP Connector 예시
<Connector port="8080"
address="127.0.0.1"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
minSpareThreads="20"
maxConnections="8192"
acceptCount="100"
connectionTimeout="20000"
keepAliveTimeout="20000"
maxKeepAliveRequests="100"
redirectPort="8443" />
앞단 Nginx나 Apache HTTP Server가 같은 서버에 있다면 address="127.0.0.1"처럼 외부 노출을 줄이는
구성을 검토한다. 별도 서버에서 들어온다면 내부망 주소와 방화벽 정책을 같이 확인한다.
JVM 옵션 위치
Tomcat을 스크립트로 실행한다면 보통 $CATALINA_BASE/bin/setenv.sh에 JVM 옵션을 둔다.
서비스 매니저나 Windows Service로 실행하는 경우에는 systemd unit, 서비스 wrapper, 배포 스크립트가
옵션을 직접 줄 수 있으므로 실제 프로세스 인자를 확인한다.
ps -ef | grep tomcat
jcmd <PID> VM.flags
jcmd <PID> VM.command_line
운영에서 자주 확인하는 값:
| 항목 | 예시 |
|---|---|
| heap | -Xms, -Xmx |
| OOM 근거 | -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=... |
| GC log | Java 버전에 맞는 GC log 옵션 |
| timezone / encoding | -Duser.timezone=Asia/Seoul, -Dfile.encoding=UTF-8 |
| profile | -Dspring.profiles.active=... |
Tomcat 문서 기준으로 메모리 제한 같은 Tomcat 실행용 JVM 옵션은 CATALINA_OPTS 쪽에 두는 것이 낫다.
JAVA_OPTS는 start뿐 아니라 stop 같은 다른 명령에도 적용될 수 있기 때문이다.
setenv.sh 예시
CATALINA_OPTS="$CATALINA_OPTS -Xms1g -Xmx1g"
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -XX:HeapDumpPath=/var/log/tomcat"
CATALINA_OPTS="$CATALINA_OPTS -Duser.timezone=Asia/Seoul"
GC log 옵션은 Java 8, 11, 17에서 문법이 달라질 수 있다. 운영 서버의 Java 버전을 먼저 확인하고, 기존 옵션을 덮어쓰기보다 현재 기동 인자를 읽은 뒤 조정한다.
장애 때 보는 thread
jcmd <PID> Thread.print > /tmp/tomcat-thread.txt
jcmd <PID> GC.heap_info
ss -lntp | grep ':8080\|:8009'
curl -I http://127.0.0.1:8080/
tail -f $CATALINA_BASE/logs/catalina.out
thread dump는 한 번만 찍으면 순간 사진이다. 10초 간격으로 2~3번 찍어 같은 thread가 같은 DB 호출이나 외부 API 호출에 계속 머무는지 보는 편이 더 유용하다.
증상별 확인
| 증상 | 확인 지점 |
|---|---|
| 504 | thread 고갈, DB slow query, 외부 API 지연, 앞단 timeout |
| 503 | Tomcat down, Connector 한계, health check 실패 |
| 500 | application exception, DB connection 오류, 메모리 문제 |
| 간헐적 느림 | GC pause, 특정 API 지연, thread pool 대기 |
| OOM 후 재기동 | heap dump, GC log, systemd restart, catalina log |
관련 문서
관련 블로그 기록
- WAS가 죽기 전에 로그 남기기 — heap dump와 GC log를 남기도록 구성한 사례
- Tomcat Catalina.out 파일 용량 증가 문제 해결 — Tomcat 로그 운영 사례
공식 문서
- Apache Tomcat HTTP Connector
- Apache Tomcat AJP Connector
- Running The Apache Tomcat 9.0 Servlet/JSP Container
닫기 전 질문
- 앞단 Web Server가 바라보는 Tomcat 포트는 HTTP인가, AJP인가?
maxThreads, DB connection pool, 앞단 timeout은 서로 맞는가?- JVM 옵션은
setenv.sh, systemd, 서비스 wrapper 중 어디에서 주입되는가? - 장애가 났을 때 thread dump, GC log, heap dump가 남도록 준비되어 있는가?