为什么JVM需要预热?

时间:2016-03-24 10:43:04

标签: java garbage-collection jvm low-latency hft

据我所知,在Java虚拟机(JVM)中,可能需要进行预热,因为Java使用延迟加载过程加载类,因此您需要确保在启动主事务之前初始化对象。我是一名C ++开发人员,没有必要处理类似的要求。

但是,我无法理解的部分如下:

  1. 你应该预热哪些部分的代码?
  2. 即使我预热了代码的某些部分,它仍保持多长时间(假设这个术语只表示你的类对象保留在内存中多长时间)?
  3. 如果每次收到活动时都有需要创建的对象,它会有什么帮助?
  4. 考虑一个例子,一个预期通过套接字接收消息的应用程序,并且交易可以是新订单,修改订单和取消订单或交易确认。

    请注意,该应用程序涉及高频交易(HFT),因此性能极为重要。

7 个答案:

答案 0 :(得分:25)

  

你应该预热哪些部分的代码?

通常,您不必做任何事情。但是,对于低延迟应用程序,您应该预热系统中的关键路径。你应该进行单元测试,所以我建议你在启动时运行它们来预热代码。

即使您的代码预热,您也必须确保您的CPU缓存保持温暖。在阻塞操作后,您可以看到性能显着下降,例如网络IO,最长50微秒。通常情况下这不是问题,但如果你在大多数情况下试图保持在50微秒以下,这在大多数情况下都会成为一个问题。

注意:预热可以允许Escape Analysis启动并在堆栈上放置一些对象。这意味着不需要对这些物体进行优化。在优化代码之前,最好对应用程序进行内存配置。

  

即使我预热了代码的某些部分,它仍保持多长时间(假设这个术语只表示你的类对象保留在内存中多长时间了?)

没有时间限制。这取决于JIt是否检测到优化代码时所做的假设是否是错误的。

  

如果每次收到活动时都有需要创建的对象,它会有什么帮助?

如果您想要低延迟或高性能,则应尽可能少地创建对象。我的目标是产生不到300 KB /秒。使用此分配率,您可以拥有足够大的Eden空间,以便每天进行一次小型收集。

  

考虑一个例子,一个预期通过套接字接收消息的应用程序,并且交易可以是新订单,修改订单和取消订单或交易确认。

我建议您尽可能多地重复使用对象,但如果它超出您的分配预算,则可能不值得担心。

  

请注意,该应用程序涉及高频交易(HFT),因此性能极为重要。

您可能对我们的开源软件感兴趣,该软件用于不同投资银行和对冲基金的HFT系统。

http://chronicle.software/

  

我的生产应用程序用于高频交易,每一点延迟都可能成为问题。很明显,在启动时如果你没有预热你的应用程序,它将导致几毫安的高延迟。

特别是您可能对https://github.com/OpenHFT/Java-Thread-Affinity感兴趣,因为此库可以帮助减少关键线程中的调度抖动。

  

并且据说需要预热的代码的关键部分应该运行(使用假消息)至少12K次以使其以优化的方式工作。为什么以及如何运作?

使用后台线程编译代码。这意味着即使某个方法可能有资格编译为本机代码,但它并不意味着它已经在编译器非常繁忙的情况下在启动时完成了esp。 12K并非不合理,但可能更高。

答案 1 :(得分:18)

变暖是指让一段代码运行足够多次以使JVM停止解释并编译为本机(至少是第一次)。一般来说,这是你不想做的事情。原因是JVM收集有关代码生成过程中使用的代码的统计信息(类似于配置文件引导的优化)。因此,如果有问题的代码块使用与真实数据具有不同属性的伪数据“加热”,则可能会损害性能。

