Java 性能分析工具 (2):Java 内置监控工具

Java 性能分析工具 (2):Java 内置监控工具

引言

本文为 Java 性能分析工具系列文章第二篇,第一篇:操做系统工具。在本文中将介绍如何使用 Java 内置监控工具更加深刻的了解 Java 应用程序和 JVM 自己。在 JDK 中有许多内置的工具,其中包括:html

  • jcmd:打印一个 Java 进程的类,线程以及虚拟机信息。适合用在脚本中。使用 jcmd - h 来查看使用方法。
  • jconsole:提供 JVM 活动的图形化展现,包括线程使用,类使用以及垃圾回收(GC)信息。
  • jhat:帮助分析内存堆存储。
  • jmap:提供 JVM 内存使用信息,适用于脚本中。
  • jinfo:访问 JVM 系统属性,同时能够动态修改这些属性。
  • jstack:提供 Java 进程内的线程堆栈信息。
  • jstat:提供 Java 垃圾回收以及类加载信息。
  • jvisualvm:监控 JVM 的可视化工具,剖析运行中的应用程序,分析 JVM 堆存储。

下面将根据功能划分来详细介绍这些工具。java

VM 基本信息

JVM 工具可以提供一个运行中的 JVM 进程的基本信息,例如运行时间、使用中的 JVM 参数以及 JVM 系统属性。算法

  • uptimeJVM 运行的时间,jcmd process_id VM.uptime
  • system properties经过 System.getProperties() 能够获得的系统属性也能够经过下面的命令得到:

jcmd process_id VM.system_properties 或者 jinfo –sysprops process_id安全

这些属性包括全部经过命令行-D 选项设置的属性、应用程序动态添加的属性和 JVM 的默认属性。服务器

  • JVM version经过 jcmd process_id VM.version 得到。
  • JVM command lineJVM 命令行能够在 jconsole 中的 VM summary 中找到,或者经过 jcmd process_id VM.command_line 命令得到。
  • JVM 调优参数经过命令 jcmd process_id VM.flags [-all] 命令或者全部生效的调优参数得到。

使用调优参数(Tuning Flags)

因为调优参数很是繁多,须要借助 JVM 命令行和 JVM 调优参数来使用。使用 command_line 命令能够得到命令行中指定的调优参数,flags 命令能够得到经过命令设置的调优参数和 JVM 设置的调优参数。socket

经过 jcmd 命令能够得到一个运行中 JVM 内生效的调优参数。经过下面这条命令能够得到一个指定平台内生效的调优参数。ide

java other_options –XX:+PrintFlagsFinal –version

咱们须要把其余选项同时包含在这条命令中,尤为是设置了 GC 相关的调优参数。这条命令的部分输出以下所示,第一行中的冒号说明第一行的调优参数使用的不是默认值,而是如下三种方式设置:工具

  1. 经过命令行设置其余选项间接改变了此调优参数的值
  2. JVM 计算出默认值第二行因为没有包含冒号,说明此行的调优参数为当前 JVM 版本的默认值,最后一列的 product 说明此行的调优参数的值在不一样平台相同,而 pd product 说明此行的调优参数的值依赖于平台。
uintx InitialHeapSize := 4169431040 {product}intx InlineSmallCode = 2000 {pd product}

最后一列的其余选项:性能

manageable:此 flag 的值能够在运行时动态改变c2 diagnostic:此 flag 提供帮助工程师理解的编译器如何工做的帮助信息。

jinfo 命令能够查看某个单一 flag 的值,经过下面的命令:优化

jinfo -flag PringGCDetails process_id –XX:+PrintGCDetails

3.经过下面的命令能够设置某个 flag 的 manageable 属性来控制其可否在运行时被改变:

jinfo -flag -printgcdetails process_id # 关闭 PrintGCDetails 的 manageable 属性

尽管 jinfo 命令能够改变任何 flag 的值,但不能肯定 JVM 会接受这些改变。例如不少影响垃圾回收算法执行的 flag 都会在 JVM 启动时被设置,在 JVM 运行过程当中经过 jinfo 命令修改 flag 的值并不会影响算法执行。全部此命令只对那些 manageable 为真的 flag 起做用。

