绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
你不知道Lambda的秘密和陷阱
2019-12-23 18:25:21

二探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[]>() {@OverridepublicConstructor[] 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 instancectrs[0].setAccessible(true); }returnctrs; } });if(ctrs.length !=1) {thrownewLambdaConversionException("Expected one lambda constructor for "+ innerClass.getCanonicalName() +", got "+ ctrs.length); }try{ Object inst = ctrs[0].newInstance();// 这部分不细讲(大概是给CallSite赋值MethodHandle对象)returnnewConstantCallSite(MethodHandles.constant(samBase, inst)); }catch(ReflectiveOperationException e) {thrownewLambdaConversionException("Exception instantiating lambda object", e); } }else{try{ UNSAFE.ensureClassInitialized(innerClass);// 这部分不细讲(大概是给CallSite赋值MethodHandle对象)returnnewConstantCallSite( MethodHandles.Lookup.IMPL_LOOKUP .findStatic(innerClass, NAME_FACTORY, invokedType)); }catch(ReflectiveOperationException e) {thrownewLambdaConversionException("Exception finding constructor", e); } } }

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 itfs =newLinkedHashSet (markerInterfaces.length +1); itfs.add(samIntf);for(Class markerInterface : markerInterfaces) { itfs.add(markerInterface.getName().replace('.','/')); accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface); } interfaces = itfs.toArray(newString[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 constructorfor(inti =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 methodMethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, samMethodType.toMethodDescriptorString(),null,null);newForwardingMethodGenerator(mv).generate(samMethodType);// Forward the bridgesif(additionalBridges !=null) {for(MethodType mt : additionalBridges) { mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName, mt.toMethodDescriptorString(),null,null);newForwardingMethodGenerator(mv).generate(mt); } }if(isSerializable) generateSerializationFriendlyMethods();elseif(accidentallySerializable) generateSerializationHostileMethods(); cw.visitEnd();// Define the generated class in this VM.// 定义好在jvm中要使用的(生成)的字节码finalbyte[] classBytes = cw.toByteArray();// If requested, dump out to a file for debugging purposes// 如果被要求,这里可以导出一个class文件作为调试使用if(dumper !=null) { AccessController.doPrivileged(newPrivilegedAction() {@OverridepublicVoidrun(){// 有兴趣的可以自己点进去看下,disk写操作dumper.dumpClass(lambdaClassName, classBytes);returnnull; } },null,newFilePermission("<<ALL FILES>>","read, write"),// createDirectories may need itnewPropertyPermission("user.dir","read")); }// 通过UNSAFE本地方法将类加载到jvm中去returnUNSAFE.defineAnonymousClass(targetClass, classBytes,null); }

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来观察元空间变化。

结语

上述是个人心得,不对之处,欢迎指正。

分享好友

分享这个小栈给你的朋友们,一起进步吧。

技术的”禅修“之路
创建时间:2019-07-22 20:29:48
RHCE,RHCDS,RHCSS,RHCA的禅修之路
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 孤独的行者
    栈主

小栈成员

查看更多
  • rikinglj
  • 栈栈
  • daxuesheng
  • lilyone
戳我,来吐槽~