关于Java-8-Lambdas(By Richard Warburton Raoul-Gabriel Urma )

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 format = new Function() {
        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."":()V
4: aload_0
5: new #2 // class AnonymousClassExample1."":(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 f = s -> Integer.parseInt(s);
}
 0: aload_0
 1: invokespecial #1 // Method java/lang/Object."":()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 指令在操作的,即运行时进行初始化

我们遇到这种情况不是通过寻找场景,而是通过内存分析,并且有一个很好的业务用例来证明优化的合理性。我们还处在这样一个位置,即只分配一次对象,它大量重用lambda表达式,因此缓存非常有益。与任何性能调整练习一样,科学方法是普遍推荐的方法。
这是任何其他想要优化lambda表达式使用的最终用户也应该采用的方法。尝试编写干净、简单和实用的代码总是最好的第一步。任何优化,如这次吊装,只应针对真正的问题进行。编写捕捉分配对象的lambda表达式并不是天生的坏事,就像编写调用“new Foo()”的Java代码一样,它本身也不是坏事。
这一经验也表明,要从lambda表达式中获得最佳效果,使用它们的习惯用法非常重要。如果lambda表达式用于表示小型的纯函数,则它们不需要从其周围的范围捕获任何内容。和大多数事情一样,如果你保持简单,事情就会表现得很简单

C思考

1.我有在博客上看到有人写实验,判别Lambdas 与for循环的性能的实验,得出的结论是Lambdas 性能不好,要比for循环慢。这种说法和做法是比较没有经验验证的,事实与结果证明,Lambdas 性能是比较理想的。

2.我们现在关注的点在于Lambdas 的使用习惯,他第一次使用的时候会很慢,是因为她的运行机制有关系,运行时加载。因为他要加载oracle的asm类。

你可能感兴趣的:(关于Java-8-Lambdas(By Richard Warburton Raoul-Gabriel Urma ))