线程信息

jconsole 和 jvisualvm 命令能够帮助开发人员剖析应用程序运行过程当中线程的相关信息。经过 jstack process_id 命令能够查看线程的运行时栈信息,能够明确得到当前线程是否被阻塞。经过命令 jcmd process_id Thread.print 能够得到相同结果。

类信息

经过 jconsole 和 jstat 命令能够得到应用程序运行过程当中的全部类的相关信息,同时 jstat 命令也提供了类编译的相关信息。

垃圾回收信息

Jconsole 展现了 JVM 堆使用的状况,它所绘制的的动态图可以帮助开发人员了解堆的内部状况。jcmd 支持垃圾回收操做。jmap 提供堆信息总览。jstat 从不一样的角度展现垃圾回收是如何工做的。

Heap Dump 文件的后序处理

经过 jvisualvm 用户界面能够获得 Heap Dump 文件,经过 jcmd 和 jmap 也能够得到。Heap Dump 文件是堆的快照,通常使用 jvisualvm 和 jhat 来分析这个快照。

性能分析工具

Java 提供的性能分析器是最重要的分析工具。它的种类繁多,各有所长,使用不一样的分析器在分析同一个应用时可能会发现不一样的问题。在使用过程当中须要各取所长,这样才能对应用进行全面的分析。

基本上全部的 Java 性能分析器都是用 Java 实现的,经过套接字(socket)与被分析应用进行通讯来得到被分析应用的运行信息。须要注意的是,在使用性能分析工具调优被分析应用的同时,须要关注性能分析器其自身的性能。假如当被分析的应用程序产生十分庞大的信息,而将其发送至性能分析器时,若是性能分析器没有空间充分管理高效的内存堆来处理这些信息时,分析将没法进行。采用并行垃圾回收算法进行内存管理是当前性能分析器比较流行的作法,这种算法可以最大程度地下降内存溢出的可能。

性能分析分为采样模式和检测模式。下面将分别介绍这两种模式。

采样分析

采样模式是性能分析中最经常使用的模式,由于其对被分析应用程序影响最小,这一点很是重要。只有当性能分析过程对应用程序的影响降到最低,才能得到有价值的性能分析结果。

在采样分析模式中,分析器被定时触发工做。在工做周期内,分析器依次检查每一个线程并记录线程中正在运行的方法,在某些特定场景下,采样分析每每会带来错误的分析结果。例如,在图 1 中,某线程在一段时间内交替执行方法 A 和方法 B,每次当分析器被触发工做时,该线程都刚好在执行方法 B,那么分析器会认为该线程的全部时间都是在执行方法 B,可是事实并不是如此,该线程执行方法 A 的时间远大于执行方法 B 的时间,只是并未被分析器采样到。

图 1. 某一时间段内线程交替执行方法 A 和 B 示例图

661d16e01c626fd44e99e1523fa2cff6.jpeg

这是采样模式中最多见的错误,经过增长采样分析器的采样时间隔能够帮助咱们有效的减小这类错误的发生,由于时间间隔过小每每会增长采样分析器对被分析的应用程序产生性能方面的影响,从而致使分析结果失真。因此时间间隔须要根据被分析应用的特色经过屡次的试验以及经验来决定,权衡过大或太小的影响以后设定。

图 2. 采样模式分析示例图

9f356b51d5b0e1fbe250afe8fec1bcff.jpeg

图 2 所示是使用采样模式分析一个应用服务器 GlassFish 启动过程的结果。从图中能够看到,方法 defineClass1() 使用了 19%的时间,接下来是方法 getPackageSourceInternal(),占用了 10%的时间。Java 应用程序中定义的类会影响应用程序启动过程当中的性能表现,为了提升应用程序的启动速度,就必须经过提升类加载的速度,从而达到提高启动速度的目标。从图中咱们可能会错误的认为要改善性能的方法是 defineClass1(),可是 defineClass1() 实际上是 JDK 中的方法,咱们不可能经过重写 JVM 来提升它的性能。即便重写此方法将其执行时间优化至原有时间的 60%,也只能减小 10%应用程序总体运行时间,这显然得不偿失。

