티스토리 뷰

Java & Kotlin

[Java] 스프링 톰캣의 스레드 덤프를 통한 스레드 상태에 대한 이해(Thread State with Spring Application Tomcat Thread Dump)

망나니개발자 2024. 12. 3. 10:00
반응형

 

 

1. 자바 스레드의 여러 가지 상태들


[ 여러 종류의 스레드 상태들(Thread State) ]

자바 공식 문서(자바 23 기준)에 따르면 다음과 같은 자바 스레드 상태가 존재한다.

  • NEW
    • 스레드가 생성되었으나 아직 시작되지 않은 상태
  • RUNNABLE
    • 스레드가 실행 가능하여 운영 체제의 자원(예: 프로세서)을 기다리고 있거나 JVM 내에서 실행중인 상태
    • 운영체제의 스케줄러가 각 스레드에 CPU 시간을 할당하여 실행하므로, Runnable 상태의 스레드는 스케줄러의 실행 대기열에 포함되어 있다가 차례로 CPU에서 실행됨
  • BLOCKED
    • 스레드가 모니터 락(동기화 락)을 기다리며 블록된 상태
    • 이 상태의 스레드는 synchronized 블록/메서드에 들어가기 위해 또는 Object.wait 호출 후 다시 들어가기 위해 모니터 락을 기다림
  • WAITING
    • 무한정 대기중인 스레드
    • Object.wait (타임아웃 없음), Thread.join (타임아웃 없음), LockSupport.park 호출로 인해 무한 대기함
  • TIMED_WAITING
    • 스레드가 지정된 시간 동안 대기함
    • Thread.sleep, Object.wait (타임아웃 포함), Thread.join (타임아웃 포함), LockSupport.parkNanos, LockSupport.parkUntil 호출로 인해 무한 대기함
  • TERMINATED
    • 실행 완료되었거나 예외가 발생하여 종료된 스레드의 상태
    • 한 번 종료된 스레드는 다시 시작할 수 없음

 

 

 

[ 스레드 상태의 전이 ]

스레드는 기본적으로 생성(NEW)된 후에, 실제 요청을 처리하기 위한 상태(Runnable)가 된다. 이때 구체적으로는 실제로 CPU를 할당받아 실행중일 수도 있지만, 운영체제의 스케줄러로부터 리소스 할당을 기다리고 있을 수도 있다. Java에서는 이 둘을 구분할 수 없기 때문에 합쳐서 RUNNABLE로 표현된다. 그러다가 차단(Blocked), 대기(Waiting), 시간 제한 대기(Timed Waiting) 상태를 오가며 변하다가, 최종적으로 스레드가 종료되면 Terminated 상태가 된다.

김영한님의 자바 고급 1편을 참고하면 스레드의 상태 전이(Thread State Transfer) 과정을 이미지로 확인할 수 있다.

 

 

 



2. 스프링 톰캣의 스레드 덤프를 통한 스레드 상태에 대한 이해


[ 스레드 덤프(Thread Dump)에 대한 개념 및 사용법 ]

스레드 덤프(Thread Dump)는 실행 중인 프로그램의 모든 스레드 상태와 관련 정보를 캡처한 스냅샷으로, 애플리케이션의 디버깅이나 성능 문제를 분석할 때 매우 유용하다. 스레드 덤프에는 스레드 이름과 상태, 스택 트레이스, 모니터와 잠금 정보 등이 포함된다.

다음과 같은 명령어를 사용하면 thread_dump.txt 파일로 스레드 덤프를 뜰 수 있다. 여기서 8080은 포트 번호이고, thread_dump.txt는 덤프를 출력할 파일 이름에 해당한다.

jstack $(lsof -t -iTCP:8080 -sTCP:LISTEN) > thread_dump.txt

 

 

해당 명령어에 대해서 자세히 살펴보도록 하자. 해당 명령어는 8080 포트에서 다른 요청들을 기다리는(LISTEN) 자바 애플리케이션의 PID를 추출하고, 해당 PID를 바탕으로 스레드 덤프를 뜨는 명령어로 구성된다. 따라서 해당 명령어는 다음과 같이 2가지를 하나로 합쳤다고 볼 수 있다.

// 1. 8080 포트를 사용하는 pid 찾기
lsof -t -iTCP:8080 -sTCP:LISTEN

// 2. pid 바탕으로 덤프 뜨기
jstack <PID> > thread_dump.txt

 

 

lsof 명령어를 통해 프로세스를 조회할 때, 8080 포트에서 다른 요청들을 기다리는(LISTEN) 프로세스로 줄인 이유는 8080 포트를 사용하는 애플리케이션이 여럿 존재할 수 있기 때문이다. 예를 들어 8080 포트를 리스닝하는 서버, 8080 포트로 접속하여 요청을 처리하는 클라이언트 등이 존재할 수 있다. 만약 8080 포트에서 요청을 기다리는 프로세스로 좁히지 않는다면 jstack 파라미터로 여러 PID가 전달되어 다음과 같은 에러가 발생할 수 있다.

jstack $(lsof -t -i :8080) > thread_dump.txt
Error: More than one non-option argument
Cannot connect to core dump or remote debug server. Use jhsdb jstack instead

 

 

 

 

[ 스레드 덤프(Thread Dump)를 통한 상태 분석 ]

WAITING 상태의 스레드 덤프 떠보기

스프링 애플리케이션을 실행한 후에 스레드 덤프를 떠보면 다음과 같은 내용들을 확인할 수 있다. 이 중에서 “http-nio-8080-exec-*” 인 스레드는 톰캣(Tomcat)에서 HTTP 요청을 처리하기 위한 실행 스레드이다.

더보기

2024-11-20 23:06:30
Full thread dump OpenJDK 64-Bit Server VM (17.0.12+7-LTS mixed mode, emulated-client, sharing):

Threads class SMR info:
_java_thread_list=0x00006000007b1d80, length=30, elements={
0x000000013b009800, 0x000000013b00c000, 0x000000013b009e00, 0x000000013a809800,
0x000000014a82cc00, 0x000000013c00aa00, 0x000000013a809e00, 0x000000013a80a400,
0x000000013b00ae00, 0x000000013b00b400, 0x000000014b97a400, 0x000000014b979e00,
0x000000013a974a00, 0x000000014bbdde00, 0x000000013a9a0e00, 0x000000014b972c00,
0x000000014bbc1800, 0x000000014bbe2000, 0x000000014bbe2600, 0x000000013a974000,
0x000000013a96d800, 0x000000014bc00c00, 0x000000014bc01200, 0x000000014bc01800,
0x000000014bc01e00, 0x000000014b10d800, 0x000000014bbff200, 0x000000014bbfea00,
0x000000014b10e600, 0x000000013b14e600
}

"Reference Handler" #2 daemon prio=10 os_prio=31 cpu=1.41ms elapsed=1462.53s tid=0x000000013b009800 nid=0x4c03 waiting on condition  [0x000000016e902000]
   java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@17.0.12/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@17.0.12/Reference.java:253)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@17.0.12/Reference.java:215)

"Finalizer" #3 daemon prio=8 os_prio=31 cpu=0.13ms elapsed=1462.53s tid=0x000000013b00c000 nid=0x4903 in Object.wait()  [0x000000016eb0e000]
   java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@17.0.12/Native Method)
- waiting on <0x000000040001d420> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@17.0.12/ReferenceQueue.java:155)
- locked <0x000000040001d420> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@17.0.12/ReferenceQueue.java:176)
at java.lang.ref.Finalizer$FinalizerThread.run(java.base@17.0.12/Finalizer.java:172)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 cpu=0.39ms elapsed=1462.53s tid=0x000000013b009e00 nid=0x5a03 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #5 daemon prio=9 os_prio=31 cpu=1.89ms elapsed=1462.53s tid=0x000000013a809800 nid=0x5b03 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Deflation Thread" #6 daemon prio=9 os_prio=31 cpu=114.22ms elapsed=1462.53s tid=0x000000014a82cc00 nid=0x7803 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread0" #7 daemon prio=9 os_prio=31 cpu=379.51ms elapsed=1462.53s tid=0x000000013c00aa00 nid=0x5d03 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #11 daemon prio=9 os_prio=31 cpu=2.60ms elapsed=1462.53s tid=0x000000013a809e00 nid=0x7503 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #12 daemon prio=8 os_prio=31 cpu=2.71ms elapsed=1462.52s tid=0x000000013a80a400 nid=0x7303 in Object.wait()  [0x000000016f86e000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(java.base@17.0.12/Native Method)
- waiting on <0x00000004000300c8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@17.0.12/ReferenceQueue.java:155)
- locked <0x00000004000300c8> (a java.lang.ref.ReferenceQueue$Lock)
at jdk.internal.ref.CleanerImpl.run(java.base@17.0.12/CleanerImpl.java:140)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)
at jdk.internal.misc.InnocuousThread.run(java.base@17.0.12/InnocuousThread.java:162)

