Virtual Machine Level Analysis

Maxine contains an experimental extension to support analysis of code executing on the virtual machine (VM). Since the VM is itself written in Java, the analysis is applicable, in principle, to the VM itself, the platform (JDK) and the application.

The analysis is implemented primarily by advising the execution of the bytecodes. This is similar to systems like AspectJ except that the advice is applied to the bytecodes and not to language-level constructs. However, since the translation of language-level constructs to bytecodes is well-defined, language-level advising could, in principle, be added as a separate layer. Certain other VM runtime operations, for example, garbage collection, thread start/end can also be advised.

The Virtual Machine Level Analysis (VMA) project is currently implemented as an extension to Maxine in the and projects. The implementation exploits the flexibility inherent in the Maxine VM by defining custom versions of several existing Maxine schemes, and building a custom VM image.

Currently bytecode advising is limited to code generated by the (template) JIT compiler, although it is expected to be added to the optimizing compiler in due course.

VMA shares some similarity with aspects of the JVMTI API, most notably the method entry/exit and field access/watch capabilities and some of the runtime advice, e.g. thread start/end. Eventually, the redundant features may be removed and the two implementations merged. In the interim, some analyses can benefit from both systems, using Maxine’s Java JVMTI interface JJVMTI.

There is the beginnings of VMA for Graal in the project. However, it should be considered experimental.


To facilitate experimentation the VMA architecture is highly flexible and configurable. The system contains four basic components:

  1. a custom version of the T1X JIT compiler (VMAT1X) that uses custom templates for adding advice at bytecode translation time.
  2. the VMA runtime which invokes methods in an advice handling class that can be specified either at VM image build time or loaded dynamically as a VM extension.
  3. a store interface that supports the persistent storage of advice data for offline analysis, with an implementation that can
  4. be specified at runtime.
  5. a tool that can execute queries against the data in the persistent store.

Note that advising is disabled while in the scope of a handler execution, to prevent recursive entry from instrumented code that may be shared by the handler.

Building a VMA-enabled image

mx image @vma-t1x

This VM image includes the custom VMA schemes but does not specify an advice handler which must, therefore be loaded dynamically using the VM extension mechanism. By default advising is enabled, and if no handler or built into the VM Image or loaded as a VM extension, the VM will abort. To disable advising set -XX:-VMA option.

Running a VMA-enabled image

A VMA-enabled image can be run in the usual way with the max vm command. When advising is enabled (the default), the VMAT1X compiler is used for compiling dynamically loaded classes. This compiler will use the advice-enabled T1X templates. By default all methods in dynamically loaded classes are processed by VMAT1X. However, this can be controlled more precisely with the -XX:VMAMI and -XX:VMAMX options. Both options take values that are regular expressions in the format expected by java,util.regex.Pattern. The -XX:VMAMI option specifies methods to include and -XX:VMAMX specifies methods to exclude. If -XX:VMAMI is unset, all methods are candidates for processing with VMAT1X, otherwise only those methods that match the pattern. In either case, if the -XX:VMAMX is also set, it excludes any method in the candidate set that matches its pattern. The syntax of a method pattern is qualified_classname#methodnameunqualified_param_class_1, unqualified_param_class_2, .... Note that the parameter list braces must be escaped, as must the square braces in any array class specifier. The wildcard specifier . can be used to match all method names and/or signatures.

The exact set of bytecodes that have advice applied during compilation can also be controlled with options. By default, all bytecodes are advised but, depending on the desired analysis, a subset is usually more appropriate. While it is possible to be quite specific about exactly which bytecodes will be advised, there are some predefined configurations set via the VMAConfig option that are easier to use, e.g.:

  • entry: advises after method entry
  • entryexit: advises after method entry and before return, including return by thrown exception
  • monitor: advises the acquisition and release of monitors.
  • read: advises any access to an object’s state. An access is defined as any read of an object’s fields or its metadata or, if an array, it’s elements.
  • write: advises any write to an object’s fields or, if an array, any of its elements.
  • objectaccess: advises the creation of objects and all access sites.
  • objectuse: advises the creation of objects and all usage sites. A use is defined as any load or store of the object reference, or any access.