编辑:由于JVM无法执行整个程序的静态分析(它无法知道应用程序将加载哪些代码),因此它可以从它收集的统计信息中对类型进行一些猜测。作为一个例子,当在一个确切的调用位置调用虚函数(用C ++说话)并且它确定所有类型具有相同的实现时,则该调用被提升为直接调用(或甚至内联)。如果后来的假设被证明是错误的,则旧代码必须“未编译”才能正常运行。 AFAIK HotSpot将呼叫站点分类为单态(单一实现),双变形(正好两个......转换为if(imp1-type){imp1} else {imp2})和完全多态...虚拟调度。

还有另一种情况,重新编译发生..当你有分层编译时。第一层将花费更少的时间来尝试生成优秀的代码,如果方法“足够热”,那么更昂贵的编译时代码生成器就会启动。

答案 2 :(得分:10)

预热很少需要 。在进行性能测试时,它是相关的,以确保JIT预热时间不会扭曲结果。

在正常的生产代码中,您很少看到用于预热的代码。 JIT将在正常处理期间预热,因此仅为此引入额外代码几乎没有优势。在最糟糕的情况下,您可能会引入错误,花费额外的开发时间甚至会损害性能。

除非您确定需要某种热身,否则不要担心。您描述的示例应用程序当然不需要它。

答案 3 :(得分:3)

  

为什么JVM需要预热?

现代(J)虚拟机在运行时收集有关最常使用哪些代码及其使用方式的统计信息。一个(数百个,如果不是数千个)示例是对虚拟函数(在C ++语言中)的调用的优化,其仅在实现上。这些统计数据的定义只能在运行时收集。

类加载本身也是热身的一部分,但它显然会在这些类中执行代码之前自动发生,所以没有太多担心

  

你应该预热哪些部分的代码?

对您的应用程序性能至关重要的部分。重要的部分是“加热”,就像在正常使用过程中使用的那样,否则将进行错误的优化(稍后撤消)。

  

即使我预热了代码的某些部分,它仍保持多长时间(假设这个术语只表示你的类对象保留在内存中多长时间了?)

这很难说基本上JIT编译器会不断监视执行和性能。如果达到某个阈值,它将尝试优化事物。然后,它将继续监视性能,以验证优化实际上是否有帮助。如果不是,它可能不优化代码。事情也可能发生,使优化无效,比如加载新类。我认为这些事情是不可预测的,至少不是基于stackoverflow的答案,但是有工具可以告诉你JIT正在做什么:https://github.com/AdoptOpenJDK/jitwatch

  

如果每次收到活动时都有需要创建的对象,它会有什么帮助。

一个简单的例子可能是:您在方法中创建对象,因为引用离开了方法的范围,这些对象将存储在堆上,并最终由垃圾收集器收集。如果使用这些对象的代码被大量使用,它可能最终会被一个大的方法内联,可能重新排序而无法识别,直到这些对象只存在于此方法中。此时,它们可以放在堆栈上,并在方法退出时被删除。这可以节省大量的垃圾收集,并且只会在一些热身后才会发生。

尽管如此:我对于需要做一些特别的热身活动的观点持怀疑态度。只需启动您的应用程序,并使用它,JIT编译器就可以做到这一点。如果遇到问题,请了解JIT对您的应用程序的作用,以及如何微调该行为或如何编写应用程序以使其受益最多。

我真正了解热身需求的唯一情况是基准测试。因为如果你在那里忽视它,你几乎可以得到伪造的结果。

答案 4 :(得分:2)

所有关于JIT编译器,在JVM上用于优化运行时中的字节码(因为javac因平台而无法使用先进或激进的优化技术 - 字节码的独立性

  1. 您可以预热处理邮件的代码。实际上,在大多数情况下,您不需要通过特殊的预热周期来实现:只需让应用程序启动并处理一些第一条消息 - JVM将尽力分析代码执行和进行优化:)使用假样本进行手动预热会产生更糟糕的结果

  2. 代码将在一段时间后进行优化,并将进行优化,直到程序流中的某个事件会降低代码状态(在JIT编译器尝试再次优化代码之后) - 此过程永远不会结束)

  3. 短生活对象也是要优化的主题,但通常它应该有助于您的消息处理终身代码更有效

