二探lambda表达式
一探Lambda:https://my.oschina.net/lt0314/blog/3144851
从例子二探lambda
传递Runnable创建Thread
java8之前
packagecom.baigt.learn.nolambda;publicclassNoLambdaWithSecond{publicstaticvoidmain(String[] args){newThread(newRunnable() {@Overridepublicvoidrun(){// do some thing} }); }}
查看编译情况
文件情况
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda>lsNoLambdaWithSecond$1.classNoLambdaWithSecond.class
java8
packagecom.baigt.learn;publicclassLambdaWithSecond{publicstaticvoidmain(String[] args){newThread(()->{}); }}
查看编译情况
查看编译目录
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>lsLambdaWithSecond.classD:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>
在上一篇文章中,我们说过,一般情况下,lambda表达的是一个匿名类,在java8之前,编译后会替我们生成一个比如XXX$num.class的文件。那么lambda中从上边来看好像没生成这个文件啊,是不是结论是错误的?
疑问?
我们的推测难道是错误的?怎么才能验证我们的结论是对的?再抛个问题,lambda因为其便捷性会被在项目中大量使用,会有什么弊端?
验证结论(一般是匿名内部类的实现),对比分析
ide反编译的文件隐藏了很多细节,java底层提供了javap命令可以显示更多的信息。那么我们就用这个命令来反编译下。
java8之前
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda>javap-verboseNoLambdaWithSecond.classClassfile/D:/IdeaProjects/course/out/production/classes/com/baigt/learn/nolambda/NoLambdaWithSecond.classLastmodified2019-12-22;size611bytesMD5checksum617cb5177a9bce206277b70044241fb9Compiledfrom"NoLambdaWithSecond.java"publicclasscom.baigt.learn.nolambda.NoLambdaWithSecondminor version:0major version:52flags:ACC_PUBLIC,ACC_SUPERConstant 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{publiccom.baigt.learn.nolambda.NoLambdaWithSecond();descriptor:()Vflags:ACC_PUBLICCode:stack=1,locals=1,args_size=10:aload_01:invokespecial#1 // Method java/lang/Object."<init>":()V4:returnLineNumberTable:line 3:0LocalVariableTable:StartLengthSlotNameSignature050thisLcom/baigt/learn/nolambda/NoLambdaWithSecond;publicstaticvoidmain(java.lang.String[]);descriptor:([Ljava/lang/String;)Vflags:ACC_PUBLIC,ACC_STATICCode:stack=4,locals=1,args_size=10:new#2 // class java/lang/Thread3:dup4:new#3 // class com/baigt/learn/nolambda/NoLambdaWithSecond$17:dup8:invokespecial#4 // Method com/baigt/learn/nolambda/NoLambdaWithSecond$1."<init>":()V11:invokespecial#5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V14:pop15:returnLineNumberTable:line 5:0line 11:15LocalVariableTable:StartLengthSlotNameSignature0160args[Ljava/lang/String;}SourceFile:"NoLambdaWithSecond.java"InnerClasses:static#3; //class com/baigt/learn/nolambda/NoLambdaWithSecond$1D:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda>
java8
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>javap-verboseLambdaWithSecond.classClassfile/D:/IdeaProjects/course/out/production/classes/com/baigt/learn/lambda/LambdaWithSecond.classLastmodified2019-12-22;size1056bytesMD5checksum3395121fedc061cfcd4854241ddeb1e8Compiledfrom"LambdaWithSecond.java"publicclasscom.baigt.learn.lambda.LambdaWithSecondminor version:0major version:52flags:ACC_PUBLIC,ACC_SUPERConstant 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;Ljava/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{publiccom.baigt.learn.lambda.LambdaWithSecond();descriptor:()Vflags:ACC_PUBLICCode:stack=1,locals=1,args_size=10:aload_01:invokespecial#1 // Method java/lang/Object."<init>":()V4:returnLineNumberTable:line 3:0LocalVariableTable:StartLengthSlotNameSignature050thisLcom/baigt/learn/lambda/LambdaWithSecond;publicstaticvoidmain(java.lang.String[]);descriptor:([Ljava/lang/String;)Vflags:ACC_PUBLIC,ACC_STATICCode:stack=3,locals=1,args_size=10:new#2 // class java/lang/Thread3:dup4:invokedynamic#3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;9:invokespecial#4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V12:pop13:returnLineNumberTable:line 5:0line 6:13LocalVariableTable:StartLengthSlotNameSignature0140args[Ljava/lang/String;}SourceFile:"LambdaWithSecond.java"InnerClasses:publicstaticfinal#42= #41 of #45; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandlesBootstrapMethods: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/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;Method arguments:#25 ()V#26 invokestatic com/baigt/learn/lambda/LambdaWithSecond.lambda$main$0:()V#25 ()VD:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>
上眼一看可能感觉是个啥,但如果你看过java8之前,会发现对比之前反编译的内容发生了很大的变化。首先是InnerClass部分,其次是多了个BootstrapMethods区域。下边是相关部分对比图
具体分析
Runnable部分
java8之前指向到一个class #3处,java8时则指向#3 和0处(BootStrapMethods) 这个可能还是不直观,那么我们借助工具,jclasslib来再看下。
借助工具,我们更清晰的可以得出一些结论。
methods 构成部分,java8出现了一个格式为“lambda$调用方法名$数量”的 一个静态方法
Attributes构成部分,java8出现了一个BootstrapMethods。
接下来我们看下这个方法
BootstrapMethods
调用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/MethodHandle;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 部分
入口
publicstatic CallSite metafactory(MethodHandles.Lookup caller,StringinvokedName,MethodTypeinvokedType,Method*amMethodType,MethodHandleimplMethod,MethodTypeinstantiatedMethodType)throwsLambdaConversionException {AbstractValidatingLambdaMetafactorymf;//创建lambda内部类元工厂mf=new InnerClassLambdaMetafactory(caller, invokedType,invokedName,samMethodType,implMethod,instantiatedMethodType,false,EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);mf.validateMetafactoryArgs();//构建returnmf.buildCallSite();}
InnerClassLambdaMetafactory
初始化比如类名、ClassWriter
publicInnerClassLambdaMetafactory(MethodHandles.Lookup caller, MethodType invokedType, String samMethodName, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType,booleanisSerializable, Class[] markerInterfaces, MethodType[] additionalBridges)throwsLambdaConversionException{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 =newClassWriter(ClassWriter.COMPUTE_MAXS);intparameterCount = invokedType.parameterCount();if(parameterCount >0) { argNames =newString[parameterCount]; argDescs =newString[parameterCount];for(inti =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
返回一个函数式接口的实例对象(生成相关字节码到jvm中)
CallSitebuildCallSite()throwsLambdaConversionException{// 编织内部类finalClass innerClass = spinInnerClass();// 无参的话,通过构造方法返回实例,否则通过findstatic方式if(invokedType.parameterCount() ==0) {finalConstructor[] ctrs = AccessController.doPrivileged(newPrivilegedAction
java.lang.invoke.InnerClassLambdaMetafactory#spinInnerClass
内部类编织
privateClass spinInnerClass()throwsLambdaConversionException { String[] interfaces; String samIntf = samBase.getName().replace('.','/');booleanaccidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);if(markerInterfaces.length ==0) { interfaces =newString[]{samIntf}; }else{// Assure no duplicate interfaces (ClassFormatError)Set
java.lang.invoke.InnerClassLambdaMetafactory#dumper
通过他可以生成具体lambda文件
// For dumping generated classes to disk, for debugging purposesprivatestaticfinalProxyClassesDumper dumper;static{// 通过 “jdk.internal.lambda.dumpProxyClasses”属性来指定具体目录来存放生成的lambda内部类finalString key ="jdk.internal.lambda.dumpProxyClasses"; String path = AccessController.doPrivileged(newGetPropertyAction(key),null,newPropertyPermission(key ,"read")); dumper = (null== path) ?null: ProxyClassesDumper.getInstance(path); }
到这里,我们基本可以确定,lambda其实后确定会生成匿名内部类且加载到jvm中,可以通过“jdk.internal.lambda.dumpProxyClasses”指定目录来存储到文件中
使用 jdk.internal.lambda.dumpProxyClasses
设置属性目录
packagecom.baigt.learn.lambda;publicclassLambdaWithSecond{publicstaticvoidmain(String[] args){ System.setProperty("jdk.internal.lambda.dumpProxyClasses","d:\\data\\");newThread(()->{}); }}
结果
D:\data\com\baigt\learn\lambda>lsLambdaWithSecond$$Lambda$1.classD:\data\com\baigt\learn\lambda>javap LambdaWithSecond$$Lambda$1.classfinalclasscom.baigt.learn.lambda.LambdaWithSecond$$Lambda$1implementsjava.lang.Runnable{publicvoid run();}D:\data\com\baigt\learn\lambda>
上边提到一个问题,项目中大量使用lambda会有什么问题?
大量使用lambda可能会有什么问题?
从上述我们了解到,lambda默认虽然不生成class文件,但是依旧会生成字节码并load到jvm中。如果使用不当,当大量的这样的数据加载到vm中,后果是可想而知的。
当大量的class加载到vm中时,java8的metaspace空间可以急剧增长,而metaspace空间,默认会自动调整阈值的,直到os内存不足,申请不到空间,会被oskill掉。感兴趣的同学可以使用cglib中的Enhancer来实践下,调用jconsole或者jmc、jvisualvm来观察元空间变化。
结语
上述是个人心得,不对之处,欢迎指正。