The VMAConfig options accepts a list of these configurations and aggregates them into a single set of the appropriate bytecodes to be advised.

The specific bytecode advice controls are -XX:VMABI and -XX:VMABX and behave very similarly to the class controls. Their argument is a regular expression matching bytecodes to be included or excluded, respectively. To provide control over enabling before/after advice a bytecode may be suffixed by :A, :B or :AB. Note that while all the bytecodes in the standard set defined by the JVM can be individually controlled, the VMAdviceHandler interface aggregates the advice for collections of similar bytecodes into a single method. For example, one can advice just the ICONST_0 and ICONST_2 bytecodes, but the advice for both will be directed to the VMAdviceHandler.adviseBeforeConstLoad method. The aggregating methods do not provide a way to distinguish which bytecode generated the advice.

Recording Time

Certain handlers can optionally gather timing information, notably SyncStoreVMAdviceHandler and VMLogStoreVMAdviceHandler. The VMATime option provides a standard way to specify how time is gathered. If the option value is none, time is not recorded. If the value starts with wallns, then wall clock time is gathered using System.nanoTime. If the value starts with wallms, then wall clock time is gathered using System.currentTimeMillis. VMLogStoreVMAdviceHandler requires the advice records to be ordered in the per-thread persistent stores, so that the analysis tool can reproduce a global order. As well as wall time, this can be achieved by a globally unique id, which can be enabled by setting the option value to ida. This variant has considerably less overhead that gathering wall clock time, yet tracks wall clock time quite closely. The TimeTestVMAdviceHandler can be used to compare the two values over a run.

Thread Advising

By default all application threads are enabled for advice. However a subset can be enabled by using the VMATI and VMATX options.

Sampled Advising

By default advising is on all the time which, evidently, has a significant performance impact on the application. Sampled advising, which is enabled by the -XX:VMASample option only enables advising periodically during the VM execution. The option value is a string of the form initialperiod,interval,period, all of which are optional. The interval value denotes the time, in milliseconds, between advising periods. The period value denotes the length of the advising period. The VM starts with advising enabled for initialperiod. If not specified the values default to 50, 50 and 10, respectively.

VM Options Summary

  • VMA: boolean valued option that enables/disables advising in the VM. Default is true.
  • VMAMI=p: p is a regular expression pattern specifying methods to include for instrumentation.
  • VMAMX=p: regular expression pattern specifying methods to exclude for instrumentation. Overrides VMAMI.
  • VMAXJDK: boolean valued option that excludes all JDK classes from instrumentation.
  • VMABI=p: p is a regular expression pattern of bytecodes to include for instrumentation.
  • VMABX=p: p is a regular expression pattern of bytecodes to exclude for instrumentation. Overrides VMABI.
  • VMAConfig=c: c is a comma spearated list of configuration names that instrument for specific analyses.
  • VMATI=p: p is a regular expression pattern of threads to have advising enabled.
  • VMATX=p: p is a regular expression pattern of threads to have advising disabled. Overrides VMATI.
  • VMATime=none|wallns|wlaams|ida}: specify how time is recorded in certain advice handlers. Default is wallns.
  • VMASample=initialperiod,interval,period: run in sampling mode. Defaults to 50,50,10.

Standard Handlers

Several existing handlers are provided, most notably handlers that store the generated advice to an external file for offline analysis. The default location for the external store is a directory vmastore in the current working directory that the VM is launched in. This can be changed by setting the property.

SyncStoreVMAdviceHandler is a simple, but inefficient, handler that synchronously stores the advice record to the store, and incurs per-advice synchronization overhead as all threads access the same store. Output goes to the shared store file named vm in the store directory.

