您现在的位置是:首页 > 文章详情

你不知道Lambda的秘密和陷阱

日期:2019-12-22点击:304

二探lambda表达式

一探Lambda:https://my.oschina.net/lt0314/blog/3144851

从例子二探lambda

传递Runnable创建Thread

java8之前

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:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda>ls NoLambdaWithSecond$1.class NoLambdaWithSecond.class 

java8

package com.baigt.learn; public class LambdaWithSecond { public static void main(String[] args) { new Thread(()->{}); } } 

查看编译情况

  • 查看编译目录
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>ls LambdaWithSecond.class D:\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 -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:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda> 

java8

D:\IdeaProjects\course\out\production\classes\com\baigt\learn\lambda>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:\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/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 部分

入口

 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

初始化比如类名、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

返回一个函数式接口的实例对象(生成相关字节码到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

内部类编织

 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

通过他可以生成具体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

设置属性目录

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:\data\com\baigt\learn\lambda>ls LambdaWithSecond$$Lambda$1.class D:\data\com\baigt\learn\lambda>javap LambdaWithSecond$$Lambda$1.class final class com.baigt.learn.lambda.LambdaWithSecond$$Lambda$1 implements java.lang.Runnable { public void 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来观察元空间变化。

结语

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

作者:baigt 交流群:244930845

原文链接:https://my.oschina.net/lt0314/blog/3146028
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章