"Monitor Ctrl-Break" #13 daemon prio=5 os_prio=31 cpu=11.53ms elapsed=1462.48s tid=0x000000013b00ae00 nid=0x7003 runnable  [0x000000016fe92000]
   java.lang.Thread.State: RUNNABLE
at sun.nio.ch.SocketDispatcher.read0(java.base@17.0.12/Native Method)
at sun.nio.ch.SocketDispatcher.read(java.base@17.0.12/SocketDispatcher.java:47)
at sun.nio.ch.NioSocketImpl.tryRead(java.base@17.0.12/NioSocketImpl.java:266)
at sun.nio.ch.NioSocketImpl.implRead(java.base@17.0.12/NioSocketImpl.java:317)
at sun.nio.ch.NioSocketImpl.read(java.base@17.0.12/NioSocketImpl.java:355)
at sun.nio.ch.NioSocketImpl$1.read(java.base@17.0.12/NioSocketImpl.java:808)
at java.net.Socket$SocketInputStream.read(java.base@17.0.12/Socket.java:966)
at sun.nio.cs.StreamDecoder.readBytes(java.base@17.0.12/StreamDecoder.java:281)
at sun.nio.cs.StreamDecoder.implRead(java.base@17.0.12/StreamDecoder.java:324)
at sun.nio.cs.StreamDecoder.read(java.base@17.0.12/StreamDecoder.java:189)
- locked <0x000000040001acf8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(java.base@17.0.12/InputStreamReader.java:177)
at java.io.BufferedReader.fill(java.base@17.0.12/BufferedReader.java:162)
at java.io.BufferedReader.readLine(java.base@17.0.12/BufferedReader.java:329)
- locked <0x000000040001acf8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(java.base@17.0.12/BufferedReader.java:396)
at cohttp://m.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:55)

"Notification Thread" #14 daemon prio=9 os_prio=31 cpu=0.03ms elapsed=1462.48s tid=0x000000013b00b400 nid=0x6103 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #16 daemon prio=9 os_prio=31 cpu=24.92ms elapsed=1461.52s tid=0x000000014b97a400 nid=0x6303 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"RMI TCP Accept-0" #17 daemon prio=5 os_prio=31 cpu=0.91ms elapsed=1457.39s tid=0x000000014b979e00 nid=0x5f07 runnable  [0x000000016fc86000]
   java.lang.Thread.State: RUNNABLE
