Tracing Agent
The tracing agent was implemented to overcome some restrictions of GraalVM Native Image and to simplify the configuration process. It records the behaviour of a Java application running on GraalVM or any other compatible JVM that supports Java VM Tool Interface (JVMTI). The tracing agent is supported in both GraalVM Enterprise and Community Editions.
The tracing agent helps to deal with such features as Reflection, Java Native Interface, Class Path Resources, and Dynamic Proxy in the GraalVM environment. It is applicable when the static analysis cannot automatically determine what to put into a native image and undetected usages of these dynamic features need to be provided to the generation process in the form of configuration files. The tracing agent observes the application behaviour and builds configuration files when running on the Java HotSpot VM, thus it can be enabled on the command line with the java
command:
$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ ...
Please note that -agentlib
must be specified before a -jar
option or a class
name or any application parameters in the java
command line.
During execution, the agent interfaces with the JVM to intercept all calls that look up classes, methods, fields, resources or request proxy accesses. The agent then generates the jni-config.json, reflect-config.json, proxy-config.json and resource-config.json in the specified output directory. The generated files are stand-alone configuration files in JSON format which contain all intercepted dynamic accesses.
The generated configuration files can later be supplied to the native-image tool
by placing them in a META-INF/native-image/
directory on the class path, for
example, in a JAR file. Not all of those files must be present. When multiple
files with the same name are found, all of them are included.
Read more about advanced usage of the tracing agent in the Assisted Configuration of Native Image Builds document.
Java Reflection Support
Java Reflection provides the ability to inspect and modify applications runtime
behaviour. It allows to inspect a class or an interface, get its methods and
fields information, invoke a method, and even create an object of a class at
runtime. The Reflection API classes are part of the java.lang.reflect
package.
For GraalVM Native Image to handle the Reflection API, in some cases, a reflection
configuration has to be provided at image build time. Within a
closed-world assumption approach, an aggressive static analysis can see most
classes, methods and fields with certainty because they are used directly.
However, when the code accesses program elements by name via reflection, the
analysis cannot always determine ahead-of-time what program elements it refers
to, and the user’s assistance is then needed to make these elements accessible
at runtime. For example, the analysis can discover all dynamic usages of the
Class.forName("java.lang.String").getMethod("hashCode").invoke("Hello!");
sequence because only constants are used, but in
String.class.getMethod("hashCode".replace('K', 'C')).invoke("Hello!");
it
cannot. Where the static analysis fails to access the program elements
reflectively, they must be specified in a configuration file via the option
-H:ReflectionConfigurationFiles=
. For more details, read our documentation on reflection support.
In the given case, the tracing agent can simplify the configuration process and
write a reflection configuration file by tracing all reflective lookup
operations on the Java HotSpot VM. The traced operations are Class.forName
, Class.getMethod
, and Class.getField
.
For demonstration purposes, save the following code as ReflectionExample.java file:
import java.lang.reflect.Method;
class StringReverser {
static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
class StringCapitalizer {
static String capitalize(String input) {
return input.toUpperCase();
}
}
public class ReflectionExample {
public static void main(String[] args) throws ReflectiveOperationException {
String className = args[0];
String methodName = args[1];
String input = args[2];
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, String.class);
Object result = method.invoke(null, input);
System.out.println(result);
}
}
This is a simple Java program where non-constant strings for accessing program
elements by name must come as external inputs. The main method invokes a method
of a particular class (Class.forName
) whose names are passed as command line
arguments. Providing any other class or method name on the command line leads to
an exception.
Having compiled the example, invoke each method:
$JAVA_HOME/bin/javac ReflectionExample.java
$JAVA_HOME/bin/java ReflectionExample StringReverser reverse "hello"
olleh
$JAVA_HOME/bin/java ReflectionExample StringCapitalizer capitalize "hello"
HELLO
Build a native image as regularly, without a reflection configuration file and run a resulting image:
$JAVA_HOME/bin/native-image ReflectionExample
Build on Server(pid: 59625, port: 58819)
[reflectionexample:59625] classlist: 467.66 ms
...
Note: Image 'reflectionexample' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
$ ./reflectionexample
The reflectionexample
binary is just a launcher for the Java HotSpot VM, a “fallback
image” as stated in the warning message. To generate a native image with
reflective lookup operations, apply the tracing agent to write a
configuration file to be later feed into the native image generation together
with a --no-fallback
option.
- Create a directory
META-INF/native-image
in the working directory:mkdir -p META-INF/native-image
- Enable the agent and pass necessary command line arguments:
$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
This command creates a reflection-config.json file which makes the
StringReverser
class and thereverse()
method accessible via reflection. The jni-config.json, proxy-config.json ,and resource-config.json configuration files are written in that directory too. - Build a native image:
$JAVA_HOME/bin/native-image --no-fallback ReflectionExample
The native image generator automatically picks up configuration files in META-INF/native-image directory or subdirectories. However, it is recommended to have META-INF/native-image location on the class path, either via a JAR file or via the
-cp
flag. It will help to avoid confusion for IDE users where a directory structure is defined by the tool. - Test the methods, but remember that you have not run the tracing agent twice to create a configuration
that supports both:
$ ./reflectionexample StringReverser reverse "hello" olleh $ ./reflectionexample StringCapitalizer capitalize "hello" Exception in thread "main" java.lang.ClassNotFoundException: StringCapitalizer at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60) at java.lang.Class.forName(DynamicHub.java:1161) at ReflectionExample.main(ReflectionExample.java:21)
Neither the tracing agent nor native images generator cannot automatically check
if the provided configuration files are complete. The agent only observes and
records which values are accessed through reflection so that the same accesses
are possible in a native image. You can either manually edit the
reflection-config.json file, or re-run the tracing agent to transform the
existing configuration file, or extend it by using config-merge-dir
option:
$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
Note, the different option config-merge-dir
instructs the agent to extend the
existing configuration files instead of overwriting them. After re-building the
native image, the StringCapitalizer
class and the capitalize
method will be
accessible too.