前言
我们日常开发中的通常会遇到匿名内部类,并且匿名内部类会持有外部类的引用,那么字节码层面是如何的呢?本文从字节码层面看内部类和 lamda
一、匿名内部类
public class NoNameInnerClass {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
}
};
r.run();
}
}
我们new 了一个 runable 然后调用 run方法,现在我们通过javac 将上述类编译成class 文件,我们看到编译后会生成 NoNameInnerClass$1.class 和 NoNameInnerClass.class 两个class文件。
我们先看下NoNameInnerClass$1.class 和 NoNameInnerClass.class
//NoNameInnerClass$1.class
final class NoNameInnerClass$1 implements Runnable {
NoNameInnerClass$1() {
}
public void run() {
}
}
//NoNameInnerClass.class
public class NoNameInnerClass {
public NoNameInnerClass() {
}
public static void main(String[] var0) {
Runnable var1 = new Runnable() {
public void run() {
}
};
var1.run();
}
}
直观上看 虽然多生成了一个 NoNameInnerClass$1,但NoNameInnerClass 中并没有用到,别着急我们看下 NoNameInnerClass 的字节码
public class javaplan.NoNameInnerClass {
//NoNameInnerClass 的默认构造方法
public javaplan.NoNameInnerClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//main方法
public static void main(java.lang.String[]);
Code:
//new 了一个 NoNameInnerClass$1 然后调用了NoNameInnerClass$1 构造方法
0: new #2 // class javaplan/NoNameInnerClass$1
3: dup
4: invokespecial #3 // Method javaplan/NoNameInnerClass$1."<init>":()V
7: astore_1
//调用 NoNameInnerClass$1 run 方法
8: aload_1
9: invokeinterface #4, 1 // InterfaceMethod java/lang/Runnable.run:()V
14: return
}
可以看到,在字节码层面,其实生成了一个 NoNameInnerClass$1 的实例对象,并调用了run方法,不然哪会无缘无故的多生成一个class 文件呢?
二、lamda
同上述步骤,我们写一个简单的lamda
public class LamdaTest {
public static void main(String[] args) {
Runnable r = ()->{
};
r.run();
}
}
然后用javac 编译生成 class 文件,奇怪的是我们发现并没有生成多余的class 文件,只有一个 LamdaTest.class
我们看下 LamdaTest.class 的字节码注意这里简单用 javap -c 的话是看不出什么门道的,需要用 javap -c -v -p :
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();
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: #17 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;
Method arguments:
#18 ()V
#19 invokestatic javaplan/LamdaTest.lambda$main$0:()V
#18 ()V
可以看到调用了 invokedynamic 指令(专门为lamda 而生),会指向常量池#2;
而常量池 #2 指向了 #0 ,#0 对应的是一个特殊查找即BootstrapMethods:
BootstrapMethods: 首先用invokestatic 调用了 metafactory 方法
入参:
看下这个方法实现:
- caller:JVM 提供的查找上下文
- invokedName:表示调用函数名,在本例中 invokedName 为 "run"
- samMethodType:函数式接口定义的方法签名(参数类型和返回值类型),本例中为 run 方法的签名 "()void"
- implMethod:编译时生成的 lambda 表达式对应的静态方法
invokestatic LamdaTest.lambda$main$0 - instantiatedMethodType:一般和 samMethodType 是一样或是它的一个特例,在本例中是 "()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();
}
//主要又调用了InnerClassLambdaMetafactory
public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
MethodType invokedType,
String samMethodName,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType,
boolean isSerializable,
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;
}
}
- lambda 表达式声明的地方会生成一个 invokedynamic 指令,同时生成一个对应的引导方法(Bootstrap Method)
- 第一次执行 invokedynamic 指令时,会调用对应的引导方法(Bootstrap Method),该引导方法会调用 LambdaMetafactory.metafactory 方法动态生成内部类
- 引导方法会返回一个动态调用 CallSite 对象,这个 CallSite 调用实现了 Runnable 接口的内部类
- lambda 表达式中的内容会被编译成静态方法,前面动态生成的内部类会直接调用该静态方法
- 执行 lambda 其实是 invokeinterface 调用了 CallSite 生成的内部类的方法