at sun.nio.ch.Net.accept(java.base@17.0.12/Native Method)
at sun.nio.ch.NioSocketImpl.accept(java.base@17.0.12/NioSocketImpl.java:760)
at java.net.ServerSocket.implAccept(java.base@17.0.12/ServerSocket.java:675)
at java.net.ServerSocket.platformImplAccept(java.base@17.0.12/ServerSocket.java:641)
at java.net.ServerSocket.implAccept(java.base@17.0.12/ServerSocket.java:617)
at java.net.ServerSocket.implAccept(java.base@17.0.12/ServerSocket.java:574)
at java.net.ServerSocket.accept(java.base@17.0.12/ServerSocket.java:532)
at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(jdk.management.agent@17.0.12/LocalRMIServerSocketFactory.java:52)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(java.rmi@17.0.12/TCPTransport.java:413)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(java.rmi@17.0.12/TCPTransport.java:377)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"HikariPool-1 housekeeper" #21 daemon prio=5 os_prio=31 cpu=9.00ms elapsed=1456.29s tid=0x000000013a974a00 nid=0x580b waiting on condition  [0x0000000170ada000]
   java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x0000000401099000> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17.0.12/LockSupport.java:252)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@17.0.12/AbstractQueuedSynchronizer.java:1674)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:1182)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:899)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.12/ThreadPoolExecutor.java:1062)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.12/ThreadPoolExecutor.java:1122)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.12/ThreadPoolExecutor.java:635)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"Catalina-utility-1" #24 prio=1 os_prio=31 cpu=138.63ms elapsed=1455.51s tid=0x000000014bbdde00 nid=0x9f03 waiting on condition  [0x000000017236a000]
   java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000040109c0a8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17.0.12/LockSupport.java:252)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@17.0.12/AbstractQueuedSynchronizer.java:1674)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:1182)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:899)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.12/ThreadPoolExecutor.java:1062)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.12/ThreadPoolExecutor.java:1122)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.12/ThreadPoolExecutor.java:635)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"Catalina-utility-2" #25 prio=1 os_prio=31 cpu=128.03ms elapsed=1455.51s tid=0x000000013a9a0e00 nid=0x9e03 waiting on condition  [0x0000000172576000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000040109c0a8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:1177)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:899)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.12/ThreadPoolExecutor.java:1062)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.12/ThreadPoolExecutor.java:1122)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.12/ThreadPoolExecutor.java:635)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"container-0" #26 prio=5 os_prio=31 cpu=4.53ms elapsed=1455.51s tid=0x000000014b972c00 nid=0x9d03 waiting on condition  [0x0000000172782000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@17.0.12/Native Method)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:520)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:219)

"http-nio-8080-exec-1" #27 daemon prio=5 os_prio=31 cpu=13434.10ms elapsed=1455.22s tid=0x000000014bbc1800 nid=0x8d03 waiting on condition  [0x000000017298e000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-2" #28 daemon prio=5 os_prio=31 cpu=3.29ms elapsed=1455.22s tid=0x000000014bbe2000 nid=0x8e03 waiting on condition  [0x0000000172b9a000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-3" #29 daemon prio=5 os_prio=31 cpu=0.45ms elapsed=1455.22s tid=0x000000014bbe2600 nid=0x9a03 waiting on condition  [0x0000000172da6000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-4" #30 daemon prio=5 os_prio=31 cpu=14.31ms elapsed=1455.22s tid=0x000000013a974000 nid=0x9003 waiting on condition  [0x0000000172fb2000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-5" #31 daemon prio=5 os_prio=31 cpu=1.70ms elapsed=1455.22s tid=0x000000013a96d800 nid=0x9903 waiting on condition  [0x00000001731be000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-6" #32 daemon prio=5 os_prio=31 cpu=1.85ms elapsed=1455.22s tid=0x000000014bc00c00 nid=0x9703 waiting on condition  [0x00000001733ca000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-7" #33 daemon prio=5 os_prio=31 cpu=1.77ms elapsed=1455.22s tid=0x000000014bc01200 nid=0x9303 waiting on condition  [0x00000001735d6000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-8" #34 daemon prio=5 os_prio=31 cpu=1.09ms elapsed=1455.22s tid=0x000000014bc01800 nid=0x9603 waiting on condition  [0x00000001737e2000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-9" #35 daemon prio=5 os_prio=31 cpu=7.87ms elapsed=1455.22s tid=0x000000014bc01e00 nid=0xaa03 waiting on condition  [0x00000001739ee000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-exec-10" #36 daemon prio=5 os_prio=31 cpu=0.30ms elapsed=1455.22s tid=0x000000014b10d800 nid=0xac03 waiting on condition  [0x0000000173bfa000]
   java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-Poller" #37 daemon prio=5 os_prio=31 cpu=106.14ms elapsed=1455.21s tid=0x000000014bbff200 nid=0xff03 runnable  [0x0000000173e06000]
   java.lang.Thread.State: RUNNABLE
