首页 >科技周边 >IT业界 >Java虚拟机整天做什么?

Java虚拟机整天做什么?

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原创
2025-02-08 10:02:08243浏览

What Does the Java Virtual Machine Do All Day?

本文最初由Ampere Computing发布

最近看到一篇关于gprofng(一款新的GNU性能分析工具)的博文。该博文中使用了一个用C语言编写的矩阵向量乘法程序作为示例。我是一名Java™程序员,而使用为静态编译的C程序设计的工具来分析Java应用程序往往很困难,因为Java程序是在运行时编译的。在这篇博文中,我将展示gprofng易于使用且对于深入研究Java应用程序的动态行为非常有用。

第一步是编写一个矩阵乘法程序。我编写了一个完整的矩阵乘矩阵程序,因为它并不比矩阵乘向量更难。主要有三个方法:一个方法计算最内层的乘加运算,一个方法将乘加运算组合成结果的单个元素,一个方法迭代计算结果的每个元素。

我将计算包装在一个简单的测试程序中,以重复计算矩阵乘积,以确保时间是可重复的。(参见附注1。)程序打印出每次矩阵乘法开始的时间(相对于Java虚拟机启动的时间),以及每次矩阵乘法花费的时间。在这里,我运行了测试程序来乘以两个8000×8000的矩阵。测试程序重复计算11次,为了更好地突出后面的行为,在重复之间休眠920毫秒:

<code class="language-bash">$ numactl --cpunodebind=0 --membind=0 -- \
java -XX:+UseParallelGC -Xms31g -Xmx31g -Xlog:gc -XX:-UsePerfData \
  MxV -m 8000 -n 8000 -r 11 -s 920</code>

What Does the Java Virtual Machine Do All Day?

图1:运行矩阵乘法程序

请注意,第二次重复花费的时间是第一次重复时间的92%,而最后一次重复只花费了第一次重复时间的89%。执行时间的这些变化证实了Java程序需要一些时间来预热。

问题是:我能否使用gprofng来查看第一次重复和最后一次重复之间发生了什么,从而导致性能提升?

回答这个问题的一种方法是运行程序并让gprofng收集运行信息。幸运的是,这很容易:我只需在命令行前加上一个gprofng命令来收集gprofng所谓的“实验”信息:

<code class="language-bash">$ numactl --cpunodebind=0 --membind=0 -- \
gprofng collect app \
    java -XX:+UseParallelGC -Xms31g -Xmx31g -Xlog:gc --XX:-UsePerfData \
        MxV -m 8000 -n 8000 -r 11 -s 920</code>

What Does the Java Virtual Machine Do All Day?

图2:在gprofng下运行矩阵乘法程序

首先要注意的是,与任何性能分析工具一样,收集性能分析信息会对应用程序造成开销。与之前的未经分析的运行相比,gprofng似乎没有造成明显的开销。

然后,我可以询问gprofng整个应用程序中时间是如何花费的。(参见附注2。)对于整个运行过程,gprofng表示最热的24个方法是:

<code class="language-bash">$ gprofng display text test.1.er -viewmode expert -limit 24 -functions</code>

What Does the Java Virtual Machine Do All Day?

图3:gprofng显示最热的24个方法

上面显示的函数视图给出了每个方法的独占和包含CPU时间,以秒和总CPU时间的百分比表示。命名的函数是由gprofng生成的伪函数,具有各种指标的总值。在本例中,我看到整个应用程序的总CPU时间为1.201秒。

应用程序的方法(来自MxV类的那些方法)都在其中,占据了绝大部分的CPU时间,但也有一些其他的方法,包括JVM的运行时编译器(Compilation::Compilation),以及其他不是矩阵乘法程序一部分的函数。整个程序执行的此显示捕获了分配(MxV.allocate)和初始化(MxV.initialize)代码,我对这些代码不太感兴趣,因为它们是测试程序的一部分,仅在启动期间使用,并且与矩阵乘法几乎没有关系。

我可以使用gprofng来关注我感兴趣的应用程序部分。gprofng的一个很棒的功能是,在收集实验后,我可以将过滤器应用于收集到的数据。例如,查看特定时间间隔内发生的情况,或者在特定方法位于调用堆栈时发生的情况。为了演示目的并使过滤更容易,我添加了对Thread.sleep(ms)的策略性调用,以便更容易编写基于程序阶段(由一秒钟间隔分开)的过滤器。这就是为什么上面图1中的程序输出每个重复之间间隔大约一秒钟,即使每个矩阵乘法只花费大约0.1秒的原因。

gprofng是可脚本化的,所以我编写了一个脚本来从gprofng实验中提取单个秒。第一秒全部是关于Java虚拟机启动的。

What Does the Java Virtual Machine Do All Day?

图4:过滤第一秒中最热的方法。在此秒内,矩阵乘法已被人工延迟,以便我展示JVM启动

我可以看到运行时编译器正在启动(例如,Compilation::compile_java_method,占CPU时间的16%),即使应用程序的任何方法都尚未开始运行。(矩阵乘法调用被我插入的sleep调用延迟了。)