检测分析

相比于采样模式,检测模式是要侵入被分析的应用程序内部,虽然这样作并非高效、友好的,但它却能够得到很是有价值的信息。图 3 为使用相同分析工具的检测模式分析相同应用服务器 GlassFish 的结果。

图 3. 检测分析示例图

8575bc20cf442cf7c88e62eb272c9a50.jpeg

在图中有如下几点信息:

  1. 最耗时的方法为 getPackageSourcesInternal(), 占用了 13%的时间,而并不是在采样模式中获得的 4%;
  2. 方法 defineClaass1() 并未出如今分析结果中。
  3. 分析结果中包括每一个方法执行的次数和平均耗时。

这些分析结果中的信息对于发现耗时多的代码是很是有帮助的。在本例中,尽管方法 ImmutableMap.get() 消耗 12%的时间,可是它被调用了四百七十万次之多。若是减小此方法的调用次数,应用的性能将会获得大幅度提高。

检测分析器在类被加载时经过改变其字节码顺序来获取应用运行数据,例如增长记录方法被调用次数的代码。相比于采样模式,这种方式会更大程度的影响应用自己的性能。例如,JVM 会根据方法的代码块大小,将方法体很小的方法内联化,这样在内联方法执行时就不会进行方法调用。在检测分析器在内联方法中加入其代码后,此方法由于方法体过大并未被 JVM 内联化,由此形成此方法的耗时被放大。内联化只是一个例子,当愈来愈多的代码被改变的时候,分析的结果失真的几率就会比较大。

形成方法 ImmutableMap.get() 没有出如今采样模式分析结果中的缘由是安全点(safepoint)的存在。只有当一个线程得到的内存大于安全点时,采样分析器才会对其进行分析。由于方法 ImmutableMap.get() 所在线程一直没有达到安全点,因此在结果中不会出现。当使用采样模式安全点太高时,会低估一些方法对性能的影响。

在本例中,不管是采样分析仍是检测分析,都能发现应用的性能瓶颈在于类的加载和解析。可是在实际中,不一样的分析器不可能得出彻底相同的分析结果。分析器擅长估量,但也只是估量,一些偏差甚至是错误不可避免,因此在性能分析过程当中还须要咱们更加灵活的使用分析器。

阻塞方法和线程的时间轴

如图 4 所示为使用 NetBeans Profiler(另外一种检测分析器)分析上述应用服务器 GlassFish 启动过程的结果展现。在此结果中,方法 park(),parkNanos() 和 read() 占用了绝大多数的应用运行时间。这些方法都是被阻塞的方法,并不消耗 CPU,因此在计算应用的 CPU 使用率时这些时间不该计入。应用中的线程并无使用 632 秒来执行 parkNanos() 方法,而是等待其余操做完成花费 632 秒。park() 和 read() 方法与此同理。

图 4. NetBeans 检测分析示例图

592672cd9aab290c2e713d7c0584271a.jpeg

所以,大多数的分析器都不会将被阻塞的方法和闲置的线程计入结果。在 NetBeans 中,能够设置分析结果包含全部的方法,因此在本例中这些方法被计入结果。在本例中,执行 park() 方法的线程位于服务器线程池中,当服务器接收到请求时,这些线程处理请求。当没有请求时,这些线程处于阻塞状态,等到新的请求,并不占用 CPU。这是应用服务器的正常状态。

绝大多数的基于 Java 的分析器均可以提供过滤器功能来查看或者隐藏被阻塞方法调用的时间,若是须要可使用该功能。一般状况下,查看线程的运行情况比查看被阻塞方法的阻塞时间更加有帮助。

图 5. Oracle Solaris Studio 中线程的运行示例图

72edf578b84b573dae9393366e408752.png

图 5 为在 Oracle Solaris Studio 中一个线程的运行状况。每个水平区域表明一个不一样的线程,因此上图中有两个线程(1.3 和 1.2)。不一样颜色的柱子表明执行的不一样方法;空白处表明该线程没有执行任何方法。综合来看,线程 1.2 先执行了一段代码而后等待线程 1.3 完成执行,线程 1.3 完成执行后等待线程 1.2 执行另外一段代码。深刻下去能够发现这些线程如何进行交互。