答案 5 :(得分:2)

  

你应该预热哪些部分的代码?

一般来说,这个问题没有答案。这完全取决于您的申请。

  

即使我热身代码的某些部分,它仍然保持多长时间   (假设此术语仅表示您的类对象保留多长时间   在内存中)?

只要您的程序引用了对象,没有任何特殊的弱引用或类似的东西,对象就会保留在内存中。了解您的程序何时“引用”某些内容可能比您初看起来时更加模糊,但它是Java中内存管理的基础,值得付出努力。

  

如果每次都需要创建对象,它会有什么帮助   我收到一个活动。

这完全取决于应用程序。一般没有答案。

我鼓励您学习和使用Java来理解类加载,内存管理和性能监视等内容。实例化一个对象需要一些时间,一般来说,加载一个类需要更多的时间(当然,这通常不那么频繁地完成)。 通常是,一旦加载了一个类,它就会在程序的生命周期内保留在内存中 - 这是你应该理解的那种东西,而不仅仅是得到一个答案到。

如果您不了解它们,还有一些技巧可供学习。有些程序使用对象的“池”,在实际需要之前进行实例化,然后在需要时进行处理。这允许程序的时间关键部分避免在时间关键时段期间实例化所花费的时间。这些池维护一组对象(10?100?1000?10000?),并在需要时实例化更多等。但是管理池是一项重要的编程工作,当然,你占用了池中对象的内存。

完全可以使用足够的内存来更频繁地触发垃圾收集,并且放松你想要加速的系统。这就是为什么你需要了解它是如何工作的,而不只是“得到答案”。

另一个考虑因素 - 迄今为止,大多数努力使程序更快地被浪费,如同不需要的那样。如果没有对所考虑的应用程序和/或系统测量的丰富经验,您根本不知道哪里()优化是否会引人注目。系统/程序设计避免病态缓慢的情况是有用的,并且不需要花费大量时间和精力进行“优化”。大部分时间都是我们所有人都需要的。

- 编辑 - 将即时编辑添加到要学习和理解的事物列表中。

答案 6 :(得分:1)

我总是把它描绘成如下:

你作为(一个C ++开发人员)可以想象一个自动迭代方法,由jvm编译/加载/替换各个部分(假想的模拟){{1} },gcc -O0-O1-O2变体(如果认为有必要,有时还原它们)

我确信这不是严格意义上的事情,但对于C ++开发者来说可能是一个有用的类比。

在标准的jvm上,jit考虑代码片段的时间由-O3设置,默认为1500。 (来源和jvm版本各不相同 - 但我认为这是jvm8)

此外,我手头的book在Host Performace JIT Chapter(p59)中指出在JIT期间完成了以下优化:

  • 内联
  • 锁定消除
  • 虚拟呼叫消除
  • 非易失性存储器写入消除
  • 原生代码生成

编辑:

关于评论

  

我认为1500可能足以暗示JIT它应该编译   将代码转换为本机和停止解释。你同意吗?

我不知道它是否仅仅是一个提示,但由于openjdk是开源的,我们可以查看globals.hpp#l3559@ver-a801bc33b08c中的各种限制和数字(对于jdk8u)

(我不是一个jvm开发者,这可能是完全错误的地方)

  

将代码编译为本机并不一定意味着它也是   优化

根据我的理解 - 真实;特别是如果你的意思是-XX:CompileThreshold(强制编译) - 这个blog甚至声明它会阻止jvm进行任何分析 - 因此优化 - 如果你不运行-Xcomp(默认)。

  

因此,计时器开始对经常访问的本机代码进行采样   优化相同。你知道我们如何控制这个计时器间隔吗?

我真的不知道细节,但我链接的-Xmixed确实定义了一些频率间隔。