## Call tree collection - collection methods and influence on performance and data |
previous | up | next

### 1. Call trees and call stacks

At first glance, it might seem that the call tree collection settings only influence the CPU section of JProfiler. However, the memory section as well as the thread section display information that originates from the call tree that is build by the profiling agent of JProfiler: the invocation tree view, the allocation monitor, the stack traces in the monitor contention views as well as many other views all depend on the same call tree. The call tree is always recorded, even if "CPU recording" is switched off in JProfiler.

Selecting the right call tree collection method is crucial for a successful profiling run. As explained in the article on profiling settings, the aim is to get the best runtime performance while retaining an acceptable level of informational detail. While the most important profiling setting in this regard is the filter configuration, the call tree collection method complements this choice. Each call tree collection method has various limitations that you have to bear in mind when configuring call tree filters.

### 2. The different method of call tree collection

There are three different methods for recording the call tree that have different advantages and disadvantages:

- Dynamic instrumentation
  This is JProfiler's default mode. Before unfiltered classes are loaded by the JVM, JProfiler injects bytecode into the methods of that class that report the entry and exit of a method as well as the invocation of any filtered method. Filtered classes are not touched and run without overhead. If most classes are filtered, this mode causes low overhead while providing highly detailed measurements. Typically, the entire JRE and any framework classes are filtered so that dynamic instrumentation is most often the best choice. Since there are some classes in the `java.*` and `sun.*` packages that the profiling agent does not get a chance to modify, the internal calls of these packages cannot be resolved with dynamic instrumentation. However, for most applications this is not a problem.
- Sampling

  "Sampling" means to periodically take measurements that are called "samples". In the case of profiling, an additional thread periodically halts the entire JVM and inspects the call stack of each thread. The period is typically 5 ms, so that a large number of method calls can occur between two samples. These method calls cannot be resolved by sampling. The sampler compares the call stacks of two subsequent samples and attributes the elapsed time to the "least common denominator" of the two call stacks. For example, with the call stacks:

        Time x:

        B.subOp()
        B.firstOp()
        B.calculate()
        B.run()
        A.main(String[] args)

  and

        Time x + 5 ms:

        B.subOp()
        B.secondOp()
        B.calculate()
        B.run()
        A.main(String[] args)

  the least common denominator is

        B.calculate()
        B.run()
        A.main(String[] args)

  This is the invocation path that will be attributed 5 ms of execution time. If there's a very rapid succession of the method calls in `B.calculate()`, its detail cannot be resolved by sampling. You will only know that `B.calculate()` takes a lot of time.

  The advantage of sampling is that its performance overhead is not very sensitive to the filter settings. Even without any filters, sampling is still fast since it operates with big granularity in time. You might ask why it is not possible to decrease the sampling time into the microsecond range to achieve a better resolution. The answer is that the process of sampling is a very expensive operation. Halting the entire JVM and querying the call stacks of a threads takes a lot of time. If you do this too often, sampling will actually become slower than dynamic or full instrumentation.

  Sampling has two other important informational deficiencies: Since sampling does not monitor the entry and the exit of method calls, there's no invocation count in the CPU views of JProfiler. Furthermore, the allocation spots for objects are only approximate. The actual call stack might always be deeper than the reported one. Consider the above example where objects allocated by `B.subOp()` between time x and time x + 5 ms are reported as being allocated by `B.calculate()`. The problem is that this informational deficiency is not systematic, but statistical: the confusion sets in when at some later time two subsequent samples both produce the first call stack. Now some objects that are allocated by `B.subOp()` are reported correctly, but not all of them. To get around this deficiency, JProfiler has an option to record the exact allocation spots for sampling. In this case, the profiling agent does not rely on the call tree as recorded by the sampler. Rather, after each object allocation, it queries the JVMPI for the call stack of the current thread. However, this is an expensive operation and if you create a lot of objects the performance of the profiled application may suffer quite a lot.

  To conclude, sampling is best suited for performance bottleneck searches with all filters turned off.

- Full instrumentation
  The profiling agent can ask the JVMPI to report each and every method call, so that the profiling agent can measure it. While this may sound like a good idea at first, in practice, the performance overhead of this "full instrumentation" is too large. Except when you have to display the internal call structure of classes in the `java.*` and `sun.*` packages, this call tree collection mode is not recommended.