Brief introduction
The traditional way to measure function uptime without using instrumentation is to log the current system time before the function call and to record the current system time again after the function call is complete (to simplify the description, This article does not consider the timing error caused by the virtual machine process mapping to the local operating system process, and finally returns the difference of two data as the function run time. The weaknesses of this approach are:
- Statements for performance measurements are directly mixed into the logic code
- The logic used for performance measurement is repetitive and does not do code reuse.
Using the functionality provided by instrumentation, 结合 Apache 开源项目 BCEL
This article implements a proxy for measuring the run time of a function. Through proxy technology, the statement used for performance measurement is completely separated from the business logic, and the agent can be used to measure the running time of any method of any class, which greatly improves the reusability of the code.
Greeting Agent
Before implementing the function run time measurement agent, let's introduce the principle of instrumentation in Java 5 by implementing a simple greeting proxy. 每个代理的实现类必须实现 ClassFileTransformer 接口
. This interface provides a way to:
Public byte[] Transform ( ClassLoader loader, String className, Class CBR, Java.security.ProtectionDomain PD, byte[] classfilebuffer) throws Illegalclassformatexception
Through this method, 代理可以得到虚拟机载入的类的字节码(通过 classfileBuffer 参数)
. The various functions of the agent are generally implemented by manipulating this string of bytecode. You also need to provide a common static method:
public static void Premain (String Agentargs, Instrumentation Inst)
This is usually the method 创建一个代理对象,通过参数 inst 的 addTransformer() 方法,将创建的代理对象再传递给虚拟机
. This method is an ingress method, somewhat similar to the main method of the General class. Figure 1 shows how the agent works:
As you can see, multiple agents can execute at the same time. 多个代理的 premain 方法将按照代理指定的顺序被依次调用
.
The following code fragment demonstrates the transform method of the greeting proxy. In this method, we make a simple customization of the agent's behavior-the output needs the class name that the agent monitors.
Public byte[] Transform (ClassLoader loader, String className, Class CBR, Java.security.ProtectionDomain PD, byte[] classfilebuffer) Throws Illegalclassformatexception { System.out.println ("hello,\t" + className); return null; }
The last of the transform function 返回 null 值,表示不需要进行类字节码的转化
. After customizing the behavior of the agent, create an instance of the greeting agent to pass the instance to the virtual machine.
public static void Premain (String options, instrumentation ins) { if (options! = null) { System.out.printf (" I ' ve been called with options: \ "%s\" \ n ", options); } else System.out.println (" I ' ve been called with no options."); Ins.addtransformer (new greeting ());}
The options parameter is passed in through the command line, similar to the argument passed when the main function is called. The command line argument that is passed in is a complete string, unlike the Main method, which is fully parsed by the agent itself. Listing 3 shows how to invoke the proxy using the command line:
Java-javaagent:greeting.jar= "Hello, sample" sample
This command indicates that the greeting proxy is invoked with the parameter "Hello, sample" to detect the operation of the Sample class. The results after running the command are as follows:
The agent needs to be packaged in a jar file that complies with a specific standard. The MANIFEST of the jar file. MF files need to include special items to define information such as proxy classes. (See the Java 5 specification for more information) in Listing 4, we specify that the proxy class for the greeting proxy is greeting.class.
manifest-version:1.0
Premain-class:greeting
Timing Agent
After introducing the rationale for the agent, the following will implement a proxy--timing for measuring the run time of the function. The traditional code snippet for function runtime measurement is:
public void Main (string[] args) { Long timeb = System. Currenttimemillis (); (1) methodx (); System.out.print (Getcurrentthreadcputime ()-TIMEB); (2)}private static void Methodx () { //originial code}
After using the proxy, the statement (1) (2) can be dynamically added to the class bytecode, resulting in a byte code equivalent to the following code fragment.
public void Main (string[] args) { methodx ();} private static void Methodx_original () { //originial code}private static void Methodx () { long timeb = GetCurrent Threadcputime (); Methodx_original (); Long period = System. Currenttimemillis ()-Timeb; }
Listing 7 gives the complete code for the timing agent, where the AddTimer method uses the power of BCEL to dynamically modify the class bytecode passed in by the virtual machine. For a detailed introduction to the BCEL project, this article does not repeat, see the BCEL Project's home page.
Import Java.lang.instrument.classfiletransformer;import Java.io.bytearrayoutputstream;import Java.lang.instrument.classfiletransformer;import Java.lang.instrument.illegalclassformatexception;import Java.lang.instrument.instrumentation;import Org.apache.bcel.constants;import Org.apache.bcel.classfile.classparser;import Org.apache.bcel.classfile.javaclass;import Org.apache.bcel.classfile.method;import Org.apache.bcel.generic.classgen;import Org.apache.bcel.generic.constantpoolgen;import Org.apache.bcel.generic.instructionconstants;import Org.apache.bcel.generic.instructionfactory;import Org.apache.bcel.generic.instructionlist;import Org.apache.bcel.generic.methodgen;import Org.apache.bcel.generic.objecttype;import Org.apache.bcel.generic.PUSH; Import Org.apache.bcel.generic.type;public class Timing implements Classfiletransformer {private String methodName; Private Timing (String methodName) {this.methodname = MethodName; System.out.println (MethodName); } public Byte[] Transform (ClassLoader loader, String className, Class CBR, Java.security.ProtectionDomain PD, byte[] Cl Assfilebuffer) throws Illegalclassformatexception {try {classparser CP = new Classparser (NE W Java.io.ByteArrayInputStream (Classfilebuffer), ClassName + ". Java"); Javaclass Jclas = Cp.parse (); Classgen Cgen = new Classgen (Jclas); Method[] methods = Jclas.getmethods (); int index; for (index = 0; index < methods.length; index++) {if (Methods[index].getname (). Equals (MethodName)) { Break }} if (Index < methods.length) {AddTimer (Cgen, Methods[index]); Bytearrayoutputstream BOS = new Bytearrayoutputstream (); Cgen.getjavaclass (). Dump (BOS); return Bos.tobytearray (); } System.err.println ("Method" + methodName+ "not found in" + className); System.exit (0); } catch (IOException e) {System.err.println (e); System.exit (0); } return null; No transformation required} private static void AddTimer (Classgen Cgen, method) {//Set up the CO Nstruction Tools Instructionfactory ifact = new Instructionfactory (Cgen); Instructionlist ilist = new Instructionlist (); Constantpoolgen Pgen = Cgen.getconstantpool (); String cname = Cgen.getclassname (); Methodgen Wrapgen = new Methodgen (method, CNAME, Pgen); Wrapgen.setinstructionlist (IList); Rename a copy of the original method Methodgen Methgen = new Methodgen (method, CNAME, Pgen); Cgen.removemethod (method); String iname = methgen.getname () + "_timing"; Methgen.setname (Iname); Cgen.addmethod (Methgen.getmethod ()); Type result = Methgen.getreturntype (); COMPUTE thE size of the calling parameters type[] Parameters = Methgen.getargumenttypes (); int stackindex = Methgen.isstatic ()? 0:1; for (int i = 0; i < parameters.length; i++) {Stackindex + = Parameters[i].getsize (); }//Save time prior to invocation ilist.append (Ifact.createinvoke ("Java.lang.System", "Currentti Memillis ", Type.long, Type.no_args, constants.invokestatic)); Ilist.append (instructionfactory. CreateStore (Type.long, Stackindex)); Call the wrapped method int offset = 0; Short invoke = Constants.invokestatic; if (!methgen.isstatic ()) {Ilist.append (instructionfactory. Createload (type.object, 0)); offset = 1; Invoke = Constants.invokevirtual; } for (int i = 0; i < parameters.length; i++) {Type type = Parameters[i]; Ilist.append (instructionfactory. CreateloAD (type, offset)); Offset + = Type.getsize (); } ilist.append (Ifact.createinvoke (CNAME, iname, result, parameters, Invoke)); Store result for return later if (result! = type.void) {ilist.append (instructionfactory. CreateStore (result, stackindex+2)); }//Print time required for method call Ilist.append (Ifact.createfieldaccess ("Java.lang.System", "Out", New ObjectType ("Java.io.PrintStream"), constants.getstatic)); Ilist.append (Instructionconstants.dup); Ilist.append (Instructionconstants.dup); String Text = "Call to Method" + methgen.getname () + "took"; Ilist.append (New PUSH (Pgen, text)); Ilist.append (Ifact.createinvoke ("Java.io.PrintStream", "print", type.void, new type[] {type.string}, constants.invokevirtual)); Ilist.append (Ifact.createinvoke ("Java.lang.System", "CurrenttimEmillis ", Type.long, Type.no_args, constants.invokestatic)); Ilist.append (instructionfactory. Createload (Type.long, Stackindex)); Ilist.append (instructionconstants.lsub); Ilist.append (Ifact.createinvoke ("Java.io.PrintStream", "print", type.void, new type[] {Type.long}, constants.invokevirtual)); Ilist.append (New PUSH (Pgen, "Ms.")); Ilist.append (Ifact.createinvoke ("Java.io.PrintStream", "println", Type.void, new type[] {type.string}, constants.invokevirtual)); Return result from wrapped method, call if (result! = type.void) {ilist.append (instructionfactory. Createload (result, stackindex+2)); } ilist.append (Instructionfactory.createreturn (result)); Finalize the constructed method Wrapgen.stripattributes (true); Wrapgen.setmaxstack (); Wrapgen.setmaxlocals (); Cgen.addmethod (Wrapgen.getmethod ()); Ilist.dispose (); public static void Premain (String options, instrumentation ins) {if (options! = null) {Ins.addtra Nsformer (new Timing (options)); } else {System.out. println ("usage:java-javaagent:timing.jar=\" class:method\ ""); System.exit (0); } }}
By calling the Timing proxy, the byte code of the instrumented class does not change after the run is finished. function run time detection, is through the runtime, the dynamic insertion function, and change the call sequence to achieve. Figure 3 shows the java -javaagent:Timing.jar="helloWorld"
result of using the command line Sample to run Agent Timing.
Java 5 features instrumentation Practice