Asynchronous execution of tasks is a common practice, both in plain Java code and even more so with reactive frameworks. Code that is adjacent in your source file is now executed on two or more different threads. For debugging and profiling, these thread changes presents two problems: On the one hand, it is not clear how expensive an invoked operation is. On the other hand, an expensive operation cannot be traced to the code that caused its execution.
JProfiler's solution to this problem is request tracking: Call sites and execution sites in multi-threaded programming are hyperlinked in the call tree, so you can seamlessly navigate both ways.
Inter-thread communication can be implemented in various ways and the semantics of starting tasks on a separate thread cannot be detected in a generic way. JProfiler explicitly supports several common asynchronous systems. You can enable or disable them in the request tracking settings. By default, request tracking is not enabled.
In JProfiler's main window, the status bar indicates if some request tracking types are enabled and gives you a shortcut to the request tracking dialog.
The simplest way to offload a task on another thread is to start a new thread. With JProfiler, you can follow a thread from its creation to the execution site by activating the "Thread start" request tracking type. However, threads are heavy-weight objects and are usually reused for repeated invocations, so this request tracking type is more useful for debugging purposes.
The most important and generic way to start tasks on other threads uses executors in the
java.util.concurrent
package. Executors are also the basis for many higher-level third party
libraries that deal with asynchronous execution. By supporting executors, JProfiler supports a whole class of
libraries that deal with multi-threaded and parallel programming.
Apart from the generic cases above, JProfiler also supports two GUI toolkits for the JVM: AWT and SWT. Both
toolkits are single-threaded, which means that there is one special event dispatch thread that can manipulate
GUI widgets and perform drawing operations. In order not to block the GUI, long-running tasks have to be
performed on background threads. However, background threads often need to update the GUI to indicate progress
or completion. This is done with special methods that schedule a Runnable
to be executed on the
event dispatch thread.
In GUI programming, you often have to follow multiple thread changes in order to connect cause and effect: The user initiates an action on the event dispatch thread, which in turn starts a background operation via an executor. After completion, that executor pushes an operation to the event dispatch thread. If that last operation creates a performance problem, it's two thread changes away from the originating event.
A call site in JProfiler is the last profiled method call before a recorded thread change is performed. It starts a task at an execution site that is located on a different thread or in a different VM. If request tracking is enabled for the appropriate request tracking type, JProfiler allows you to jump from a call site to an execution site by using hyperlinks that are shown in the call tree view.
Call sites have the same identity with respect to request tracking for all threads. This means that when you jump from call sites to execution sites and vice versa, there is no thread-resolution and the jump always activates the "All thread groups" as well as the "All thread states" thread status selection, so that the target is guaranteed to be part of the displayed tree.
Call sites and execution sites are in a 1:n relationship. A call site can start tasks on several execution sites, especially if they are in multiple remote VMs. In the same VM, multiple execution sites for a single call site are uncommon, because they would have to occur at different call stacks. If a call site calls more than one execution site, you can choose one of them in a dialog.
An execution site is a synthetic node in the call tree that contains all executions that were started by one particular call site. The hyperlink in the execution site node takes you back to that call site.
In principle, call sites and execution sites could be implemented in an n:m relationship. However, it is often important to separately analyze the execution site depending on the call site. For example, the same executor thread can handle tasks submitted from different methods, but they will probably be of a different nature and so merging them would not be beneficial. That's why JProfiler creates a new execution site for every call site.
If the same call site invokes the same execution site repeatedly, the execution site will show the merged call tree of all its invocations. If that is not desired, you can use the exceptional methods feature to split the call tree further, as shown in the screen shot below.
Because several execution sites can refer to the same call site, call sites have a numeric ID. In that way you can recognize the same call site if you see it referenced from different execution sites. Execution sites are only referenced from a single call site and so they do not need a separate ID.
For selected communication protocols, JProfiler is able to insert meta-data and track requests across JVM boundaries. The supported technologies are:
In order to be able to follow the request in JProfiler you have to profile both VMs and open them at the same time in separate JProfiler top-level windows. This works with both live sessions as well as with snapshots. If the target JVM is not currently open, or if CPU recording was not active at the time of the remote call, clicking on a call site hyperlink will show an error message.
In addition, a call site with a remote execution site will display the ID of the remote VM in addition to the call site ID. The ID of the profiled VM can be seen in the status bar. It is not the unique ID that JProfiler manages internally, but a display ID that starts at one and is incremented for each new profiled VM that is opened in JProfiler.