A 第一节
https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood/
Java 8 was released in March 2014 and introduced lambda expressions as its flagship feature. You may already be using them in your code base to write more concise and flexible code. For example, you can combine lambda expressions with the new Streams API to express rich data processing queries:
Java8于2014年3月发布,并引入了lambda表达式作为其旗舰特性。您可能已经在代码库中使用它们来编写更简洁和灵活的代码。例如,可以将lambda表达式与新的Streams API结合起来,以表示丰富的数据处理查询:
然后紧接着往下看
匿名内部类具有可能影响应用程序性能的不良特性。
首先,编译器为每个匿名内部类生成一个新的类文件。文件名通常看起来像类名$1,其中ClassName是定义匿名内部类的类的名称,后跟一个美元符号和一个数字。生成许多类文件是不可取的,因为每个类文件在使用之前都需要加载和验证,这会影响应用程序的启动性能。加载可能是一个昂贵的操作,包括磁盘I/O和解压JAR文件本身。
如果lambda被转换为匿名内部类,那么每个lambda都会有一个新的类文件。当加载每个匿名内部类时,它将占用JVM的元空间(java8代替永久代)。如果JVM将每个匿名内部类中的代码编译为机器代码,那么它将存储在代码缓存中。此外,这些匿名内部类将被实例化为单独的对象。因此,匿名内部类会增加应用程序的内存消耗。为了减少所有这些内存开销,引入一种缓存机制可能会很有帮助,这促使引入某种抽象层。
最重要的是,从一开始就选择使用匿名内部类来实现lambda,这将限制lambda未来实现更改的范围,以及它们根据未来JVM改进而演进的能力。
作者举了一个例子
import java.util.function.Function;
public class AnonymousClassExample {
Function<String, String> format = new Function<String, String>() {
public String apply(String input){
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
}
};
}
You can examine the bytecode generated for any class file using the command
您可以使用命令检查为任何类文件生成的字节码
不妨看看生成后的字节码文件
发现
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class AnonymousClassExample1."<init>":(LAnonymousClassExample;)V
13: putfield #4 // Field format:Ljava/util/function/Function;
16: return
翻译一下也就是说
5: 使用字节码操作new实例化AnonymousClassExample$1类型的对象。对新创建的对象的引用同时被推送到堆栈上。
8: 操作dup在堆栈上复制该引用。
10: 该值随后由invokespecial指令使用,该指令初始化匿名内部类实例。
13: 堆栈顶部现在仍然包含对对象的引用,该引用使用putfield指令存储在AnonymousClassExample类的format字段中
AnonymousClassExample1类文件,你会找到实现函数接口的代码。
将lambda表达式转换为匿名内部类将限制未来可能的优化(例如缓存),因为它们将绑定到匿名内部类字节码生成机制。因此,语言和JVM工程师需要一个稳定的二进制表示法,该表示法能够提供足够的信息,同时允许JVM在未来使用其他可能的实现策略。下一节将解释如何做到这一点!**
B 第二节
为了解决上一节中解释的问题,Java语言和JVM工程师决定将转换策略的选择推迟到运行时。Java7引入的新invokedynamic字节码指令为他们提供了一种机制,可以有效地实现这一点。lambda表达式到字节码的转换分两步执行:
生成一个invokedynamic调用站点(称为lambda factory),当调用该站点时,将返回lambda正在转换到的函数接口的实例;
将lambda表达式的主体转换为将通过invokedynamic指令调用的方法。
为了演示第一步,让我们检查编译包含lambda表达式的简单类时生成的字节码,例如:
import java.util.function.Function;
public class Lambda {
Function<String, Integer> f = s -> Integer.parseInt(s);
}
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: invokedynamic #2, 0 // InvokeDynamic
#0:apply:()Ljava/util/function/Function;
10: putfield #3 // Field f:Ljava/util/function/Function;
13: return
发现lambda表达式字节码是通过invokedynamic 指令在操作的,即运行时进行初始化