at sun.nio.ch.KQueue.poll(java.base@17.0.12/Native Method)
at sun.nio.ch.KQueueSelectorImpl.doSelect(java.base@17.0.12/KQueueSelectorImpl.java:122)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@17.0.12/SelectorImpl.java:129)
- locked <0x000000043ef2e4d0> (a sun.nio.ch.Util$2)
- locked <0x000000043ef2e2e0> (a sun.nio.ch.KQueueSelectorImpl)
at sun.nio.ch.SelectorImpl.select(java.base@17.0.12/SelectorImpl.java:141)
at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:755)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"http-nio-8080-Acceptor" #38 daemon prio=5 os_prio=31 cpu=12.78ms elapsed=1455.21s tid=0x000000014bbfea00 nid=0xfd03 runnable  [0x0000000174012000]
   java.lang.Thread.State: RUNNABLE
at sun.nio.ch.Net.accept(java.base@17.0.12/Native Method)
at sun.nio.ch.ServerSocketChannelImpl.implAccept(java.base@17.0.12/ServerSocketChannelImpl.java:425)
at sun.nio.ch.ServerSocketChannelImpl.accept(java.base@17.0.12/ServerSocketChannelImpl.java:391)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:518)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:71)
at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:128)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"scheduling-1" #39 prio=5 os_prio=31 cpu=272.56ms elapsed=1455.21s tid=0x000000014b10e600 nid=0xae03 waiting on condition  [0x000000017421e000]
   java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for  <0x000000043eb4f3a0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17.0.12/LockSupport.java:252)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@17.0.12/AbstractQueuedSynchronizer.java:1674)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:1182)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@17.0.12/ScheduledThreadPoolExecutor.java:899)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.12/ThreadPoolExecutor.java:1062)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.12/ThreadPoolExecutor.java:1122)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.12/ThreadPoolExecutor.java:635)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

"DestroyJavaVM" #40 prio=5 os_prio=31 cpu=2237.94ms elapsed=1455.21s tid=0x000000013b14e600 nid=0x2203 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=31 cpu=42.08ms elapsed=1462.53s tid=0x000000013b804740 nid=0x4e03 runnable  

"GC Thread#0" os_prio=31 cpu=18.16ms elapsed=1462.54s tid=0x000000014a607730 nid=0x3b03 runnable  

"GC Thread#1" os_prio=31 cpu=25.07ms elapsed=1457.18s tid=0x000000013a615ec0 nid=0x6a03 runnable  

"GC Thread#2" os_prio=31 cpu=22.22ms elapsed=1457.18s tid=0x000000013a616ab0 nid=0x6803 runnable  

"GC Thread#3" os_prio=31 cpu=10.11ms elapsed=1457.18s tid=0x000000013a616f30 nid=0x8103 runnable  

"GC Thread#4" os_prio=31 cpu=21.65ms elapsed=1457.18s tid=0x000000013a6173b0 nid=0xa803 runnable  

"GC Thread#5" os_prio=31 cpu=12.61ms elapsed=1457.18s tid=0x000000013a617830 nid=0xa603 runnable  

"GC Thread#6" os_prio=31 cpu=12.16ms elapsed=1457.18s tid=0x000000014a632ea0 nid=0xa403 runnable  

"GC Thread#7" os_prio=31 cpu=19.82ms elapsed=1457.18s tid=0x000000014a6338e0 nid=0xa303 runnable  

"GC Thread#8" os_prio=31 cpu=16.19ms elapsed=1457.18s tid=0x000000013a6180a0 nid=0xa203 runnable  

"G1 Main Marker" os_prio=31 cpu=0.45ms elapsed=1462.54s tid=0x000000013a7043c0 nid=0x3903 runnable  

"G1 Conc#0" os_prio=31 cpu=35.96ms elapsed=1462.54s tid=0x000000013a704c50 nid=0x3603 runnable  

"G1 Conc#1" os_prio=31 cpu=33.45ms elapsed=1457.04s tid=0x000000013a62aad0 nid=0x8507 runnable  

"G1 Refine#0" os_prio=31 cpu=3.94ms elapsed=1462.54s tid=0x000000014a6095b0 nid=0x5303 runnable  

"G1 Refine#1" os_prio=31 cpu=0.39ms elapsed=1455.76s tid=0x000000014a732760 nid=0xa007 runnable  

"G1 Service" os_prio=31 cpu=181.07ms elapsed=1462.54s tid=0x000000013a7054c0 nid=0x5103 runnable  

"VM Periodic Task Thread" os_prio=31 cpu=645.47ms elapsed=1457.38s tid=0x000000014a61d0d0 nid=0x6403 waiting on condition  

JNI global refs: 16, weak refs: 0

 

 

먼저 확인 가능한 스레드들은 JVM의 스레드들로 finalize 처리 혹은 reference 처리 등을 담당하고 있다. 해당 스레드들은 daemon 키워드가 존재하므로 데몬 스레드에 해당된다.

자바 애플리케이션을 종료한다고 하면, JVM은 일반적인 스레드들이 종료되기를 기다린 후에 종료된다. 하지만 만약 해당 스레드가 데몬 스레드라면 JVM이 실행 완료를 기다리지 않고, 실행중인 데몬 스레드가 있어도 자바 프로그램을 종료시킨다.

"Reference Handler" #2 daemon prio=10 os_prio=31 cpu=1.41ms elapsed=1462.53s tid=0x000000013b009800 nid=0x4c03 waiting on condition  [0x000000016e902000]
   java.lang.Thread.State: RUNNABLE
	at java.lang.ref.Reference.waitForReferencePendingList(java.base@17.0.12/Native Method)
	at java.lang.ref.Reference.processPendingReferences(java.base@17.0.12/Reference.java:253)
	at java.lang.ref.Reference$ReferenceHandler.run(java.base@17.0.12/Reference.java:215)

 

 

이 중에서 "http-nio-8080-exec-1"의 경우에는 다음과 같은 출력이 존재함을 확인할 수 있다.

"http-nio-8080-exec-1" #27 daemon prio=5 os_prio=31 cpu=13434.10ms elapsed=1455.22s tid=0x000000014bbc1800 nid=0x8d03 waiting on condition  [0x000000017298e000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
	- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

 

 

스레드 덤프를 보면 스택 트레이스와 함께 CPU 사용량(cpu), 처리 지속 시간(elapsed) 등의 정보와 스레드의 상태(Thread.State)가 출력됨을 확인할 수 있는데, 자세한 내용은 다음과 같다.

  • 이름: http-nio-8080-exec-1
  • 상태: WAITING
  • 스레드 ID: 0x000000014bbc1800
  • 우선순위: prio=5 (기본 우선순위)
  • 운영체제 스레드 ID: nid=0x8d03
  • CPU 사용 시간: 13434.10ms (13초 정도)
  • 경과 시간: 1455.22s (24분 정도 스레드가 실행 중)

 

 

이때 스레드는 WAITING 상태임을 확인할 수 있는데, Unsafe.park() 메서드로 대기 상태에 들어갔으며, 이는 다른 스레드가 신호를 보내기 전까지 멈춰 있음을 의미한다. 해당 부분은 톰캣의 동작과 관련이 있는데 자세히 살펴보도록 하자.

  1. 해당 스레드는 스레드 풀(ThreadPoolExecutor)에서 새롭게 생성 및 관리됨
  2. 생성 후에 작업 큐(TaskQueue)에서 작업을 가져오려고 시도함
  3. 하지만 작업 큐에 처리할 작업이 없어서 스레드가 블로킹 상태로 대기함

 

 

요청이 언제 들어올지 기대할 수 없기에 기본적으로 정해진 시점 없이 무한정 대기하여, 해당 스레드는 WAITING 상태가 된다. 그러다가 클라이언트로부터 새로운 요청이 인입되면 해당 스레드가 깨어나면서 상태가 바뀌게될 것이다. 이번에는 스레드가 작업을 처리 중인 경우의 상태 변화에 대하여 살펴보도록 하자.

 

 

 

RUNNABLE 상태의 스레드 덤프 떠보기

다음과 같이 CPU를 활용해 연산 중인 스레드가 존재하는 경우에 스레드 덤프를 떠보도록 하자.

@RestController
class ThreadController {