VMLogStoreVMAdviceHandler uses a custom instance of the VMLog class to store advice records in per-thread buffers. When the buffer is full it is flushed to a file in a compact textual format, that can be processed with the QueryAnalysis tool. VMLogStoreVMAdviceHandler adds a small, but generally consistent, overhead to the execution of each bytecode. Assuming the use of the default text-based store, it also causes object allocation as internal objects are converted into String representations when the records are flushed. The maximum latency occurs when the store buffer is flushed to the file. Note: this handler requires that some of its code to be included in the boot image as it adds some VM thread local variables, and these cannot be added at runtime. This handler operates in per-thread mode throughout the store process, thereby avoiding almost all synchronization overhead.

Developers can define additional handlers for specific purposes that may, for example, do all analysis internally in the VM. An example is CBCVMAdviceHandler that simply counts the advice calls and outputs a summary at the end of execution. ThreadLocalVMAdviceHandler analyses object use for thread locality. Evidently such handlers incur much less CPU overhead that those that externalize the data, but may incur additional memory costs. Handlers that only handle a subset of the advice calls should subclass NullVMAdviceHandler which defines a null implementation of each advice method. Note that CBCVMAdviceHandler can be used to estimate the size of the persistent store that would be created by VMLogStoreVMAdviceHandler or SyncStoreVMAdviceHandler, since the store will contain approximately the same number of lines as the number of advice counts reported. On average a trace line in the store is 16 bytes in length.

Per-Object State

One of the issues for analysis tools, either online or offline, is associating analysis-specific state with an object, for example a unique identifier. The standard approach is to use a Map but this has problems both with the space overhead and the fact that the map keeps objects reachable, which perturbs the behavior of the garbage collector. An WeakHashMap can mitigate the latter problem but perturbs the garbage collector in a different way and provides no guarantee of object lookup if the object is collected.

VMA provides a solution to this by using a custom Maxine object layout scheme, XOhm, to provide extra header words for use by VMA handlers. Evidently this has some impact on the behavior of the system, for example, making garbage collection more frequent owing to the increased object size. However, the overhead is as minimal as can be achieved. By default, one extra word is provided and basic access to the state word is provided by the ObjectState class. Support for unique identifiers is provided through the ObjectId interface and support for marking bits through the ObjectBitSet interface. The class SimpleObjectState implements both of these interface. Additional words can be included by setting the max.vm.layout.xohm.words system property on the image build. For example setting -Dmax.vm.layout.xohm.words=2 would provide a total of two additional header words, one for use by ObjectId and ObjectBitSet and one for use by the handler. Access to the additional words is through the ObjectVars interface. The class VarsObjectState extends SimpleObjectState to implement this interface. Note that only scalar values can be stored in the extra header words as they are not scanned by the garbage collector.

Handlers that need to use an persistent object id to represent an object, should subclass ObjectStateAdapter which implements all the VMAdviceHandler methods that take Object types as arguments. Unique identifiers are assigned to objects returned by the NEW family of bytecodes. Objects passed as arguments to the other methods are checked for a uuid having been assigned and, if not, the abstract method unseenObject, which must be implemented the handler, is called. The adapter also handles unique id generation for ClassLoader instances, since these may be user defined.

Specifying handlers

Handlers can either be built into the boot image or loaded dynamically as a VM extension.

Building handlers into the boot image

To build an advice handler into the boot image set the max.vma.handler.class system property to the fully qualified name of a class that extends VMAdviceHandler, e.g.:

mx --Jp image @vma-t1x

A short form is available for the standard handlers using the max.vma.handler property:

  • null: NullVMAdviceHandler
  • syncstore: SyncStoreVMAdviceHandler
  • vmlogstore: VMLogStoreVMAdviceHandler
  • cbc: CBCVMAdviceHandler
  • tl: ThreadLocalVMAdviceHandler

When creating a new handler it is important to prevent it being included in the boot image by default. This is achieved by adding a Package class to the handler package that specifies inclusion only when the -Dmax.vma.handler.class option matches the handler in question. See the the existing handlers for an example.

Dynamically loaded handlers

Follow the instructions for building a VM extension JAR file, using one of the included handlers as an example, the load the handler at runtime, e.g.:

mx vm -vmextension:yourhandler.jar ...

