内存如何分配给lambda | 它是如何通过非超类引用变量引用的

The*_*ous 7 java lambda heap-memory java-8

我正在创建功能接口的实现,下面是我的代码

Consumer<Integer> consumer=new Consumer<Integer>() {
    @Override
    public void accept(Integer t) {
        System.out.println(t);
    }
};
Run Code Online (Sandbox Code Playgroud)

按照javadoc

类类型T的变量可以包含空引用或对类T的实例或作为T的子类的任何类的引用.

这里创建了匿名对象的对象,它是子类,Consumer可以通过引用变量引用consumer,这很精细.

但我看到Consumer就是FunctionalInterface,所以我也可以做这样的事情在java8-

使用Lambda

Consumer<Integer> consumer=t->System.out.println(t);
Run Code Online (Sandbox Code Playgroud)

或使用方法参考

Consumer<Integer> consumer=System.out::println;
Run Code Online (Sandbox Code Playgroud)

我知道在上述两种情况下都没有创建子类或匿名类.所以这导致我两次混淆 -

1:如果这里consumer没有引用子类或Anonymous类Consumer,那么这不违反上面提到的概念变量只能引用child/self或null?

2:内存如何分配给lamdas以及JVM在运行时如何处理?

Eug*_*ene 7

首先,让-巴蒂斯特 (Jean-Baptiste) 向您展示了为什么该作业首先起作用。

现在,我认为您缺少的部分是生成的类Consumerin case ofConsumer<Integer> consumer = t -> System.out.println(t);仅在运行时可见,因为invokedynamic.

使用标志运行您的课程:

java -Djdk.internal.lambda.dumpProxyClasses=/Your/Path
Run Code Online (Sandbox Code Playgroud)

您会注意到有一个生成的类(在类的包名称的文件夹路径中)包含.class类似这样的文件SOQuestion$$Lambda$1.class。

如果你反编译,你会看到它实际上是一个实现的类Consumer:

final class org.eugene.so.SOQuestion$$Lambda$1 
            implements java.util.function.Consumer {
     public void accept(java.lang.Object);
}
Run Code Online (Sandbox Code Playgroud)

如果您查看定义 lambda 的生成的字节码,您将看到 lambda 表达式实际上是去糖化为静态方法(对于非捕获 lambda,如果它是捕获 lambda,它也可以是非静态的)。它的代码:

private static void lambda$main$0(java.lang.Integer);
Code:
   0: getstatic     #3  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   7: return
Run Code Online (Sandbox Code Playgroud)