    @GetMapping("/runnable")
    public long runnable() {
        return LongStream.rangeClosed(0, 10000000000L).sum();
    }
}

 

 

스레드 덤프를 확인해보면 CPU를 활용하여 연산을 처리중이기 때문에 스레드가 RUNNABLE 상태임을 확인할 수 있다.

"http-nio-8080-exec-1" #27 daemon prio=5 os_prio=31 cpu=3609.56ms elapsed=8.42s tid=0x000000014bbc1800 nid=0x8d03 runnable  [0x000000017298b000]
   java.lang.Thread.State: RUNNABLE
	at java.util.stream.Streams$RangeLongSpliterator.forEachRemaining(java.base@17.0.12/Streams.java:228)
	at java.util.Spliterator$OfLong.forEachRemaining(java.base@17.0.12/Spliterator.java:775)
	at java.util.stream.AbstractPipeline.copyInto(java.base@17.0.12/AbstractPipeline.java:509)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(java.base@17.0.12/AbstractPipeline.java:499)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(java.base@17.0.12/ReduceOps.java:921)
	at java.util.stream.AbstractPipeline.evaluate(java.base@17.0.12/AbstractPipeline.java:234)
	at java.util.stream.LongPipeline.reduce(java.base@17.0.12/LongPipeline.java:498)
	at java.util.stream.LongPipeline.sum(java.base@17.0.12/LongPipeline.java:456)
	at com.mangkyu.thread.ThreadController.runnable(ThreadController.java:13)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17.0.12/Native Method)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17.0.12/NativeMethodAccessorImpl.java:77)
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17.0.12/DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(java.base@17.0.12/Method.java:569)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

 

 

 

TIMED_WATING 상태의 스레드 덤프 떠보기

애플리케이션 로직 중에는 외부 API 호출을 기다리거나 DB 호출을 하는 경우가 생기는데, 이때 스레드는 다른 요청들을 기다리게 된다. 따라서 다음과 같이 다른 요청을 기다리는 상황을 Thread.sleep()으로 가정하고 스레드 덤프를 떠보도록 하자.

@RestController
class ThreadController {

    @GetMapping("/timed-waiting")
    public long timedWaiting() throws InterruptedException {
        Thread.sleep(10000000000L);
        return 10000000000L;
    }
}

 

 

그러면 주어진 시간 만큼 다른 작업을 대기하게 되므로 TIMED_WATING 상태가 되었음을 확인할 수 있다.

"http-nio-8080-exec-1" #32 daemon prio=5 os_prio=31 cpu=57.04ms elapsed=8.41s tid=0x000000012729d200 nid=0x9707 waiting on condition  [0x00000003024d0000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(java.base@17.0.12/Native Method)
	at com.mangkyu.thread.ThreadController.timedWaiting(ThreadController.java:18)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17.0.12/Native Method)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17.0.12/NativeMethodAccessorImpl.java:77)
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17.0.12/DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(java.base@17.0.12/Method.java:569)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

 

 

 

[ 톰캣 작업 스레드의 상태 전이(Tomcat Worker Thread State Transfer) ]

톰캣 스레드 풀 설정에 대한 이해

스레드의 경우 생성 비용이 매우 비싸기 때문에, 매 번 요청이 들어올 때마다 새롭게 스레드를 생성하고 실행하면 병목이 될 수 있다. 따라서 스프링 애플리케이션에서는 요청을 처리하기 위한 스레드들을 생성하여 스레드 풀에 대기 상태로 두었다가, 요청이 들어오면 스레드 풀에서 스레드를 꺼내서 재사용하도록 한다. 대표적으로 톰캣은 요청을 처리하기 위한 스레드를 스레드 풀에 생성해두고, 데이터 소스의 경우 커넥션을 미리 생성하여 커넥션 풀에 넣어두고 재사용할 수 있도록 한다. 스프링 부트 공식 문서를 보면 톰캣의 스레드 풀을 위한 설정으로 스레드 min-spare, max 설정 등이 존재하는데, 각각 다음과 같다.

  • server.tomcat.threads.min-spare
    • 최소 worker 스레드의 수, 기본값 10
    • 가상 스레드가 활성화된 경우에는 적용되지 않음
  • server.tomcat.threads.max
    • 최대 worker 스레드의 수, 기본값 200
    • 가상 스레드가 활성화된 경우에는 적용되지 않음

 

 

