二探lambda表达式
从例子二探lambda
传递Runnable创建Thread
java8之前
代码语言:javascript复制package com.baigt.learn.nolambda;
public class NoLambdaWithSecond {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// do some thing
}
});
}
}
查看编译情况
- 文件情况
D:IdeaProjectscourseoutproductionclassescombaigtlearnnolambda>ls
NoLambdaWithSecond$1.class NoLambdaWithSecond.class
java8
代码语言:javascript复制package com.baigt.learn;
public class LambdaWithSecond {
public static void main(String[] args) {
new Thread(()->{});
}
}
查看编译情况
- 查看编译目录
D:IdeaProjectscourseoutproductionclassescombaigtlearnlambda>ls
LambdaWithSecond.class
D:IdeaProjectscourseoutproductionclassescombaigtlearnlambda>
在上一篇文章中,我们说过,一般情况下,lambda表达的是一个匿名类,在java8之前,编译后会替我们生成一个比如XXX$num.class的文件。那么lambda中从上边来看好像没生成这个文件啊,是不是结论是错误的?
- 疑问?
我们的推测难道是错误的?怎么才能验证我们的结论是对的?再抛个问题,lambda因为其便捷性会被在项目中大量使用,会有什么弊端?
验证结论(一般是匿名内部类的实现),对比分析
ide反编译的文件隐藏了很多细节,java底层提供了javap命令可以显示更多的信息。那么我们就用这个命令来反编译下。
java8之前
代码语言:javascript复制D:IdeaProjectscourseoutproductionclassescombaigtlearnnolambda>javap -verbose NoLambdaWithSecond.class
Classfile /D:/IdeaProjects/course/out/production/classes/com/baigt/learn/nolambda/NoLambdaWithSecond.class
Last modified 2019-12-22; size 611 bytes
MD5 checksum 617cb5177a9bce206277b70044241fb9
Compiled from "NoLambdaWithSecond.java"
public class com.baigt.learn.nolambda.NoLambdaWithSecond
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // java/lang/Thread
#3 = Class #24 // com/baigt/learn/nolambda/NoLambdaWithSecond$1
#4 = Methodref #3.#22 // com/baigt/learn/nolambda/NoLambdaWithSecond$1."<init>":()V
#5 = Methodref #2.#25 // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
#6 = Class #26 // com/baigt/learn/nolambda/NoLambdaWithSecond
#7 = Class #27 // java/lang/Object
#8 = Utf8 InnerClasses
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/baigt/learn/nolambda/NoLambdaWithSecond;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 SourceFile
#21 = Utf8 NoLambdaWithSecond.java
#22 = NameAndType #9:#10 // "<init>":()V
#23 = Utf8 java/lang/Thread
#24 = Utf8 com/baigt/learn/nolambda/NoLambdaWithSecond$1
#25 = NameAndType #9:#28 // "<init>":(Ljava/lang/Runnable;)V
#26 = Utf8 com/baigt/learn/nolambda/NoLambdaWithSecond
#27 = Utf8 java/lang/Object
#28 = Utf8 (Ljava/lang/Runnable;)V
{
public com.baigt.learn.nolambda.NoLambdaWithSecond();
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baigt/learn/nolambda/NoLambdaWithSecond;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: new #2 // class java/lang/Thread
3: dup
4: new #3 // class com/baigt/learn/nolambda/NoLambdaWithSecond$1
7: dup
8: invokespecial #4 // Method com/baigt/learn/nolambda/NoLambdaWithSecond$1."<init>":()V
11: invokespecial #5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
14: pop
15: return
LineNumberTable:
line 5: 0
line 11: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
}
SourceFile: "NoLambdaWithSecond.java"
InnerClasses:
static #3; //class com/baigt/learn/nolambda/NoLambdaWithSecond$1
D:IdeaProjectscourseoutproductionclassescombaigtlearnnolambda>
java8
代码语言:javascript复制D:IdeaProjectscourseoutproductionclassescombaigtlearnlambda>javap -verbose LambdaWithSecond.class
Classfile /D:/IdeaProjects/course/out/production/classes/com/baigt/learn/lambda/LambdaWithSecond.class
Last modified 2019-12-22; size 1056 bytes
MD5 checksum 3395121fedc061cfcd4854241ddeb1e8
Compiled from "LambdaWithSecond.java"
public class com.baigt.learn.lambda.LambdaWithSecond
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Class #22 // java/lang/Thread
#3 = InvokeDynamic #0:#27 // #0:run:()Ljava/lang/Runnable;
#4 = Methodref #2.#28 // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
#5 = Class #29 // com/baigt/learn/lambda/LambdaWithSecond
#6 = Class #30 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/baigt/learn/lambda/LambdaWithSecond;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 lambda$main$0
#19 = Utf8 SourceFile
#20 = Utf8 LambdaWithSecond.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Utf8 java/lang/Thread
#23 = Utf8 BootstrapMethods
#24 = MethodHandle #6:#31 // 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;
#25 = MethodType #8 // ()V
#26 = MethodHandle #6:#32 // invokestatic com/baigt/learn/lambda/LambdaWithSecond.lambda$main$0:()V
#27 = NameAndType #33:#34 // run:()Ljava/lang/Runnable;
#28 = NameAndType #7:#35 // "<init>":(Ljava/lang/Runnable;)V
#29 = Utf8 com/baigt/learn/lambda/LambdaWithSecond
#30 = Utf8 java/lang/Object
#31 = Methodref #36.#37 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Lj
ava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#32 = Methodref #5.#38 // com/baigt/learn/lambda/LambdaWithSecond.lambda$main$0:()V
#33 = Utf8 run
#34 = Utf8 ()Ljava/lang/Runnable;
#35 = Utf8 (Ljava/lang/Runnable;)V
#36 = Class #39 // java/lang/invoke/LambdaMetafactory
#37 = NameAndType #40:#44 // 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;
#38 = NameAndType #18:#8 // lambda$main$0:()V
#39 = Utf8 java/lang/invoke/LambdaMetafactory
#40 = Utf8 metafactory
#41 = Class #46 // java/lang/invoke/MethodHandles$Lookup
#42 = Utf8 Lookup
#43 = Utf8 InnerClasses
#44 = 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;
#45 = Class #47 // java/lang/invoke/MethodHandles
#46 = Utf8 java/lang/invoke/MethodHandles$Lookup
#47 = Utf8 java/lang/invoke/MethodHandles
{
public com.baigt.learn.lambda.LambdaWithSecond();
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baigt/learn/lambda/LambdaWithSecond;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: pop
13: return
LineNumberTable:
line 5: 0
line 6: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
}
SourceFile: "LambdaWithSecond.java"
InnerClasses:
public static final #42= #41 of #45; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #24 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/MethodH
andle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#25 ()V
#26 invokestatic com/baigt/learn/lambda/LambdaWithSecond.lambda$main$0:()V
#25 ()V
D:IdeaProjectscourseoutproductionclassescombaigtlearnlambda>
上眼一看可能感觉是个啥,但如果你看过java8之前,会发现对比之前反编译的内容发生了很大的变化。首先是InnerClass部分,其次是多了个BootstrapMethods区域。下边是相关部分对比图
具体分析
- Runnable部分
java8之前指向到一个class #3处,java8时则指向#3 和0处(BootStrapMethods) 这个可能还是不直观,那么我们借助工具,jclasslib来再看下。
借助工具,我们更清晰的可以得出一些结论。
- methods 构成部分,java8出现了一个格式为“lambda$调用方法名$数量”的 一个静态方法
- Attributes构成部分,java8出现了一个BootstrapMethods。
接下来我们看下这个方法
BootstrapMethods
代码语言:javascript复制调用LambdaMetafactory.metafactory方法,传入的参数包含#25,26,#25类
BootstrapMethods:
0: #24 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/MethodH
andle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#25 ()V
#26 invokestatic com/baigt/learn/lambda/LambdaWithSecond.lambda$main$0:()V
#25 ()V
下边我们从 LambdaMetafactory.metafactory入手来继续分析下
LambdaMetafactory.metafactory源码分析
- metafactory 部分
代码语言:javascript复制入口
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
// 创建lambda内部类元工厂
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
// 构建
return mf.buildCallSite();
}
- InnerClassLambdaMetafactory
代码语言:javascript复制初始化比如类名、ClassWriter
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;
}
}
- java.lang.invoke.InnerClassLambdaMetafactory#buildCallSite
代码语言:javascript复制返回一个函数式接口的实例对象(生成相关字节码到jvm中)
CallSite buildCallSite() throws LambdaConversionException {
// 编织内部类
final Class<?> innerClass = spinInnerClass();
// 无参的话,通过构造方法返回实例,否则通过findstatic方式
if (invokedType.parameterCount() == 0) {
final Constructor<?>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction<Constructor<?>[]>() {
@Override
public Constructor<?>[] run() {
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
innerClass.getCanonicalName() ", got " ctrs.length);
}
try {
Object inst = ctrs[0].newInstance();
// 这部分不细讲(大概是给CallSite赋值MethodHandle对象)
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
} else {
try {
UNSAFE.ensureClassInitialized(innerClass);
// 这部分不细讲(大概是给CallSite赋值MethodHandle对象)
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP
.findStatic(innerClass, NAME_FACTORY, invokedType));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception finding constructor", e);
}
}
}
- java.lang.invoke.InnerClassLambdaMetafactory#spinInnerClass
代码语言:javascript复制内部类编织
private Class<?> spinInnerClass() throws LambdaConversionException {
String[] interfaces;
String samIntf = samBase.getName().replace('.', '/');
boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);
if (markerInterfaces.length == 0) {
interfaces = new String[]{samIntf};
} else {
// Assure no duplicate interfaces (ClassFormatError)
Set<String> itfs = new LinkedHashSet<>(markerInterfaces.length 1);
itfs.add(samIntf);
for (Class<?> markerInterface : markerInterfaces) {
itfs.add(markerInterface.getName().replace('.', '/'));
accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface);
}
interfaces = itfs.toArray(new String[itfs.size()]);
}
cw.visit(CLASSFILE_VERSION, ACC_SUPER ACC_FINAL ACC_SYNTHETIC,
lambdaClassName, null,
JAVA_LANG_OBJECT, interfaces);
// Generate final fields to be filled in by constructor
for (int i = 0; i < argDescs.length; i ) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE ACC_FINAL,
argNames[i],
argDescs[i],
null, null);
fv.visitEnd();
}
generateConstructor();
if (invokedType.parameterCount() != 0) {
generateFactory();
}
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
samMethodType.toMethodDescriptorString(), null, null);
new ForwardingMethodGenerator(mv).generate(samMethodType);
// Forward the bridges
if (additionalBridges != null) {
for (MethodType mt : additionalBridges) {
mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
mt.toMethodDescriptorString(), null, null);
new ForwardingMethodGenerator(mv).generate(mt);
}
}
if (isSerializable)
generateSerializationFriendlyMethods();
else if (accidentallySerializable)
generateSerializationHostileMethods();
cw.visitEnd();
// Define the generated class in this VM.
// 定义好在jvm中要使用的(生成)的字节码
final byte[] classBytes = cw.toByteArray();
// If requested, dump out to a file for debugging purposes
// 如果被要求,这里可以导出一个class文件作为调试使用
if (dumper != null) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
// 有兴趣的可以自己点进去看下,disk写操作
dumper.dumpClass(lambdaClassName, classBytes);
return null;
}
}, null,
new FilePermission("<<ALL FILES>>", "read, write"),
// createDirectories may need it
new PropertyPermission("user.dir", "read"));
}
// 通过UNSAFE本地方法将类加载到jvm中去
return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
}
- java.lang.invoke.InnerClassLambdaMetafactory#dumper
代码语言:javascript复制通过他可以生成具体lambda文件
// For dumping generated classes to disk, for debugging purposes
private static final ProxyClassesDumper dumper;
static {
// 通过 “jdk.internal.lambda.dumpProxyClasses”属性来指定具体目录来存放生成的lambda内部类
final String key = "jdk.internal.lambda.dumpProxyClasses";
String path = AccessController.doPrivileged(
new GetPropertyAction(key), null,
new PropertyPermission(key , "read"));
dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);
}
到这里,我们基本可以确定,lambda其实最后确定会生成匿名内部类且加载到jvm中,可以通过“jdk.internal.lambda.dumpProxyClasses”指定目录来存储到文件中
使用 jdk.internal.lambda.dumpProxyClasses
代码语言:javascript复制设置属性目录
package com.baigt.learn.lambda;
public class LambdaWithSecond {
public static void main(String[] args) {
System.setProperty("jdk.internal.lambda.dumpProxyClasses","d:\data\");
new Thread(()->{});
}
}
- 结果
D:datacombaigtlearnlambda>ls
LambdaWithSecond$$Lambda$1.class
D:datacombaigtlearnlambda>javap LambdaWithSecond$$Lambda$1.class
final class com.baigt.learn.lambda.LambdaWithSecond$$Lambda$1 implements java.lang.Runnable {
public void run();
}
D:datacombaigtlearnlambda>
上边提到一个问题,项目中大量使用lambda会有什么问题?
大量使用lambda可能会有什么问题?
从上述我们了解到,lambda默认虽然不生成class文件,但是依旧会生成字节码并load到jvm中。如果使用不当,当大量的这样的数据加载到vm中,后果是可想而知的。
当大量的class加载到vm中时,java8的metaspace空间可以急剧增长,而metaspace空间,默认会自动调整阈值的,直到os内存不足,申请不到空间,会被oskill掉。感兴趣的同学可以使用cglib中的Enhancer来实践下,调用jconsole或者jmc、jvisualvm来观察元空间变化。
结语
上述是个人心得,不对之处,欢迎指正。