preface
We often encounter anonymous inner classes in our daily development, and anonymous inner classes hold references to external classes. What about the bytecode level? This article looks at inner classes and LAMda at the bytecode level
Anonymous inner classes
public class NoNameInnerClass {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run(a) {}}; r.run(); }}Copy the code
We created a new runable and called the run method. Now we’re going to class file the above class through Javac, We see two class files, NoNameInnerClass$1.class and nonameinnerclass.class, generated when compiled.
Let’s look at NoNameInnerClass$1.class and nonameinnerclass.class
//NoNameInnerClass$1.class
final class NoNameInnerClassThe $1implements Runnable {
NoNameInnerClass$1() {}public void run(a) {}}//NoNameInnerClass.class
public class NoNameInnerClass {
public NoNameInnerClass(a) {}public static void main(String[] var0) {
Runnable var1 = new Runnable() {
public void run(a) {}}; var1.run(); }}Copy the code
It’s intuitively obvious that we’re generating an extra $1 NoNameInnerClass, but we’re not using it in the NoNameInnerClass, so let’s look at the bytecode for the NoNameInnerClass
public class javaplan.NoNameInnerClass {
// The default constructor for NoNameInnerClass
public javaplan.NoNameInnerClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
/ / the main method
public static void main(java.lang.String[]);
Code:
// New a NoNameInnerClass$1 and call the NoNameInnerClass$1 constructor
0: new #2 // class javaplan/NoNameInnerClass$1
3: dup
4: invokespecial #3 // Method javaplan/NoNameInnerClass$1."<init>":()V
7: astore_1
Call the NoNameInnerClass$1 run method
8: aload_1
9: invokeinterface #4.1 // InterfaceMethod java/lang/Runnable.run:()V
14: return
}
Copy the code
At the bytecode level, an instance of NoNameInnerClass$1 is generated and the run method is called. Why else would an extra class file be generated for no reason?
Second, the lambda
With the above steps, we write a simple lamda
public class LamdaTest {
public static void main(String[] args) { Runnable r = ()->{ }; r.run(); }}Copy the code
We then compile the class file using javac and find, surprisingly, that there is no extra class file generated, just a LamdaTest. Class
Lamdatest.class bytecode: javap-c: javap-c: javap-c: javap-c: javap-c
public class javaplan.LamdaTest
//
minor version: 0
major version: 52
flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref #5.#15 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0: #20 // #0:run:()Ljava/lang/Runnable;
#3 = InterfaceMethodref #21.#22 // java/lang/Runnable.run:()V
#4 = Class #23 // javaplan/LamdaTest
#5 = Class #24 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 main
#11 = Utf8 ([Ljava/lang/String;)V
#12 = Utf8 lambda$main$0
#13 = Utf8 SourceFile
#14 = Utf8 LamdaTest.java
#15 = NameAndType #6: #7 // "<init>":()V
#16 = Utf8 BootstrapMethods
#17 = MethodHandle #6: #25 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
#18 = MethodType #7 // ()V
#19 = MethodHandle #6: #26 // invokestatic javaplan/LamdaTest.lambda$main$0:()V
#20 = NameAndType #27: #28 // run:()Ljava/lang/Runnable;
#21 = Class #29 // java/lang/Runnable
#22 = NameAndType #27: #7 // run:()V
#23 = Utf8 javaplan/LamdaTest
#24 = Utf8 java/lang/Object
#25 = Methodref #30.#31 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
#26 = Methodref #4.#32 // javaplan/LamdaTest.lambda$main$0:()V
#27 = Utf8 run
#28 = Utf8 ()Ljava/lang/Runnable;
#29 = Utf8 java/lang/Runnable
#30 = Class #33 // java/lang/invoke/LambdaMetafactory
#31 = NameAndType #34: #38 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
#32 = NameAndType #12: #7 // lambda$main$0:()V
#33 = Utf8 java/lang/invoke/LambdaMetafactory
#34 = Utf8 metafactory
#35 = Class #40 // java/lang/invoke/MethodHandles$Lookup
#36 = Utf8 Lookup
#37 = Utf8 InnerClasses
#38= Utf8 (Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; #39 = Class #41 // java/lang/invoke/MethodHandles
#40 = Utf8 java/lang/invoke/MethodHandles$Lookup
#41 = Utf8 java/lang/invoke/MethodHandles
{
public javaplan.LamdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: invokedynamic #2.0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #3.1 // InterfaceMethod java/lang/Runnable.run:()V
12: return
LineNumberTable:
line 5: 0
line 8: 6
line 9: 12
private static void lambda$main$0(a); descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 7: 0
}
SourceFile: "LamdaTest.java"
InnerClasses:
public static final #36= #35 of #39; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #17invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite; Method arguments: #18 ()V
#19 invokestatic javaplan/LamdaTest.lambda$main$0:()V
#18 ()V
Copy the code
You can see that the Invokedynamic instruction (specifically for LAMda) is invoked, pointing to constant pool #2;
Constant pool #2 points to #0, which corresponds to a special lookup called BootstrapMethods:
BootstrapMethods: The MetaFactory method is first called with Invokestatic
The arguments:
Take a look at this method implementation:
- Caller: Lookup context provided by the JVM
- InvokedName: Invokes the function name, in this case invokedName is “run”
- SamMethodType: The method signature (parameter type and return value type) defined by the functional interface, in this case the signature “()void” of the run method
- ImplMethod: The static method corresponding to the lambda expression generated at compile time
invokestatic LamdaTest.lambda$main$0
- InstantiatedMethodType: Generally the same as samMethodType or a special case of it, in this case “()void”
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
/ / main calls the InnerClassLambdaMetafactory again
public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
MethodType invokedType,
String samMethodName,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType,
booleanisSerializable, Class<? >[] markerInterfaces, MethodType[] additionalBridges)
throws LambdaConversionException {
super(caller, invokedType, samMethodName, samMethodType,
implMethod, instantiatedMethodType,
isSerializable, markerInterfaces, additionalBridges);
implMethodClassName = implDefiningClass.getName().replace('. '.'/');
implMethodName = implInfo.getName();
implMethodDesc = implMethodType.toMethodDescriptorString();
implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
? implDefiningClass
: implMethodType.returnType();
constructorType = invokedType.changeReturnType(Void.TYPE);
lambdaClassName = targetClass.getName().replace('. '.'/') + "$$Lambda$" + counter.incrementAndGet();
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
int parameterCount = invokedType.parameterCount();
if (parameterCount > 0) {
argNames = new String[parameterCount];
argDescs = new String[parameterCount];
for (int i = 0; i < parameterCount; i++) {
argNames[i] = "arg$" + (i + 1); argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i)); }}else{ argNames = argDescs = EMPTY_STRING_ARRAY; }}Copy the code
- Where lambda expressions are declared, an InvokeDynamic instruction is generated, along with a corresponding Bootstrap Method.
- Perform invokedynamic instruction for the first time, will call the corresponding guidance Method (the Bootstrap Method), the guidance Method will be called LambdaMetafactory. Metafactory Method dynamically generated inner classes
- The bootstrap method returns a dynamic CallSite object that calls the inner class that implements the Runnable interface
- The contents of lambda expressions are compiled into static methods that are called directly by the previously dynamically generated inner class
- Lambda is actually invoked by InvokeInterface to invoke the method of the inner class generated by CallSite