图中存在一些没有线程执行的空白区域,这是由于图中只展现了其中的两个线程,因此在那段空白区域是图中所示两个线程在等待其余线程执行完成。

本地分析器

本地分析器是用来分析 JVM 自己的工具。经过本地分析器能够观察到 JVM 正在进行的操做或者查看是否有应用程序包含了 JVM 的本地库,也能够观察到代码内部。任何本地分析器均可以分析使用 C 语言实现的 JVM(包括全部本地库),可是一些本地分析其不能分析使用 Java 和 C++实现的应用。

图 6. 本地分析器分析示例图

9fc3ad5efd214a0d3254a734410272ef.jpeg

图 6 中展现了使用 Oracle Solaris Studio 分析器中分析 GlassFish 启动过程的结果。Oracle Solaris Studio 是一个能够分析 Java 和 C++的本地分析器。从图中能够发现,应用消耗的 CPU 时间为 25.1 秒。其中 JVM-System 消耗 20 秒,包括 JVM 编译器线程,垃圾回收线程以及一些辅助线程。因为在启动过程当中须要编译很是多的代码,因此 JVM 编译器线程消耗了绝大多数时间,而垃圾回收线程只消耗了不多的时间。

经过本地分析器咱们不只能够分析优化 JVM 自身功能,更重要的是能够得到应用程序进行垃圾回收的时间。在 Java 分析工具中,垃圾回收线程的信息是没法获得的。

分析过 JVM 本地代码后,咱们将对应用程序的启动过程进行分析。如图 7 所示,继在采样模式分析后,方法 defineclass1() 又一次被分析为最耗时的方法。值得关注的是,再次分析结果中,解压读取 jar 文件的方法耗时相对较多。类加载中会用到这些方法,因此证实优化的方向是正确的。因为 Java zip 库中引用的本地代码在其余分析工具中被做为阻塞方法调用,因此在上文各种工具中并无发现此方法。

图 7. 采样模式分析示例图

448037693708f8a4e1ba60ed6acd1ebf.jpeg

不管使用何种性能分析工具,最重要的是熟悉每种工具的优点和劣势。这样才能取长补短,配合使用。开发人员必须学会如何使用性能分析器来找到性能瓶颈,找到须要优化的代码,而不是单纯的关注最耗时的个别方法。

总结

基于采样的性能分析是最多见的一种,由于其相对能作到的分析是有限的,亦或者分析过程所能搜集到的信息是概述性的,每每并不能真实表现应用程序内部的运行状况,可是其分析过程当中引入的工做量一般是较低的。不一样的采样分析工具行为是不一样的,充分利用其优点,作有针对性的分析才是最有意义的。

检测分析可以得到很是多的有关应用程序内部信息,可是前期准备工做每每是很是大的。检测分析方法应当尽可能应用在一小节代码中,或者少数几个类、包中。这种方法其实必定程度上限制了对总体应用程序的性能分析,仅适合在程序单元中使用,点对点,针对性较强的分析,采用检测分析的时候,更多时间要求开发人员明确知道哪里有可能产生性能瓶颈。

线程阻塞不必定就是代码编写而产生的,发生线程阻塞时,更多的建议是去想,去看为何会被阻塞,而不是直接查看代码。尽可能采用线程执行时间轴的分析方法。

本地分析提供了既能够深刻查看 JVM 内部,同时也能够查看应用程序代码执行的状况。

若是本地分析显示在 GC 过程当中大量的使用 CPU 资源,那么调优收集器就是必要的。须要提醒你们的是,编译线程一般是不影响应用程序的性能。

原做者:李 伟军, 宋 翰瀛, 和 杨 翔宇
原文连接: Java 性能分析工具 , 第 2 部分:Java 内置监控工具
原出处:IBM Developer

64669c7e6712295f10a8c0382d616e16.jpeg

相关文章
相关标签/搜索