第一秒之后是一秒钟,在此期间分配和初始化方法与各种JVM方法一起运行,但矩阵乘法代码尚未开始。

What Does the Java Virtual Machine Do All Day?

图5:第二秒中最热的方法。矩阵分配和初始化与JVM启动竞争

现在JVM启动和数组的分配和初始化已经完成,第三秒有矩阵乘法代码的第一次重复,如图6所示。但请注意,矩阵乘法代码正在与Java运行时编译器(例如,CompileBroker::invoke_compiler_on_method,在图6中占8%)竞争机器资源,该编译器正在编译方法,因为矩阵乘法代码被发现很热。

即便如此,矩阵乘法代码(例如,MxV.main方法中的“包含”时间,91%)仍然获得了大部分CPU时间。包含时间表明矩阵乘法(例如,MxV.multiply)花费了0.100 CPU秒,这与图2中应用程序报告的实际时间一致。(收集和报告实际时间需要一些实际时间,这在gprofng计入MxV.multiply的CPU时间之外。)

What Does the Java Virtual Machine Do All Day?

图6:第三秒中最热的方法,表明运行时编译器正在与矩阵乘法方法竞争

在这个特定的例子中,矩阵乘法并没有真正争夺CPU时间,因为测试在一个多处理器系统上运行,该系统有大量的空闲周期,并且运行时编译器作为单独的线程运行。在更受限制的情况下,例如在负载很重的共享机器上,运行时编译器花费的8%的时间可能是一个问题。另一方面,在运行时编译器中花费的时间会产生更高效的方法实现,因此如果我计算许多矩阵乘法,那将是一项我愿意做的投资。

到第五秒时,矩阵乘法代码拥有了Java虚拟机。

What Does the Java Virtual Machine Do All Day?

图7:第五秒期间运行的所有方法,表明只有矩阵乘法方法处于活动状态

请注意MxV.oneCell、MxV.multiplyAdd和MxV.multiply之间在独占CPU秒方面的60%/30%/10%的分配。MxV.multiplyAdd方法只计算乘法和加法:但它是矩阵乘法中最内层的方法。MxV.oneCell有一个循环调用MxV.multiplyAdd。我可以看到循环开销和调用(评估条件和控制转移)比MxV.multiplyAdd中的直接算术运算相对更多工作。(MxV.oneCell的独占时间为0.060 CPU秒,而MxV.multiplyAdd为0.030 CPU秒,这反映了这种差异。)MxV.multiply中的外循环执行频率不够高,以至于运行时编译器尚未编译它,但该方法正在使用0.010 CPU秒。

矩阵乘法继续进行到第九秒,这时JVM运行时编译器再次启动,发现MxV.multiply已经变得很热。

What Does the Java Virtual Machine Do All Day?

在最后一次重复中,矩阵乘法代码充分利用了Java虚拟机。

What Does the Java Virtual Machine Do All Day?

图9:矩阵乘法程序的最后一次重复,显示了代码的最终配置

结论

我已经展示了通过使用gprofng进行性能分析来轻松深入了解Java应用程序运行时间的简便性。使用gprofng的过滤功能按时间片检查实验,使我能够检查感兴趣的程序阶段。例如,排除应用程序的分配和初始化阶段,并且只显示运行时编译器正在运行其魔术时程序的一次重复,这使我能够突出显示随着热代码被逐步编译而提高的性能。

进一步阅读

对于想要了解gprofng的读者,这里有一篇博文,其中包含关于gprofng的介绍性视频,包括如何在Oracle Linux上安装它的说明。

致谢

感谢Ruud van der Pas、Kurt Goebel和Vladimir Mezentsev的建议和技术支持,并感谢Elena Zannoni、David Banman、Craig Hardy和Dave Neary鼓励我撰写这篇博文。

附注

  1. 程序命令行组件的动机是:
  • numactl --cpunodebind=0 --membind=0 --。将Java虚拟机使用的内存限制在一个NUMA节点的核心和内存中。将JVM限制在一个节点上可以减少程序的运行间差异。
  • java。我使用的是aarch64的jdk-17.0.4.1的OpenJDK版本。
  • -XX: UseParallelGC。启用并行垃圾收集器,因为它在可用收集器中进行的后台工作最少。
  • -Xms31g -Xmx31g。提供足够的Java对象堆空间,永远不需要垃圾收集。
  • -Xlog:gc。记录GC活动以验证确实不需要收集。(“信任但要验证。”)
  • -XX:-UsePerfData。降低Java虚拟机的开销。
  1. gprofng选项的解释是:
  • -limit 24。仅显示前24个方法(此处按独占CPU时间排序)。我可以看到,显示24个方法可以让我很好地了解几乎不使用时间的那些方法。稍后,我将在某些地方使用limit 16,其中16个方法可以降低到贡献微不足道的CPU时间的那些方法。在某些示例中,gprofng本身会限制显示,因为没有那么多方法会累积时间。
  • -viewmode expert。显示所有累积CPU时间的那些方法,而不仅仅是Java方法,包括JVM本身的那些方法。使用此标志可以让我看到运行时编译器方法等。

以上是Java虚拟机整天做什么?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn