In this topic, we have prepared some very important info concerning thread monitoring: you will explore the basics of the mechanism for performing thread debugging known as a thread dump. We will show you how to generate and read thread dumps and what kind of issues you can investigate with it.
How to get a thread dump
A thread dump provides us with a snapshot of all threads' stack traces in a Java process (in other words, a Java application), along with some additional information. There are different ways to generate a thread dump but we will focus on just two of them:
IntelliJ IDEA. If you are using this IDE, you can generate a thread dump without any extra tools. When you run the application in debug mode, you will find a camera icon in the debug panel. Once you hit it, you will get a thread dump for the given execution point. You can do the same thing from the IDE navbar: go to the Run section, choose Debugging Actions. There you will find a Get Thread Dump option.
jstack. This is a built-in Java command-line utility you can find inside the bin directory of the JDK files. The
jstackcommand has several options (flags) to print thread dumps of Java threads for a specified process. Thejstack [option] pidis a structure of the command which will show the required information of threads attached to the given process, wherepidis a process ID. This is the user manual of thejstackcommand:Usage: jstack [-l][-e] pid (to connect to running process) Options: -l long listing. Prints additional information about locks -e extended listing. Prints additional information about threads -? -h --help -help to print this help message
Getting a thread dump is the first step to mastering this important mechanism. We still have to figure out what kind of information we can get from it. Let's move on to the next section, and study the thread dump structure.
How to read a thread dump
To show you what a thread dump looks like, we will use a Java process itself. Since the message will be quite lengthy, we can't show it in its entirety. At first glance, the message may look confusing, but we will provide explanations where we think you might need them.
Below, you see the result of the jstack -l pid command. Note that pid is not a constant value. If you want to try the same command, you must find the required process ID on your machine.
- Section 1 -
2022-03-24 18:38:37
Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.1+12-LTS-39 mixed mode, sharing):
- Section 2 -
Threads class SMR info:
_java_thread_list=0x00000239fb521fc0, length=13, elements={
0x00000239ddbf9430, 0x00000239ddbfa1b0, 0x00000239fa0beef0, 0x00000239fa0c17c0,
0x00000239fa0c2080, 0x00000239fa0c54f0, 0x00000239fa0cca00, 0x00000239fa0cdb50,
0x00000239fa0e1b60, 0x00000239fac70440, 0x00000239fac7cb70, 0x00000239fb0412d0,
0x00000239ddb2d280
}
- Section 3 -
"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=65.65s tid=0x00000239ddbf9430
nid=0x3e3c waiting on condition [0x00000042fcbff000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Native Method)
at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:253)
at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:215)
Locked ownable synchronizers:
- None
.......................................
Other Threads
.......................................
"DestroyJavaVM" #16 prio=5 os_prio=0 cpu=1218.75ms elapsed=64.15s tid=0x00000239ddb2d280
nid=0x4c4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
- Section 4 -
"VM Thread" os_prio=2 cpu=0.00ms elapsed=65.65s tid=0x00000239ddbf18a0 nid=0x22c0 runnable
"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=65.66s tid=0x00000239ddb55b00 nid=0x12cc runnable
"GC Thread#1" os_prio=2 cpu=0.00ms elapsed=65.02s tid=0x00000239facab240 nid=0x235c runnable
"GC Thread#2" os_prio=2 cpu=0.00ms elapsed=65.02s tid=0x00000239fb0e7030 nid=0x70c runnable
"GC Thread#3" os_prio=2 cpu=0.00ms elapsed=65.02s tid=0x00000239fb0c4ec0 nid=0x3d60 runnable
"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=65.66s tid=0x00000239ddb5d0e0 nid=0x2384 runnable
"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=65.66s tid=0x00000239ddb5da00 nid=0x4054 runnable
"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=65.66s tid=0x00000239ddbd1fe0 nid=0x1e80 runnable
"G1 Refine#1" os_prio=2 cpu=0.00ms elapsed=65.02s tid=0x00000239fb071b10 nid=0x3668 runnable
"G1 Service" os_prio=2 cpu=0.00ms elapsed=65.66s tid=0x00000239ddbd3920 nid=0x39f0 runnable
"VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=65.60s tid=0x00000239fac81060 nid=0x3410
waiting on condition
JNI global refs: 18, weak refs: 0We divided the message into four sections. The first section shows when the dump was printed and provides general information about the JVM. The next section shows the list of threads inside the process, which is followed by a detailed description of each thread in the third section.
Each thread description starts with a status bar similar to the one you see above. As for the Java Thread Name, it's pretty self-explanatory. The Daemon Status shows whether the thread is a daemon thread or not. If this option is missing, then the thread is not a daemon. The Thread Priority is a priority of the thread inside the process, and the OS Thread Priority is the priority of the operating system thread to which the Java thread is attached. In the next pair of highlighted options, we see the Java Thread ID and the Native Thread ID (the ID of the operating system thread to which the Java thread was attached). On the second line, as you might have guessed, the thread dump shows us the thread state, which is Runnable in our example. After the status bar, each thread description contains the stack trace for the given thread.
The fourth and last section of the thread dump shows threads related to the operation of the virtual machine itself. As a rule, they are associated with the garbage collector.
Using a thread dump
A thread dump is quite useful when investigating common issues regarding thread operations. With its help, you can not only check whether a thread is alive but perform much deeper troubleshooting. Among the issues you can tackle with a thread dump, there are:
Blocked threads. Just to refresh your memory, the thread is blocked when it is waiting for a monitor lock.
Deadlocks. This is a situation that can only occur in a multithreaded program when two threads are waiting for each other to access the same resource. This situation is problematic because it can last forever. A thread dump also shows information concerning deadlocks.
CPU consumption spikes. Sometimes unexpected spikes occur in the operation of the process. A thread dump can be useful for solving such a problem because it shows the activity inside the application.
This list is not exhaustive: there are a lot more situations that you can analyze with the help of thread dump, but we hope you can already see how useful this technique is.
Conclusion
With the help of this topic, you learned about an important function that allows you to monitor the execution of threads. We have given you the basics to form an idea of its fundamentals and purpose. With practice, you will learn how to benefit from it, which will help you work with multithreaded applications more efficiently.