스프링 부트 애플리케이션이 실행되면 톰캣은 기본적으로 min-spare 숫자로 스레드 풀을 생성한다. 그러다가 요청이 min-spare 스레드 숫자로 감당하기 어려워져 요청이 밀리게 되면, 최대 max 숫자까지 스레드의 수를 늘리게 된다.

 

 

 

톰캣 스레드가 증가한 경우에 작업 스레드의 상태 변경

만약 min-spare 스레드 숫자로 요청을 감당하기 어려워서 스레드의 숫자가 증가했다면 다음과 같이 waiting 상태의 스레드가 줄어들고, timed-waiting 상태의 스레드가 증가함을 확인할 수 있다.

 

 

위와 같은 상황이 되었을 때 스레드 덤프를 떠보면 다음과 같은 스택 트레이스를 확인할 수 있다.

"http-nio-8080-exec-1" #964 daemon prio=5 os_prio=0 cpu=100026.33ms elapsed=34773.79s tid=0x00007fb9dea057e0 nid=0x7fc waiting on condition  [0x00007fb92c2d3000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.9/Native Method)
	- parking to wait for  <0x0000000712423c30> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17.0.9/LockSupport.java:252)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@17.0.9/AbstractQueuedSynchronizer.java:1672)
	at java.util.concurrent.LinkedBlockingQueue.poll(java.base@17.0.9/LinkedBlockingQueue.java:460)
	at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:128)
	at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:33)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1113)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(java.base@17.0.9/Thread.java:840)

 

 

이전의 스택 트레이스에서는 WAITING 상태였던 데에 비해 TIMED_WAITING 상태였던 것과 다르게

"http-nio-8080-exec-1" #27 daemon prio=5 os_prio=31 cpu=13434.10ms elapsed=1455.22s tid=0x000000014bbc1800 nid=0x8d03 waiting on condition  [0x000000017298e000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
	- parking to wait for  <0x000000043ed50390> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:341)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.12/AbstractQueuedSynchronizer.java:506)
	at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.12/ForkJoinPool.java:3465)
	at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.12/ForkJoinPool.java:3436)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.12/AbstractQueuedSynchronizer.java:1625)
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@17.0.12/LinkedBlockingQueue.java:435)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:117)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1175)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

 

 

이렇게 스레드의 상태가 변경되는 이유는 자바 스레드 풀의 구현에 따라 톰캣 스레드 풀의 스케줄링 알고리즘이 변경하기 때문이다. 자바 21을 기준으로 보면 ThreadPoolExecutor 내부에서 현재의 작업 스레드가 corePoolSize 보다 크거나 많다면 nanoSeconds 단위의 keepAliveTime 만큼 스레드를 꺼내오도록 대기한다. 해당 부분은 poll(keepAliveTime, TimeUnit.NANOSECONDS) 메서드 부분에 해당한다. 특정한 시간 만큼 스레드가 기다리게 되므로 TIMED_WAITING 상태가 되는 것이다. 만약 그렇지 않은 상황이라면 스레드들은 take()에서 보이듯이 대기 시간 없이 기다릴 것이므로, WAITING 상태가 될 것이다.

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();

        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

 

 

따라서 min-spare보다 요청이 많아져서 추가적인 스레드가 생성된다면, WAITING 상태가 아닌 TIMED_WAITING 상태의 스레드가 급증하는 것이며, 이러한 부분은 정상적인 상황이기에 문제로 삼을 부분은 없다. 참고로 min-spare과 max의 수치가 동일하다면 busy 스레드가 max까지 찬 상황에서도 요청을 처리할 수 없으므로 min-spare에 비례하여 max도 함께 챙겨주는 것이 좋다. 일반화 할 수는 없지만 기본적으로 다음과 같이 접근해주는 것이 좋다.

  • TPS가 80보다 적은 경우
    • min-spare: 80
    • max: 200
  • TPS가 80이상인 적은 경우
    • min-spare: 200
    • max: 400

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함