前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >二探Lambda 顶

二探Lambda 顶

作者头像
石奈子
发布2019-12-24 14:58:08
3700
发布2019-12-24 14:58:08
举报

二探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
            }
        });
    }
}
查看编译情况
  • 文件情况
代码语言:javascript
复制
D:\IdeaProjects\course\out\production\classes\com\baigt\learn\nolambda>ls
NoLambdaWithSecond$1.class  NoLambdaWithSecond.class

java8

代码语言:javascript
复制
package com.baigt.learn;
public class LambdaWithSecond {
    public static void main(String[] args) {
        new Thread(()->{});
    }
}
查看编译情况
  • 查看编译目录
代码语言:javascript
复制
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之前
代码语言:javascript
复制
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
代码语言:javascript
复制
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类

代码语言:javascript
复制
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

初始化比如类名、ClassWriter

代码语言:javascript
复制
    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中)

代码语言:javascript
复制
    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

通过他可以生成具体lambda文件

代码语言:javascript
复制
   // 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(()->{});
    }
}
  • 结果
代码语言:javascript
复制
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来观察元空间变化。

结语

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二探lambda表达式
    • 从例子二探lambda
      • java8之前
      • java8
      • 验证结论(一般是匿名内部类的实现),对比分析
      • 大量使用lambda可能会有什么问题?
    • 结语
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档