每当我在Java中编写异步代码时,我都必须使用Runnable
(Function
,Callable
,...)或新的lambda语法。与C ++模板不同,无法保证它将被编译器内联。
在什么情况下编译器可以优化它,这意味着比实例化Runnable
对象更有效? JIT怎么样?例如,流操作,延迟初始化,回调?
如果没有进行优化,HotSpot可以管理数百万Runnable
个实例而不会给GC带来任何显着的开销吗?一般来说,我是否应该关注在应用程序中广泛使用lambdas和回调?
答案 0 :(得分:1)
首先,您需要了解javac编译器的功能以及JVM通过JIT编译器执行的操作。
Runnable是一个接口,因此您可以创建一个实现该接口的类,然后将其实例传递给Thread
构造函数,也可以使用匿名内部类(AIC)。在这种情况下,javac
编译器将为您生成实现Runnable
并为您创建实例的合成类。
C ++使用静态,提前(AOT)编译,正如您所说,可以使用内联模板。 JVM使用自适应,及时(JIT)编译。加载类文件时,将解释字节码,直到JVM确定代码中存在热点并将其编译为可缓存的本机指令。所使用的优化的积极程度取决于所使用的JIT。 OpenJDK有两个JIT,C1和C2(有时也称为客户端和服务器)。 C1编写代码的速度更快,但优化程度更低。 C2编译需要更长时间,但优化更多。 run()
的{{1}}方法会在编译器确定是最佳优化时进行内联(这意味着如果它被大量使用,则最有可能)。我们在Azul(我为他们工作)最近发布了一个基于LLVM的新的JVM JIT,称为Falcon,它进一步优化。
Lambdas有点不同。任何Lambda表达式都可以转换为等效的AIC,并且对于JDK 8中的早期实现,这就是它们的实现方式,作为AIC的语法糖。要优化性能,Runnable
现在会生成使用javac
字节码的代码。通过执行此操作,它将实现Lambda的方式留给JVM,而不是在类文件中对其进行硬编码。 JVM可以使用AIC,它可以使用静态方法或一些其他实现方法。作为一个小问题,使用方法引用而不是显式Lambda对于性能来说要好一点。
对于问题的GC方面,它取决于代码的配置文件。如果您使用数百万invokedynamic
个对象,我会更关注Runnable
对象的影响。如果你没有汇集这些,那么创建和收集数百万个线程的GC开销将远远超过Thread
个对象的开销。只要可以在Eden空间中收集Runnable
个对象,开销实际上就是零。