你不知道Lambda的秘密和陷阱 - 路上有你0314的个人空间 - OSCHINA

二探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


Original url: Access
Created at: 2019-12-24 09:54:13
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论