Also, look at one of the Eclipse JAR file creation descriptions for the existing handlers, in the file with extension jardesc. Note that all referenced classes that are not already included in the boot image must be specified in the jar file, as the VM extension mechanism has no search path support.

Instrumenting JDK classes in the Boot Image

It is possible to instrument JDK methods that were included in the boot image. This occurs transparently if such a JDK method is in set of methods specified to be instrumented. This is implemented on VM startup by deoptimizing the methods in the boot image. Note that this can greatly increase the quantity of generated advice and also has an impact on the performance of the handlers themselves since, although advising is disabled in the scope of a handler, the JDK method is no longer optimized.

Note that the -XX:+VMAXJDK can be used to suppress instrumentation of all JDK methods. Setting this option is the easiest way to just instrument all application methods.

VMA and Handler Initialization

VMA overrides the standard run scheme, JavaRunScheme, with VMAJavaRunScheme, to interpose on the VM startup to perform VMA specific initialization. VMAdviceHandler defines an initialise(MaxineVM.Phase phase) method that normally should be overridden in a handler, and this method is called from VMAJavaRunScheme. The only phases that are of interest are BOOTSTRAPPING, RUNNING and TERMINATING. BOOTSTRAPPING is only relevant for the case where the handler is being included in the boot image, and provides an opportunity to allocate and initialize certain data structures in the boot heap. Other initialization must be performed in the RUNNING phase. In order for the handler to influence the behavior of the system, for example,to customize the bytecodes that are advised, the handler’s initialise method is called before any methods are instrumented and before the JDK classes are considered for deoptimization and instrumentation. Note that this means that some JDK classes may be loaded and compiled without instrumentation as a side effect of being used by the handler’s initialise method. However, these will be recompiled and instrumented if required as part of the JDK instrumentation phase. Actual advice method invocations are not enabled until after the VMAJavaRunScheme.initialize returns in the RUNNING phase. The TERMINATING phase is typically where the handler reports any results, using whatever mechanism it chooses.

Note that the onLoad method of a dynamically loaded handler is called in the STARTING phase, which precedes the RUNNING phase. Typically all the method should do is register an instance of the handler using VMAJavaRunScheme.registerAdviceHandler, so that it can be invoked in the RUNNING phase.

Properties to control VMA

VMA behavior can be controlled by setting some system properties in addition to the command line options. These are generally handler specific.

Boot Image Generation Properties

  • max.vma.vmlog: This property must be set on a boot image build and controls whether the custom VMLog used by VMLogStoreVMAdviceHandler is included. It is on by default but the code can be excluded by setting the value to false.
  • max.vma.handler.class: The fully qualified name of a handler class to be included in the boot image.
  • max.vma.handler: The short name of a standard handler class to be included in the boot image.

Handler Specific Properties

  • max.vma.handler.cbc.sort: When set, CBCVMAdviceHandler sorts the pan-thread advice counts by frequency.
  • The size of the StringBuilder used to buffer store records. Default is 1 MB.
  • The size at which the store buffer is flushed to the file; defaults to
  • Use a 3 character mnemonic key for stored records instead of a single digit code.


The performance overhead varies, evidently, with the set of bytecodes that are being advised and the set of classes that are subject to instrumentation. Performance is fundamentally limited by VMA currently being restricted to the T1X baseline compiler. The numbers presented below are for the SpecJVM98 benchmarks. The overhead of using T1X with no optimization for hot methods varies considerable depending on the application behavior, averaging 6.9% for SpecJVM98 with a range of 1.0% to 16.7%. The benchmarks that make use of the JDK classes show much less overhead since they are able exploit the optimized JDK methods compiled into the boot image.

The NullVMAdviceHandler provides a measure of the basic overhead of a handler. The overhead is relative to Maxine using T1X with no optimization of hot methods with every bytecode advised. The average overhead is 3.5%, with a range of 1.8% to 7.3%, when only the benchmark classes are instrumented. If we restrict the advice to just those bytecodes needed by the objectuse configuration, the overhead averages 2.1% with a range of 1.3% to 3.7%. If the JDK classes are also instrumented, including those in the boot image, the average overhead with all bytecodes advised increases to 5.6%, with a range of 1.8% to 12% and for the objectuse configuration averages 2.9% with a range of 1.4% to 6.8%.