至于它如何VM管理内存,对于非捕获 lambda,您将获得一个单例,对于捕获 lambda,您将获得每个调用的新实例。绝对必读的在这里

  • 嘿,你在做什么?你已经更详细地解释了我所说的,以及 [@Holger 的回答](/sf/ask/1678868271/ /23991339#23991339) 准确地描述了“*它可能在链接阶段包含一个新类*”,所以你有我的 +1。做得好, :) (2认同)

Jea*_*nès 6

需要参考SE8 15.27:

15.27.3. Lambda 表达式的类型

如果 T 是函数接口类型(第 9.8 节)并且表达式与从 T 派生的基础目标类型的函数类型一致,则 lambda 表达式在具有目标类型 T 的赋值上下文、调用上下文或转换上下文中兼容。

运行时处理使用棘手的事情invokedynamic。让我们检查一些代码:

import java.util.function.*;

class R implements Runnable {
  public void run() { System.out.println("there"); }
}

public class L {
  public static void execute(Runnable r) {
    System.out.println(r.getClass());
    r.run();
  }
  public static void main(String []a) {
    execute(new R()); // subclass
    execute(new Runnable() { // anonymous subclass
        public void run() { System.out.println("elsewhere"); }
      });
    execute(() -> System.out.println("here")); // lambda
  }
}
Run Code Online (Sandbox Code Playgroud)

执行给出:

> java L
class R
there
class L$1
elsewhere
class L$$Lambda$1/791452441
here
Run Code Online (Sandbox Code Playgroud)

对于前两个,毫不奇怪,方法接收到的对象的类execute是(按给定顺序)R( 的子类型Runnable),L$1( 的匿名子类型Runnable,和L$$Lambda$1/791452441(Runnable在运行时从 lambda 构造的子类型)。请注意,在 lambda 没有.class文件的情况下,类型是在运行时通过特殊构造构造的。让我们检查字节码:

> javap -c -v L
Classfile /private/tmp/L.class
  Last modified 1 août 2017; size 1234 bytes
  MD5 checksum 9680a2bc143d25344979bae00fff3db7
  Compiled from "L.java"
public class L
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #15.#28        // java/lang/Object."<init>":()V
   #2 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #15.#31        // java/lang/Object.getClass:()Ljava/lang/Class;
   #4 = Methodref          #32.#33        // java/io/PrintStream.println:(Ljava/lang/Object;)V
   #5 = InterfaceMethodref #34.#35        // java/lang/Runnable.run:()V
   #6 = Class              #36            // R
   #7 = Methodref          #6.#28         // R."<init>":()V
   #8 = Methodref          #14.#37        // L.execute:(Ljava/lang/Runnable;)V
   #9 = Class              #38            // L$1
  #10 = Methodref          #9.#28         // L$1."<init>":()V
  #11 = InvokeDynamic      #0:#43         // #0:run:()Ljava/lang/Runnable;
  #12 = String             #44            // here
  #13 = Methodref          #32.#45        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #14 = Class              #46            // L
  #15 = Class              #47            // java/lang/Object
  #16 = Utf8               InnerClasses
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               execute
  #22 = Utf8               (Ljava/lang/Runnable;)V
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               lambda$main$0
  #26 = Utf8               SourceFile
  #27 = Utf8               L.java
  #28 = NameAndType        #17:#18        // "<init>":()V
  #29 = Class              #48            // java/lang/System
  #30 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #31 = NameAndType        #51:#52        // getClass:()Ljava/lang/Class;
  #32 = Class              #53            // java/io/PrintStream
  #33 = NameAndType        #54:#55        // println:(Ljava/lang/Object;)V
  #34 = Class              #56            // java/lang/Runnable
  #35 = NameAndType        #57:#18        // run:()V
  #36 = Utf8               R
  #37 = NameAndType        #21:#22        // execute:(Ljava/lang/Runnable;)V
  #38 = Utf8               L$1
  #39 = Utf8               BootstrapMethods
  #40 = MethodHandle       #6:#58         // 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;
  #41 = MethodType         #18            //  ()V
  #42 = MethodHandle       #6:#59         // invokestatic L.lambda$main$0:()V
  #43 = NameAndType        #57:#60        // run:()Ljava/lang/Runnable;
  #44 = Utf8               here
  #45 = NameAndType        #54:#61        // println:(Ljava/lang/String;)V
  #46 = Utf8               L
  #47 = Utf8               java/lang/Object
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               getClass
  #52 = Utf8               ()Ljava/lang/Class;
  #53 = Utf8               java/io/PrintStream
  #54 = Utf8               println
  #55 = Utf8               (Ljava/lang/Object;)V
  #56 = Utf8               java/lang/Runnable
  #57 = Utf8               run
  #58 = Methodref          #62.#63        // 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;
  #59 = Methodref          #14.#64        // L.lambda$main$0:()V
  #60 = Utf8               ()Ljava/lang/Runnable;
  #61 = Utf8               (Ljava/lang/String;)V
  #62 = Class              #65            // java/lang/invoke/LambdaMetafactory
  #63 = NameAndType        #66:#69        // 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;
  #64 = NameAndType        #25:#18        // lambda$main$0:()V
  #65 = Utf8               java/lang/invoke/LambdaMetafactory
  #66 = Utf8               metafactory
  #67 = Class              #71            // java/lang/invoke/MethodHandles$Lookup
  #68 = Utf8               Lookup
  #69 = 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;
  #70 = Class              #72            // java/lang/invoke/MethodHandles
  #71 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #72 = Utf8               java/lang/invoke/MethodHandles
{
  public L();
    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 9: 0

  public static void execute(java.lang.Runnable);
    descriptor: (Ljava/lang/Runnable;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #3                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        10: aload_0
        11: invokeinterface #5,  1            // InterfaceMethod java/lang/Runnable.run:()V
        16: return
      LineNumberTable:
        line 11: 0
        line 12: 10
        line 13: 16

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #6                  // class R
         3: dup
         4: invokespecial #7                  // Method R."<init>":()V
         7: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        10: new           #9                  // class L$1
        13: dup
        14: invokespecial #10                 // Method L$1."<init>":()V
        17: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        20: invokedynamic #11,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
        25: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        28: return
      LineNumberTable:
        line 15: 0
        line 16: 10
        line 19: 20
        line 20: 28
}
SourceFile: "L.java"
InnerClasses:
     static #9; //class L$1
     public static final #68= #67 of #70; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #40 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:
      #41 ()V
      #42 invokestatic L.lambda$main$0:()V
      #41 ()V
Run Code Online (Sandbox Code Playgroud)

第一个有趣的部分是以下代码main:

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #6                  // class R
         3: dup
         4: invokespecial #7                  // Method R."<init>":()V
         7: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        10: new           #9                  // class L$1
        13: dup
        14: invokespecial #10                 // Method L$1."<init>":()V
        17: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        20: invokedynamic #11,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
        25: invokestatic  #8                  // Method execute:(Ljava/lang/Runnable;)V
        28: return
Run Code Online (Sandbox Code Playgroud)

如您所见,接口的显式实现或匿名实现之间没有区别。最后一个只涉及类命名技巧(L$1),但两者的使用方式相同,通过invokestatic.

有趣的案例是第三个(lambda 案例),它涉及invokedynamic和 then invokestatic。请注意,这些invokestatic调用与前两次调用(方法run)中的方法相同。

粗略地说,第一次invokedynamic调用 bootstrap 方法时,会调用它来构造一个CallSite(请参阅Java API 中的 CallSite),然后将进一步使用它来执行 lambda 的代码。请参阅此处的引导程序调用:

BootstrapMethods:
  0: #40 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:
      #41 ()V
      #42 invokestatic L.lambda$main$0:()V
      #41 ()V
Run Code Online (Sandbox Code Playgroud)

以及调用站点引用的代码:

  #41 = MethodType         #18            //  ()V
  #42 = MethodHandle       #6:#59         // invokestatic L.lambda$main$0:()V
  #43 = NameAndType        #57:#60        // run:()Ljava/lang/Runnable;
  #44 = Utf8               here
  #45 = NameAndType        #54:#61        // println:(Ljava/lang/String;)V
  #46 = Utf8               L
  #47 = Utf8               java/lang/Object
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               getClass
  #52 = Utf8               ()Ljava/lang/Class;
  #53 = Utf8               java/io/PrintStream
  #54 = Utf8               println
  #55 = Utf8               (Ljava/lang/Object;)V
  #56 = Utf8               java/lang/Runnable
  #57 = Utf8               run
Run Code Online (Sandbox Code Playgroud)


hol*_*ava 5

问:如果这里的消费者不是指消费者的子类或匿名类......?

实际上,子类是在Linkage阶段通过invokedynamc指令引入的。

链接可能涉及动态加载实现目标接口的新类。的CallSite可以被认为是“工厂”为函数对象,因此这些连接方法被称为“metafactories”。

问:内存如何分配给 lamdas 以及 JVM 在运行时如何处理?

它通过三个阶段按顺序进行:

  • 链接- 可能涉及动态加载实现目标接口的新类。
  • 捕获- 产生一个函数对象。
  • 调用 - 在函数对象上调用已实现的接口方法。

有关更多详细信息,您可以进一步查看LambdaMetafactory。