1. 목적


2. 환경

  • Oracle Java SE Development Kit 8u151
  • Apache Tomcat 8.5

3. 실행

3.1. 기본 (default 설정)

  • 기동 후 30분간 아무런 부하도 넣지 않고 지속적으로 jstack을 이용하여 Thread Pool 모니터링

3.1.1. http 스레드

"http-nio-8080-exec-10" #26 daemon prio=5 os_prio=0 tid=0x00007fcaa8474000 nid=0x5b1f waiting on condition [0x00007fca77ffe000]
"http-nio-8080-exec-9" #25 daemon prio=5 os_prio=0 tid=0x00007fcaa8472000 nid=0x5b1e waiting on condition [0x00007fca8416b000]
"http-nio-8080-exec-8" #24 daemon prio=5 os_prio=0 tid=0x00007fcaa8470000 nid=0x5b1d waiting on condition [0x00007fca8426c000]
"http-nio-8080-exec-7" #23 daemon prio=5 os_prio=0 tid=0x00007fcaa846e000 nid=0x5b1c waiting on condition [0x00007fca8436d000]
"http-nio-8080-exec-6" #22 daemon prio=5 os_prio=0 tid=0x00007fcaa846c000 nid=0x5b1b waiting on condition [0x00007fca8446e000]
"http-nio-8080-exec-5" #21 daemon prio=5 os_prio=0 tid=0x00007fcaa846a000 nid=0x5b1a waiting on condition [0x00007fca8456f000]
"http-nio-8080-exec-4" #20 daemon prio=5 os_prio=0 tid=0x00007fcaa8468000 nid=0x5b19 waiting on condition [0x00007fca84670000]
"http-nio-8080-exec-3" #19 daemon prio=5 os_prio=0 tid=0x00007fcaa8466800 nid=0x5b18 waiting on condition [0x00007fca84771000]
"http-nio-8080-exec-2" #18 daemon prio=5 os_prio=0 tid=0x00007fcaa8464800 nid=0x5b17 waiting on condition [0x00007fca84872000]
"http-nio-8080-exec-1" #17 daemon prio=5 os_prio=0 tid=0x00007fcaa8463000 nid=0x5b16 waiting on condition [0x00007fca84973000]

3.1.2. ajp 스레드

"ajp-nio-8009-exec-10" #39 daemon prio=5 os_prio=0 tid=0x00007fcaa849e000 nid=0x5b2c waiting on condition [0x00007fca772f1000]
"ajp-nio-8009-exec-9" #38 daemon prio=5 os_prio=0 tid=0x00007fcaa849c000 nid=0x5b2b waiting on condition [0x00007fca773f2000]
"ajp-nio-8009-exec-8" #37 daemon prio=5 os_prio=0 tid=0x00007fcaa849a000 nid=0x5b2a waiting on condition [0x00007fca774f3000]
"ajp-nio-8009-exec-7" #36 daemon prio=5 os_prio=0 tid=0x00007fcaa8498000 nid=0x5b29 waiting on condition [0x00007fca775f4000]
"ajp-nio-8009-exec-6" #35 daemon prio=5 os_prio=0 tid=0x00007fcaa8496000 nid=0x5b28 waiting on condition [0x00007fca776f5000]
"ajp-nio-8009-exec-5" #34 daemon prio=5 os_prio=0 tid=0x00007fcaa8494000 nid=0x5b27 waiting on condition [0x00007fca777f6000]
"ajp-nio-8009-exec-4" #33 daemon prio=5 os_prio=0 tid=0x00007fcaa8492000 nid=0x5b26 waiting on condition [0x00007fca778f7000]
"ajp-nio-8009-exec-3" #32 daemon prio=5 os_prio=0 tid=0x00007fcaa8490000 nid=0x5b25 waiting on condition [0x00007fca779f8000]
"ajp-nio-8009-exec-2" #31 daemon prio=5 os_prio=0 tid=0x00007fcaa848e000 nid=0x5b24 waiting on condition [0x00007fca77af9000]
"ajp-nio-8009-exec-1" #30 daemon prio=5 os_prio=0 tid=0x00007fcaa848c000 nid=0x5b23 waiting on condition [0x00007fca77bfa000]

3.1.3. 결론

  • 각 Connector별로 10개의 스레드 생성된다.
  • 이것은 minSpareThreads(The minimum number of threads always kept running. If not specified, the default of 10 is used.) 설정에 의한 것이다. (https://tomcat.apache.org/tomcat-8.5-doc/config/http.html)
  • 그런데 주의할 점이 있다. Tomcat 5.5 시절에는 minSpareThreads의 기본 값이 4였다. 간혹 Tomcat HTTP Connector로 검색하였는데 5.5 도큐먼트가 구글링되는 경우가 있다. (https://tomcat.apache.org/tomcat-5.5-doc/config/http.html) 문서 버전을 확인하지 않고 내용만 확인하여 잘못된 기본 값을 알아가는 실수를 범하기 쉬우니 주의하자!

3.2. minSpareThreads 설정 후 확인

  • 각 Connector 설정에 명시적으로 minSpareThreads 설정을 적용한다.
  • 확인 결과 기동 후 minSpareThreads 수 만큼의 스레드가 생성되며 유지된다. (예: 20개, 30개)

4. 의문점

Thread Pool 내 Thread 생성과 minThreads (http://sarc.io/index.php/tomcat/321-thread-pool-thread-minthreads) 문서에서는 AJP Thread Pool의 경우 요청이 없으면 스레드가 생성되지 않는다고 하였다. 그러나 위 테스트에서는 AJP Connector이거나 HTTP Connector이거나 동일하게 minSpareThreads만큼의 스레드가 생성되었음을 알 수 있다.

그런데 해당 문서에서는 apr 방식의 Connector를 사용하고 있던 것 같다. 그래서 동일하게 apr Connector로 테스트를 해보려고 한다.

4.1. APR

우선 APR (Apache Portable Runtime) 및 Tomcat(톰캣) Native Library 설치 & 설정 (http://sarc.io/index.php/tomcat/899-apr-apache-portable-runtime-tomcat-native-library) 문서를 참조하여 Tomcat Native를 설치하였다.

다만 Tomcat Native만 설치한다고 해서 apr 방식의 Connector로 기동되는 것은 아니다. Connector 별로 protocol을 맞추어줘야 한다.

  • HTTP : org.apache.coyote.http11.Http11AprProtocol
  • AJP : org.apache.coyote.ajp.AjpAprProtocol

혹은 AprLifecycleListener에 useAprConnector를 true로 설정하면 protocol이 HTTP/1.1, AJP/1.3로 설정되어 있을 때 자동으로 apr 방식으로 전환된다.

<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" useAprConnector="true" />

다시 정리해보자. apr 방식을 사용하는 방법이다.

  1. Tomcat Connector를 설치한 후 각 Connector의 protocol을 org.apache.coyote.http11.Http11AprProtocol 혹은 org.apache.coyote.ajp.AjpAprProtocol으로 설정한다.
  2. Tomcat Connector를 설치한 후 AprLifecycleListener에 useAprConnector="true"를 설정한다. 단 protocol이 HTTP/1.1 혹은 AJP/1.3 으로 설정되어 있을 때에 한한다.
09-Jan-2018 10:47:53.470 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-apr-8080"]
09-Jan-2018 10:47:53.493 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-apr-8009"]

4.2. Tomcat 8.5 & APR 테스트 결과

역시 apr 방식 Connector라고 하더라도 기본적으로 minSpareThreads 만큼의 스레드가 생성되는 것을 확인할 수 있었다.

4.3. Tomcat 7.0에서는? w/ APR

그런데 Thread Pool 내 Thread 생성과 minThreads 문서에서는 Tomcat 7.0.70 기반으로 테스트를 했었다. 무언가 Tomcat 버전에 따라 다를 수도 있겠다는 생각이 든다.

그래서 Tomcat 7.0.70 상에서 추가로 테스트를 해보려고 한다.

Jan 09, 2018 12:33:57 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-apr-8080"]
Jan 09, 2018 12:33:57 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-apr-8009"]

그리고 결과는...

apr 방식으로 설정된 HTTP Connector, AJP Connector 모두 기본적으로 어떠한 스레드도 생성하지 않았다. (단, AsyncTimeout, Acceptor, Poller 등 제외)

4.3.1. HTTP Connector 스레드

"http-apr-8080-AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007efc584d6800 nid=0x7151 waiting on condition [0x00007efc310b9000]
"http-apr-8080-Acceptor-0" #16 daemon prio=5 os_prio=0 tid=0x00007efc584d5800 nid=0x7150 runnable [0x00007efc311ba000]
"http-apr-8080-Sendfile" #15 daemon prio=5 os_prio=0 tid=0x00007efc584d3800 nid=0x714f in Object.wait() [0x00007efc312bb000]
"http-apr-8080-Poller" #14 daemon prio=5 os_prio=0 tid=0x00007efc584bd800 nid=0x714e in Object.wait() [0x00007efc313bc000]

4.3.2. AJP Connector 스레드

"ajp-apr-8009-AsyncTimeout" #20 daemon prio=5 os_prio=0 tid=0x00007efc5855a000 nid=0x7154 waiting on condition [0x00007efc30db6000]
"ajp-apr-8009-Acceptor-0" #19 daemon prio=5 os_prio=0 tid=0x00007efc58558000 nid=0x7153 runnable [0x00007efc30eb7000]
"ajp-apr-8009-Poller" #18 daemon prio=5 os_prio=0 tid=0x00007efc58556000 nid=0x7152 in Object.wait() [0x00007efc30fb8000]

4.4. Tomcat 7.0에서는? w/ BIO

그럼 apr이 아닌 bio에서는 어떨까?

4.4.1. HTTP Connector 스레드

"http-bio-8080-AsyncTimeout" #15 daemon prio=5 os_prio=0 tid=0x00007efcf43fb000 nid=0x71f4 waiting on condition [0x00007efce48fb000]
"http-bio-8080-Acceptor-0" #14 daemon prio=5 os_prio=0 tid=0x00007efcf43f9000 nid=0x71f3 runnable [0x00007efce49fc000]

역시 기본적으로 스레드가 생성되지 않았다. apr에서 보였던 Sendfile, Poller 스레드도 bio에서는 없다.

4.4.2. AJP Connector 스레드

"ajp-bio-8009-AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007efcf43fe000 nid=0x71f6 waiting on condition [0x00007efce46f9000]
"ajp-bio-8009-Acceptor-0" #16 daemon prio=5 os_prio=0 tid=0x00007efcf43fc800 nid=0x71f5 runnable [0x00007efce47fa000]

역시나이고, apr에서 보였던 Poller 스레드도 bio에서는 없다.

4.5. 의문점에 대한 결론

명확하게 의문점이 해소되지는 않았다. 다만 확인한 것은 다음과 같다.

  • Tomcat 8.5에서는 기동시 각 Connector 별로 minSpareThreads 만큼의 exec 스레드가 생성되었다.
  • Tomcat 7.0에서는 기동시 각 Connector 별로 0개의 exec 스레드가 생성되었다. 

해당 문서에서 도출된 결론이 어떠한 설정에 의해 나온 것인가.. 궁금할 뿐이다.


5. 참고 (Stack 생성)

5.1. 자바

  • jstack 사용
  • JVMRuntimeClient

5.2. OS

  • pstack
  • gdp -p / set pagination 0 / set width 1000 / thread apply all bit / detach

5.3. Windows

  • cdb