Analysis Tools


QueryAnalysis is a command line tool that was originally implemented in a project that was focused on analyzing objects for immutability. It reads the compact text form of the store file and builds a data structure suitable for analysis. The tool has no pre-defined analyses built in but supports dynamically loaded queries that are written to a standard API.

The basic data structure created by the tool is a list of the advice records in the file. Object instances and Maxine meta-objects, e.g. MethodActor, are replaced with types defined in the analysis tool, namely ObjectRecord, ClassRecord, FieldRecord and MethodRecord, with the obvious mappings.

The tools also builds some auxiliary data structures to facilitate analysis.

  • objects: a map containing all the object instances in the store trace. The key is the id of the object and the value is the ObjectRecord. Note that since id’s might be reused by the VM as objects are garbage collected, the id is tagged with the allocation epoch to ensure uniqueness.
  • classLoaders: a map from classloader instances to a (sorted) map from classes loaded by that classloader. The key of the classLoaders map is the id of the classloader instance. The key of the class map is the name of the class and the value is the ClassRecord.
  • missingConstructors: Inevitably object instances occur in the trace for which no allocation event was seen, e.g. objects allocated in the boot image. These are given id’s that decrease from -1. The missingConstructors map is keyed by the id and the value is the associated ObjectRecord.
  • allocationEpochs: a list of AllocationEpoch objects that define when garbage collection events have occurred. Each instance specifies the period from one garbage collection to the next.
  • objectCount: the total number of object instances, that are not arrays, encountered in the trace.
  • arrayCount: the total number of array instances in the trace.


[-f inpath] [-v] [-i queryfile] [-e query]

The default value of inpath is vmastore. If the value is a directory (normal) it is expected to contain either a vm file or a set of per-thread files. If the value is a file, it is used directly.


  • -v: Report on progress reading the store file, showing the time to read every 100000 records.
  • -e query: initial query to execute
  • -i queryfile: Execute the commands in queryfile before entering interactive mode.

After processing the store file the tool enters interactive mode allowing queries to be executed against the data structures described above. The input prompt is %%. The following interactive commands are available:

  • e query: The tool prepends and appends Query to queryclass and then attempts to load that class, which must be a subclass of QueryBase, and then invokes its execute method. E.g., e Foo will attempt to invoke See below for details on the pre-defined queries.
  • i infile: Execute commands from infile.
  • o outfile: Redirect output to outfile or the standard output if outfile omitted.

A query is executed with the command:

%% e query

Standard Query Arguments

  • -v: set verbose output mode.
  • -class name | -c name: restrict output to class name
  • -thread name | -th name: restrict output to thread name.
  • -id id: restrict output to object with id.
  • -clid id: restrict output to classloader id.
  • -abs: report time as absolute

Evidently these arguments have a query-specific interpretation, within the general definition given above.

Pre-defined Analysis Queries

The analysis tool is meant to be easily extensible, but a set of simple query classes are included with the tool. Many of these derive from the prior work on immutability so, for example, when displaying objects it is typical for information on immutability to be output.

AdviceRecords -from fromindex -to toindex -showindex -indent

Lists the advice records reconstructed from the store. Note: Unless the range is constrained this generates even more output that contained in the original (comopressed) store, and is best redirected to a file with the o outfile command. The -showIndex option adds the index of the record as a prefix, and the -indent option indents on a method entry record.


Displays the number of classes, classloaders, objects, arrays, the number of missing constructors and also displays the result of the ImmutableCount query.


Displays the classes, showing the classloader and the number of instances of each class.

Additional arguments:

  • -sortbycount: sort the output from largest number of instances to smallest.

Displays the classloader objects in the trace, i.e., those that inherit from java.lang.ClassLoader.


For each classloader, display the data on objects of a given class loaded by that classloader.

