Thread analysis: tracking thread behavior
The audience for this section is users who wish to identify and correct any number of thread-related issues in their application. For specific threads of interest, you can use these profiling tools to examine how often these particular threads are blocked (and by whom), and to visualize your application's thread characteristics.
For global concerns, such as for instance performance concerns where thread behavior is a potential suspect, the goal is to identify general thread issues that might impact application performance. A primary objective of an application developer who wants to profile thread behavior is to find threads that would otherwise run sooner, or more rapidly, were the resource and thread characteristics of the application altered.
To begin to gather thread information from the target application, launch the application from the Profiling dialog using the Thread Analysis profiling type. The Thread Analysis view of Rational Application Developer is the central view for all of the information gathered by the thread profiling agent.
The Thread Analysis view is split into three tabs:
- Thread Statistics: A table of statistics for every thread launched by the application, both past and present. Listed information includes thread state; total running, waiting, and blocked times; as well as the number of blocks and deadlocks per thread.
- Monitor Statistics: Provides detailed information on monitor class statistics, including block and wait statistics for individual monitor classes.
- Threads Visualizer: Provides a visual representation of all threads profiled in the target application, by status.
Threads in all of the views are organized by thread group. The data in the thread analysis view will only update whenever additional data is received from the profiled application. This point is an important one: the tables and graph will only be updated when a thread-related event is received from the target application profiler. If it appears as if the data is not being updated, this is because no new thread events have been received, and the data is still in its previous state.
The thread name listed is the name passed into the
Thread(String name) constructor, or set with the
Thread.setName method. It may be beneficial to call this method in the target application, in order to allow for easier thread identification. Thread statistics are gathered on all Java threads, which include VM threads, and may also include threads used by the application container or application framework. Fortunately, you can filter uninteresting threads out of the view by selecting the thread filter icon (three arrows with middle yellow one going through vertical green line ), and clearing unwanted threads.
The seven thread states are:
Sleeping: one in which the
sleepmethod has been explicitly called
Waiting: one on which the
waitmethod has been explicitly called and is waiting for a
notifyAllcall on its monitor object
- Blocked: refers to cases where a thread is blocked by an object monitor that is in use by another thread (for instance, a thread holding an object monitor in a synchronized statement)
- Deadlocked: statistics are gathered on a per-thread level, for each thread in the target application. A deadlock is considered to have occurred when two or more threads hold resources for which the resource dependency graph contains a cycle (that is, all deadlocked threads require additional resources that they cannot obtain without another deadlocked thread releasing required resources).
In Java, deadlocks can occur in a variety of situations, for example:
- When, in two threads, synchronized methods in two classes are trying to call the synchronized methods of each other.
- When one thread synchronizes on resource A and attempts to synchronize on a second resource B, while a second thread synchronizes on resource B and attempts to synchronize on resource A.
- Any other situation where it is not possible for two or more deadlocked threads to unblock and complete.
This tab lists all of the threads that are currently running, or have run, throughout the lifetime of the application. Threads remain in the table even after termination. This view lists running time, waiting time, and blocked time. Running time is defined as the total running time of the thread minus the time that it was waiting or blocked. Waiting time is defined as the amount of time that the thread spent waiting for a monitor, and Blocked time is the amount of time that the thread spent blocked by the ownership of monitors by other threads. In addition, there are several recorded counts: Block count and Deadlock count are the number of times a thread has blocked or deadlocked, respectively, throughout the life of the thread.
In this example, you are profiling an Eclipse plug-in. Figure 10 shows the present states of all running threads, their running time, waiting time, blocked time, and deadlocked time, and the blocked count. Application developers may utilize this view to either view a general picture of the entire thread landscape of their application, or to drill down to the moment-by-moment statistics of particular threads.
Figure 10. List of threads that are running in a profiled Eclipse plug-in
The waiting time, blocked time, and deadlocked time are important statistics to consider in the context of application performance. These values should be closely scrutinized to ensure that they are appropriate, especially for time-dependent threads.
The second tab in the Thread Analysis view is Monitor Statistics, shown in Figure 11. All of the objects in Java have a corresponding monitor, which is the basis for all concurrency operations in Java. Monitors are invoked when inside a synchronization block, or when
notify methods are called to wait for a dependency or signal an availability, respectively. This tab provides monitor statistics on a thread-by-thread basis.
Figure 11. The Monitor Statistics tab of the Thread Analysis view
In Figure 11, the Thread Statistics table contains a list of threads in the profiled application, along with various statistics of the thread from the first tab. Select one of the threads in the Thread Statistics table to display the associated monitors referenced by that Thread, including various statistics for those monitors. You can then select the monitor to open up information about its class, including block and wait statistics for callers of the monitor, as well as timing and object information. This allows the identification of the particular objects that are in contention, by whom, and how often.
In the Threads Visualizer tab shown in Figure 12, each of the seven thread states is denoted by bars of varying backgrounds and line patterns. Threads are sorted by thread group and thread name. The x-axis of the graph represents time, the range of which can be adjusted using the zoom-in and zoom-out icons.
Each row of the table contains a bar representing thread execution. Inside each bar is a continuous list of events, which represent changes in the thread state. You can double-click an event to display its call stack in the Call Stack view, and you can move from event to event using the Select Next Event and Select Previous Event buttons in the top right-hand corner of the Threads Visualizer tab.
In the graph, Waiting and Blocked states are denoted by dotted lines, and Deadlocked and Stopped states are denoted by a solid line. Of most importance to the application developers looking to identify performance issues are Deadlocked (red), Waiting (orange), and Blocked (yellow).
One important UI note: Perhaps contrary to what might be expected behavior, when a thread has terminated it will continue to maintain a dark grey representation on the chart (rather than disappearing from the chart entirely).
Figure 12. The Threads Visualizer represents the thread status of all threads in a graph, plotted against the application timeline
The buttons on the thread analysis toolbar are used to move or change focus to or from particular threads. From left to right, as shown in Figure 13, the buttons are: Legend, Show Call Stack, Reset Timescale, Zoom In/Out, Select Next/Previous Event, Select Next/Previous Thread, Group Threads, Filter Threads, and Visualize Thread Interactions. Most of these are self-explanatory: for example, the Next/Previous Thread button changes the currently selected thread, and the Select Next/Previous Event button moves the event cursor to the next or previous event of the currently selected thread. Additionally, you can group and filter threads as required.
Figure 13. Interact with the Threads Visualizer graph using the buttons at the top of the view
When you use the Rational Application Developer thread profiling functionality, you should take into account a number of considerations:
- For threads that are on a wake-sleep cycle, how long does it take to complete the wake phase of the cycle, and how long does the thread sleep?
- For threads that are dependent on external resources becoming available, how long are they blocked waiting for a resource to become available?
- In input-processing-output oriented applications, such as a Web application, how long do the various threads take to respond to user input, process the data, and produce the corresponding output?
- Some threads periodically wake to check a condition or perform a function, then return to sleep. Thread analysis allows you to observe these relationships using the Threads Visualizer.
- You can use the profiling functionality to monitor producer-consumer and reader-writer relationships.
In aggregate, the thread profiling functionality provides a variety of views to analyze application thread performance and behavior. These views allow you to gather information and analyze various aspects of program execution, in order to gain insight into potential bottlenecks or failure conditions.