When we look at the Java Virtual Machine bytecode execution engine, there are 5 types of bytecode directives that call methods in the Java Virtual machine:
-
- Invokestatic//Call static method
- Invokespecial//Call private method, instance constructor method, parent class method
- Invokevirtual//Invoke instance method
- Invokeinterface//Invokes an interface method that determines at run time an object that implements this interface
- invokedynamic //First dynamically resolves the method referenced by the call Point qualifier at run time, and then executes the method, before which the 4 calling instructions, the dispatch logic is cured inside the Java Virtual machine, The dispatch logic of the invokedynamic instruction is determined by the user-defined guidance method.
The first 4 kinds are easy to understand, but the 5th kind of author himself from this section of the description can not understand what is the invokedynamic, so decided to start from the practice to analyze.
Invokedynamic itself is a bytecode command, we would like to call this command directly can only handwritten Java bytecode, this difficulty is too large. There are no alternatives, the answer is yes.
Introduction to ASM
The official definition: ASM is a Java bytecode manipulation and analysis framework. Can be used to edit the classes file and directly generate the class file dynamically, everything is directly based on the binary form.
Let me explain: we all know that A. java file is compiled with a. class file, the form of the. java file is the Java source code, and the. class file is in the form of a Java bytecode, which essentially stores the same content in a different form, and both can be related to transformations (compile and decompile). ASM itself is a Java library, so writing ASM code is essentially about writing Java source code, but the ultimate purpose of ASM code is not to run, but to generate bytecode.
As an example:
I now have a Test.java class:
Package Common; Public class Test { publicvoid say () { System.out.println ("Hi");
This class compiles after the generated Test.class,test.class file is actually a byte array, but we can use the Javap-verbose command to translate the bytecode command view (ignoring the constant pool and other irrelevant information, only intercept say () Code code for the method):
public void say (); Flags:acc_public code:stack =2, Locals=1, args_size=1 0: getstatic
#15 // Field java/lang/system.out:l Java/io/printstream; 3: LDC #21 // String Hi 5: invokevirtual #23 // Method java/io/printstream.println: (ljava/lang/ String;) V 8: return
My red part is a byte code translation from the bytecode command, bytecode and bytecode command is one by one corresponding, such as the invokevirtual command in the source file is a byte 0xb6, the corresponding relationship can look up the table: http://www.cnblogs.com/sheeva/ P/6279096.html
See here we'll find that the invokevirtual command here is the 3rd of the 5 bytecode of the Java calling method, If we can change the invokevirtual here to invokedynamic we can figure out what invokedynamic is doing, but the JAVAP command can only view the bytecode in the form of a command, but it can't be modified, and then it's ASM.
Download Asm5.2:http://download.forge.ow2.org/asm/asm-5.2-bin.zip
After decompression, find Asm-all-5.2.jar in Lib and put it in Test.class directory, execute:
Java-classpath "./*" Org.objectweb.asm.util.ASMifier Test.class,
Run results
Generated a Java class source code, there is a dump () method, this method return value is byte[], this byte[] Is the Test.class byte code, that is, if the return value of the method is saved to a file, then the file and the Test.class file is exactly the same.
To verify this conclusion, I paste this method and write a classloader to load the bytecode returned by the method and call the Say () Method:
Packageinvokedynamic;ImportOrg.objectweb.asm.AnnotationVisitor;ImportOrg.objectweb.asm.ClassWriter;ImportOrg.objectweb.asm.FieldVisitor;ImportOrg.objectweb.asm.MethodVisitor;ImportOrg.objectweb.asm.Opcodes; Public classHelloImplementsopcodes { Public Static voidMain (string[] args)throwsException {byte[] codes=dump (); Class<?> clazz=NewMyclassloader (). DefineClass ("Common. Test ", codes); Clazz.getmethod ("Say",NULL). Invoke (Clazz.newinstance (),Newobject[]{}); } Public Static byte[] Dump ()throwsException {classwriter cw=NewClasswriter (0); Fieldvisitor FV; Methodvisitor MV; Annotationvisitor av0; Cw.visit (v1_7, Acc_public+ Acc_super, "Common/test",NULL, "Java/lang/object",NULL); {MV= Cw.visitmethod (Acc_public, "<init>", "() V",NULL,NULL); Mv.visitcode (); MV.VISITVARINSN (Aload,0); MV.VISITMETHODINSN (Invokespecial,"Java/lang/object", "<init>", "() V",false); MV.VISITINSN (RETURN); MV.VISITMAXS (1, 1); Mv.visitend (); } {MV= Cw.visitmethod (Acc_public, "Say", "() V",NULL,NULL); Mv.visitcode (); MV.VISITFIELDINSN (Getstatic,"Java/lang/system", "Out", "Ljava/io/printstream;"); MV.VISITLDCINSN ("Hi"); MV.VISITMETHODINSN (invokevirtual,"Java/io/printstream", "println", "(ljava/lang/string;) V",false); MV.VISITINSN (RETURN); MV.VISITMAXS (2, 1); Mv.visitend (); } cw.visitend (); returnCw.tobytearray (); } Private Static classMyclassloaderextendsClassLoaderImplementsopcodes { PublicClass<?> defineclass (String name,byte[] b) { return Super. defineclass (name, B, 0, b.length); } }}
Run successfully:
Now that we're sure that the dump () method does generate Test.class bytecode, now look at the contents of the Dump () method:
Public Static byte[] Dump ()throwsException {classwriter cw=NewClasswriter (0); Fieldvisitor FV; Methodvisitor MV; Annotationvisitor av0; Cw.visit (v1_7, Acc_public+ Acc_super, "Common/test",NULL, "Java/lang/object",NULL); {MV= Cw.visitmethod (Acc_public, "<init>", "() V",NULL,NULL); Mv.visitcode (); MV.VISITVARINSN (Aload,0); MV.VISITMETHODINSN (Invokespecial,"Java/lang/object", "<init>", "() V",false); MV.VISITINSN (RETURN); MV.VISITMAXS (1, 1); Mv.visitend (); } {MV= Cw.visitmethod (Acc_public, "Say", "() V",NULL,NULL); Mv.visitcode (); mv.visitfieldinsn (getstatic, " Java/lang/system", "Out", "ljava/io/printstream;"); MV.VISITLDCINSN ("Hi"); MV.VISITMETHODINSN (invokevirtual, "Java/io/printstream", "println", "(ljava/lang/string;) V", False); MV.VISITINSN (RETURN); MV.VISITMAXS (2, 1); Mv.visitend (); } cw.visitend (); returnCw.tobytearray (); }
If you are familiar with bytecode, it should already be seen, dump () This method is in the class implementation of the Opcodes interface, the Opcodes interface defines almost all Java bytecode commands, we said before the 5 invoke command is also within:
Then take a look at the dump () method I marked Red 4, and the previous JAVAP command to call out the Test.class bytecode command against the view:
MV.VISITFIELDINSN (getstatic, "Java/lang/system", "Out", "ljava/io/printstream;"); MV.VISITLDCINSN ("Hi"); MV.VISITMETHODINSN (invokevirtual, "Java/io/printstream", "println", "(ljava/lang/string;) V" , false); MV.VISITINSN (RETURN);
Public void say (); Flags:acc_public Code: stack=2, Locals=1, args_size=1 0:getstatic // Field java/lang/system.out:ljava/io/printstream; 3:ldc #21 // String Hi 5:invokevirtual #23 // Method java/io/printstream.println: (ljava/lang/string;) V 8:return
No need to explain it.
Invoking the invokedynamic directive with ASM
Now let's remove the code from the original say method through the invokevirtual output hi, and change it to output hello via invokedynamic.
Add a class bootstrap to the package of the class Hello class where the Dump () method is located:
Packageinvokedynamic;ImportJava.lang.invoke.*; Public classBootstrap {Private Static voidHello () {System.out.println ("Hello!"); } Public StaticCallSite Bootstrap (methodhandles.lookup caller, String name, Methodtype type)throwsnosuchmethodexception, illegalaccessexception {methodhandles.lookup Lookup=Methodhandles.lookup (); Class ThisClass=Lookup.lookupclass (); Methodhandle MH= Lookup.findstatic (ThisClass, "Hello", Methodtype.methodtype (void.class)); return Newconstantcallsite (Mh.astype (type)); }}
The Hello class removes the lines from the Say () method that originally called the System.out.println () method through invokevirtual, replacing them with a dynamic call:
{mv= Cw.visitmethod (Acc_public, "Say", "() V",NULL,NULL); Mv.visitcode (); //mv.visitfieldinsn (getstatic, "Java/lang/system", "Out", "ljava/io/printstream;");//mv.visitldcinsn ("Hi");//mv.visitmethodinsn (invokevirtual, "Java/io/printstream", "println", "(ljava/lang/string;) V", false);//MV.VISITINSN (RETURN);//Mv.visitmaxs (2, 1);Methodtype MT= Methodtype.methodtype (CallSite.class, Methodhandles.lookup.class, String.class, Methodtype.class); Handle Bootstrap=NewHandle (opcodes.h_invokestatic, "Invokedynamic/bootstrap", "Bootstrap", mt.tomethoddescriptorstring ()); MV.VISITINVOKEDYNAMICINSN ("DynamicInvoke", "() V", Bootstrap); MV.VISITINSN (RETURN); MV.VISITMAXS (0, 1); Mv.visitend (); }
Run again, this time the output has changed:
Conclusion
Now let's combine the code we get and then re-understand the definition of invokedynamic:
The method referenced by the call-point qualifier is parsed dynamically at runtime, and/or the Hello method is parsed dynamically through the bootstrap method
Then execute the method,//That is, execute the Hello method
The dispatch logic of the invokedynamic instruction is determined by the user-defined guidance method. Here's the Guide method, which is our definition of the bootstrap method, where our logic is to dispatch the Hello method directly, but we can also write some logic, such as depending on the parameter type of the call time to dynamically decide which method to invoke
Now that we have practiced the use of the invokedynamic command ourselves, I believe many people still do not understand the meaning of this command, starting with the static type and dynamic type of the language:
A static type is one in which each variable declares a unique type and cannot be changed at initialization time.
A dynamic type means that a variable has no fixed type, and the type of the variable depends on the type of element inside it.
The Java language is statically typed. One might refer to generics, where Java generics are erased, meaning that although it may seem impossible to determine a variable type when writing Java source code, each variable has a deterministic type in the process of compiling Java into bytecode.
So from the Java language point of view, the previous 4 method invocation instructions are fully sufficient, but to know that the JVM is not only cross-platform, or cross-language, when someone on the JVM trying to develop dynamic type language, the problem comes:
Most of the JVM directives are type-independent, but not at the time of the method invocation, and each method call must indicate the method parameter and the return value type at compile-time, but the method parameters of the dynamic type language will not know the type until runtime, so the JDK has made such a "patch" : When the method is called with invokedynamic, it goes to the bootstrap method, in which the parameter type can be dynamically obtained, and then the appropriate method is assigned to the Callsite (dynamic call point) according to the parameter type, and the last real call is the Callsize method. This enables the method invocation of the dynamic type language to be implemented on the JVM.
Java7 invokedynamic Command Research