Example output:

Objects organized by classloader

Classloader: (sun.misc.Launcher$AppClassLoader) -1:0
  Objects organized by class
  test.Simple, total objects 1
    1:0, c 223.318617ms, la 253.762114ms, lm 238.633649ms, stable for 49.693585%
Classloader: (com.sun.max.vm.type.BootClassLoader) -2:0
  Objects organized by class, total objects 1
    -4:0, c 253.77319ms, la 253.777241ms, lm 253.77319ms, stable for 100%

The end of construction by c, the last access time by la, and the last modify time by lm. Note that in the current implementation objects whose construction was not observed are given ids that decrease from -1, and a creation time of the first time they are accessed.

Additional options:

  • -showthread: show the thread that allocated the instance (default false)
  • -sort_lt: Sort instances by lifetime (highest to lowest)
  • -sort_mlt: Sort instances by mutable lifetime (highest to lowest)
  • -summary: Suppress the individual instance output and replace with the percentage of objects immutable for more than a given percentile (default 100) (default false).
  • -pci percentile: Set the percentile to use with -summary.
  • -sort_summary mode: The summary data can be sorted by class mode == class, total number of instances mode == total or total number immutable for more than percentile, mode = imm_total.

The same output as DataByClassLoader except for all classes, irrespective of classloader.


The data, in same format as DataByClass on all the objects in the objects map.


The data on a specific object id.


Similar output to DataByClass except grouped by the allocating thread.

Additional arguments:

  • -summary: restrict output to the total number allocated and the total live number.
  • -sort_lt: as per DataByClass.
  • -sort_mlt: as per DataByClass.

Displays the allocation epochs in the trace, that is, the periods between garbage collections. If -r is set, also displays the objects that were collected at the end of each epoch.


For each class: first computes the immutable lifetime as a percentage, then counts how many objects fall into buckets that are 1% in size. E.g.:

Class sun.reflect.NativeMethodAccessorImpl, Object count 4

  4: 1 (25.00)  42: 1 (25.00)  75: 1 (25.00)  100: 1 (25.00)

This means that one object was immutable for 4%, one for 42%, one for 75% and 1 for 100% of respective lifetime. The number in brackets is the percent of the total for that bucket.


Displays the immutable object percentage and the immutable array percentage.


Displays the total number and size of the live objects.


Displays the number of objects for which no trace was generated for construction and, if -v is set, the data on those objects.


Display all the mutable objects of a given class and the list of modifications.


Displays the accesses to the static fields of classes.


Analyzes the data for thread locality, reporting on objects created by one thread that are accessed by another thread. Evidently, this analysis is dependent on the appropriate bytecodes being traced. The objectuse configuration will report any use of an object, whereas objectaccess will report an actual access to the content of an object.


Assuming that the store file contains method entry and exit traces, this query constructs a call graph and displays it graphically using a standard Swing JTree.


This tool can perform various conversion operations on the store file.


[-f inpath] [-o outfile] [-readable | -unbatch | -batch | -merge] [-abstime]

The default value if inpath is vmastore. If no output file is specified the output goes to the standard output. The options have the following effect:

  • -readable: Generate a (more) readable form of the store file.
  • -unbatch: Convert the store file containing records batched by thread into a time ordered file
  • -batch: Reverse the process performed by -unbatch. This is a debugging tool to check the -unbatch command.
  • -abstime: By default store files denote time by the increment between successive records (in a batch). This option will convert the file to use absolute time for each record.
  • -merge: Merges all per-thread store files into a single output file.

Simple Tests

The package contains some simple test programs to exercise the system, of which we highlight a few here. To simplify the exposition, assume that the current working directory contains the Maxine projects and the following aliases have been defined:

alias qa="java -classpath"
alias maxvma="max vm -cp"


This is an example of a class with a non-final field that is stable in the sense that it is written to once shortly after the constructor executes and is then immutable from that point on. To demonstrate this, execute:

$ maxvma '-XX:VMAMI=test.Simple.*' test.Simple
$ qa
%% e DataByClass -c test.Simple
Objects organized by class
test.Simple, total objects 1, cl: (sun.misc.Launcher$AppClassLoader) -1:0
  1:0, c 223.318617ms, la 253.762114ms, lm 238.633649ms, stable for 49.693585%


GCTest is a multi-threaded program where each thread iteratively builds up a list of objects with a randomly generated lifetime, removing those that have expired after each iteration, and then invoking System.GC. E.g.,

$ maxvma -XX:VMAMI='test.GCTest.*' test.GCTest
$ qa -v

processing trace file vmastore starting
processed 100000 traces in 1331 ms (1331)
processed 200000 traces in 1887 ms (556)
processed 300000 traces in 2399 ms (512)
processed 400000 traces in 2702 ms (303)
processed 500000 traces in 3024 ms (322)
processing trace file vmastore complete
%% e GC
Allocation epochs
Epoch 0, 0ms, 646.481ms
Epoch 1, 646.481ms, 772.329ms
Epoch 2, 772.329ms, 879.328ms
Epoch 3, 879.328ms, 985.426ms
Epoch 4, 985.426ms, 1117.057ms
Epoch 5, 1117.057ms, 1226.683ms

Since the store file is considerably larger for this example, we used the -v option to report processing speed.


ThreadLocal01 is a multi-threaded program where several worker threads (default 2) build lists of objects that are private to the thread. Optionally the program can be configured so that they leak objects that are accessed by a leak observer thread. To run in private (thread local) mode, execute:

$ maxvma -XX:VMAMI='test.ThreadLocal.*' test.ThreadLocal01

running with 2 threads
Thread Generator-0 running
Thread Generator-1 running
Thread Generator-0 returning
Thread Generator-1 returning
global list size 22, LeakObserver accessCount 437
main thread terminating
$ qa
%% e ThreadLocal
Check objects allocated by thread Generator-1
Check objects allocated by thread Generator-0
Check objects allocated by thread main
object 5:0 is accessed by thread Generator-0
object 2:0 is accessed by thread Generator-1
object 2:0 is accessed by thread Generator-0
object 9:0 is accessed by thread Generator-1
object 8:0 is accessed by thread Generator-1
object 6:0 is accessed by thread Generator-0

The ThreadLocal query determines that objects allocated by the generator threads are not accessed by any other thread.

Now execute:

$ maxvma -XX:VMAMI='test.ThreadLocal.*' test.ThreadLocal01 -l

running with 2 threads
Thread LeakObserver running
Thread Generator-0 running
Thread Generator-1 running
Thread Generator-0 returning
Thread Generator-1 returning
main thread terminating
$ qa
%% e ThreadLocal
Check objects allocated by thread Generator-1
object 120:0 is accessed by thread LeakObserver
object 120:0 is accessed by thread Generator-0
object 161:0 is accessed by thread LeakObserver
object 283:0 is accessed by thread Generator-0
object 387:0 is accessed by thread LeakObserver
object 381:0 is accessed by thread LeakObserver
object 137:0 is accessed by thread Generator-0
object 147:0 is accessed by thread LeakObserver
object 30:0 is accessed by thread LeakObserver
object 30:0 is accessed by thread Generator-0
object 62:0 is accessed by thread LeakObserver
object 51:0 is accessed by thread LeakObserver
object 406:0 is accessed by thread LeakObserver
object 347:0 is accessed by thread LeakObserver
object 309:0 is accessed by thread Generator-0
object 208:0 is accessed by thread LeakObserver
object 208:0 is accessed by thread Generator-0
object 202:0 is accessed by thread LeakObserver

In this case the query detects that objects created by Generator-1 have leaked to both LeakObserver and Generator-0. Note: since both generator threads are leaking objects to the same global list, they also see each others leaks.

Note that a similar analysis is performed online by the ThreadLocalVMAdviceHandler.

Bugs and Limitations

The main limitation at present is that VMA is only available for the baseline (JIT) compiler, and that many bytecodes do not support both BEFORE and AFTER advice.