java.lang.Instrument Agent Use
The java.lang.Instrument package was introduced in JDK5. Programmers can dynamically modify class code by modifying the bytecode of the method. This is usually preprocessed before the main method of the class is called, and is implemented by java to specify the proxy class of the class. Before the bytecode of the class is loaded into the JVM, the transform method of ClassFileTransformer will be called to realize the function of modifying the original class method and implementing AOP. The advantage of this is that it will not produce a new class like dynamic proxy or CGLIB technology that implements AOP, and there is no need for the original class to have an interface.
(1) Agent is an interceptor before your main method, that is, the code that executes the agent before the main method is executed. The agent's code runs in the same JVM as your main method, is loaded by the same system classloader, and is managed by the same security policy and context. The name agent is a bit misleading, and it is not much the same as the agent we generally understand. Java agent is relatively simple to use. How to write a java agent? You only need to implement the premain method: public static void premain(String agentArgs, Instrumentation inst) If the above definition of premain cannot be found in JDK 6, you will also try to call the following premain definition: public static void premain(String agentArgs)
(2) The Agent class must be typed into a jar package, and then the META-INF/MAINIFEST.MF inside must contain the Premain-Class attribute. Here is an example of MANIFEST.MF:
Manifest-Version: 1.0 Premain-Class:MyAgent1 Created-By:1.6.0_06
Then add MANIFEST.MF to your jar package. The following is the Manifest Attributes manifest for the agent jar file: Premain-Class If a proxy is specified when the JVM is started, this attribute specifies the proxy class, that is, the class containing the premain method. This property is required if a proxy is specified when the JVM is started. If the property does not exist, the JVM will abort. Note: This property is a class name, not a file name or path. Agent-Class If the implementation supports the mechanism to start the agent at a certain moment after the VM is started, this property specifies the agent class. That is, the class containing the agentmain method. This property is required and the proxy will not be started if it does not exist. Note: This is the class name, not the file name or path. Boot-Class-Path Sets the path list for boot class loader searches. Paths represent directories or libraries (usually referenced as JAR or zip libraries on many platforms). After a platform-specific mechanism for finding a class fails, the boot class loader searches for these paths. Search the paths in the order listed. The paths in the list are separated by one or more spaces. Paths use the path component syntax of the hierarchical URI. If the path starts with a slash character ("/"), it is an absolute path, otherwise it is a relative path. The relative path is parsed based on the absolute path of the proxy JAR file. Ignore paths with incorrect format and non-existent paths. If the agent is started at a certain moment after the VM is started, the path that does not represent the JAR file is ignored. This property is optional. Can-Redefine-Classes Boolean (true or false, irrelevant to upper and lower case). Whether the required classes for this proxy can be redefined. Values other than true are considered false. This property is optional and the default value is false. Can-Retransform-Classes Boolean (true or false, irrelevant to upper and lower case). Whether the required classes for this proxy can be reconverted. Values other than true are considered false. This property is optional and the default value is false. Can-Set-Native-Method-Prefix Boolean value (true or false, irrelevant to upper and lower case). Whether the native method prefix required by this proxy can be set. Values other than true are considered false. This property is optional and the default value is false.
(3) All these Agent jar packages will be automatically added to the program's classpath. So there is no need to add them to the classpath manually. Unless you want to specify the order of classpaths.
(4) There is no limit on the number of parameters of -javaagent in a java program, so you can add as many java agents. All java agents will be executed in the order you define. For example:
java -javaagent:MyAgent1.jar -javaagent:MyAgent2.jar -jar MyProgram.jar
Suppose the main function in MyProgram.jar is in MyProgram. MyAgent1.jar, MyAgent2.jar, the classes that implement premain in these two jar packages are MyAgent1, and the execution order of the MyAgent2 program will be:
MyAgent1.premain -> MyAgent2.premain -> MyProgram.main
(5) In addition, premain placed after main function will not be executed, for example:
java -javaagent:MyAgent1.jar -jar MyProgram.jar -javaagent:MyAgent2.jar
MyAgent2 is placed behind MyProgram.jar, so the premain of MyAgent2 will not be executed, so the execution result will be:
MyAgent1.premain -> MyProgram.main
(6) Each java agent can receive a string-type parameter, that is, agentArgs in premain. This agentArgs is defined in java option. For example:
java -javaagent:MyAgent2.jar=thisIsAgentArgs -jar MyProgram.jar
The value of agentArgs received by premain in MyAgent2 will be "thisIsAgentArgs" (excluding double quotes).
(7) Instrumentation in the parameter: add the ClassFileTransformer defined by the parameter to change the class file. The custom Transformer here implements the transform method, which provides modification to the bytecode of the class to be actually executed, and can even reach the point of executing another class method. For example: Write agent class:
package org.toy;import java.lang.instrument.Instrumentation;import java.lang.instrument.ClassFileTransformer;public class PerfMonAgent { private static Instrumentation inst = null; /** * This method is called before the application's main-method is called, * when this agent is specified to the Java VM. **/ public static void premain(String agentArgs, Instrumentation _inst) { System.out.println("PerfMonAgent.premain() was called."); // Initialize the static variables we use to track information. inst = _inst; // Set up the class-file transformer. ClassFileTransformer trans = new PerfMonXformer(); System.out.println("Adding a PerfMonXformer instance to the JVM."); inst.addTransformer(trans); }}Write ClassFileTransformer class:
package org.toy;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtBehavior;import javassist.CtClass;import javassist.NotFoundException;import javassist.expr.ExprEditor;import javassist.expr.MethodCall;public class PerfMonXformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { byte[] transformed = null; System.out.println("Transforming " + className); ClassPool pool = ClassPool.getDefault(); CtClass cl = null; try { cl = pool.makeClass(new java.io.ByteArrayInputStream( classfileBuffer)); if (cl.isInterface() == false) { CtBehavior[] methods = cl.getDeclaredBehaviors(); for (int i = 0; i < methods.length; i++) { if (methods[i].isEmpty() == false) { doMethod(methods[i]); } } transformed = cl.toBytecode(); } } catch (Exception e) { System.err.println("Could not instrument " + className + ", exception : " + e.getMessage()); } finally { if (cl != null) { cl.detach(); } } return transformed; } private void doMethod(CtBehavior method) throws NotFoundException, CannotCompileException { // method.insertBefore("long stime = System.nanoTime();"); // method.insertAfter("System.out.println(/"leave "+method.getName()+" and time:/"+(System.nanoTime()-stime));"); method.instrument(new ExprEditor() { public void edit(MethodCall m) throws CannotCompileException { m.replace("{ long stime = System.nanoTime(); $_ = $proceed($$); System.out.println(/"" + m.getClassName()+"."+m.getMethodName() + ":/"+(System.nanoTime()-stime));}"); } }); }}); }}The above two classes are the core of the agent. When jvm starts, PerfMonAgent.premain will be called before the application is loaded. Then, a customized ClassFileTransforme, namely PerfMonXformer, is instantiated in PerfMonAgent.premain, and then a custom ClassFileTransformer is instantiated in PerfMonXformer, and then the instance of PerfMonXformer is added to the Instrumentation instance (transmitted from jvm). This makes PerfMonXformer.transform will be called when the class in the application is loaded. You can change the loaded class in this method. It is really magical. In order to change the bytecode of the class, I used jboss' javassist. Although you don't have to use it like this, jboss' javassist is really powerful, allowing you to easily change the bytecode of the class.
In the above method, I changed the bytecode of the class and added long stime = System.nanoTime(); to the method entrance of each class, and added System.out.println("methodClassName.methodName:"+(System.nanoTime()-stime));
Thank you for reading, I hope it can help you. Thank you for your support for this site!