《Java性能优化》第三章:Java性能优化工具集
O’Reilly Japan – Java性能
这本书的第三章总结
1篇介绍 – Qiita
2篇性能测试方法 – Qiita ←上篇文章
3篇Java性能工具箱 – Qiita ←本篇文章
4篇JIT编译器的原理 – Qiita ←下篇文章
5篇垃圾收集的基础 – Qiita
3.1 配备在操作系统中的工具和分析
最初使用的工具与Java无关。
在基于Unix的系统中,拥有sar命令、vmstat、iostat和prstat,而在Windows系统中则有一个名为typeperf的工具。
CPU使用率
CPU使用率可以分为以下两类。
-
- ユーザー時間: CPUがアプリケーションを実行している時間
-
- システム時間: CPUがOSのカーネルを実行している時間
- システム時間はアプリケーションと無関係ではない。ディスクへ書き込みを行ったり、ネットワークへ転送したりなどといったことをしています。
※在Windows操作系统中,系统时间被称为特权时间。
目标是尽可能提高处理器使用率并缩短执行时间。
CPU处于空闲状态的原因
-
- 同期プリミティブでロックの開放待ち
-
- 何かを(例えば、データベースからのレスポンス)を待っている。
-
- 行うべき処理が無い。
- といったことが考えられる。
有时候,我们也可能希望限制每个程序的CPU使用率。在这种情况下,我们可以强制将CPU周期设置为一定的空闲状态,或者降低程序的优先级等。
中央处理器的队列
Windows和Unix系统都有一种机制来监视可执行的(不处于输入输出等待或休眠状态的)线程。这个机制被称为运行队列(run queue)。
在Windows中被称为处理器队列,可以通过typeperf命令查看。
C:> typeperf -si 1 "\System\Processor Queue Length"
"05/11/2013 19:09:42.678","0.000000"
"05/11/2013 19:09:43.678","0.000000"
由于ランキュー中包含了当前正在运行的项目,因此它肯定是1或更多。
另一方面,Windows的处理器队列不包括当前正在运行的项目,因此它是0或更多。
当执行的线程数量超过CPU数量时,性能会下降。
在Unix系统中,希望队列的数量等于CPU的数量,而在Windows系统中,希望处理器队列的长度为0。
然而,这不是绝对的原则。
只要队列的长度偶尔增加,就没有问题。
另一方面,如果队列过长持续存在,会增加机器的负载。需要将处理分布到另一台机器上或者优化代码。
磁盘使用率
确认问题的提示。
例如,如果iostat -x的wMB/s(每秒写入字节数)较低,但w/s(每秒写入次数)较高,则可能提高性能使用批量写入处理。
反过来,如果写入太多,则可以确定那里成为瓶颈。
可以通过查看系统是否进行了交换来判断。这可能会对性能产生影响。
在vmstat中,可以看到si(交换入)和so(交换出)。
网络使用率
在中文中,网络流量监控通常需要使用专门的工具,标准的系统工具往往无法满足要求。在Unix系统中,通常使用netstat进行监控,而在Windows系统中,则使用typeperf。
在Unix系統中,nicstat被用來測量網絡帶寬。
3.2 Java 监控工具
-
- jcmd: 指定したプロセスについての情報が出てくる
-
- jconsole: GUIでJVMの動きを見ることができる。
-
- jhat: Webブラウザでヒープダンプをブラウズできる。
-
- jmap: ヒープダンプ、メモリの情報を取得できる。
-
- jinfo: JVMのシステムプロパティを表示できる
-
- jstack: Javaプロセスのスタックをダンプする。
-
- jstat: GCやクラスローディングについての情報を表示。
- jvisualvm: JVMの監視。ヒープダンプの解析ができる。
Java虚拟机(JVM)的基本信息。
执行时间
jcmd ${プロセスID} VM.uptime
系统属性
可以看到与System.getProperties()获取的值相同的值(由-Dhogehoge指定在启动时)。
jcmd ${プロセスID} VM.system_properties
jinfo -sysprops ${プロセスID}
JVM 版本
jcmd ${プロセスID} VM.version
JVM 的命令行参数
jcmd ${プロセスID} VM.command_line
JVM调优标志
jcmd ${プロセスID} VM.flags -all
如果去掉”all”,剩下的是指定的那些吗?
展示平台特定的默认值
java -XX:+PrintFlagsFinal -version
:=是指定了除默认值以外的值。
更改标记的动态值
首先,您可以查看下方设置的所有数值。
jinfo -flags ${プロセスID}
如果您想按设置项目来查看的话
jinfo -flag PrintGCDetails ${プロセスID}
更改設定值時
jinfo -flag -PrintGCDetails ${プロセスID}
jinfo -flag PrintGCDetails ${プロセスID}
线程的信息
可以使用jconsole或jvisualvm在GUI界面上查看正在运行的线程数。
线程堆栈显示方式
jstack ${プロセスID}
jcmd ${プロセスID} Thread.print
班级的资讯 de
使用jconsole或jstat。
在jstat中还可以获得有关编译的信息。
垃圾收集的动态分析
在jconsole中,可以用图形显示堆内存的使用率。
在jcmd中可以执行GC操作(类似于jconsole的功能吗?)。
通过jmap可以查看堆内存的总览信息。
jstat提供了多种视图来显示GC操作的相关情况。
堆栈转储的后续分析
可以使用jvisualvm(GUI工具)、jcmd或jmap来获取。
使用标准工具可以使用jvisualvm或jhat进行分析。
还有一个名为Eclipse Memory Analyzer Tool的第三方工具可供使用。
3.3性能优化工具
采样类型的性能分析器
有两种模式,即抽样模式和仪器化模式。
抽样模式的开销最小,可以尽可能减少性能特性的改变,以防止性能分析器的干扰。
然而,许多采样型的性能分析工具都不够精确。例如,定期通过定时器调用的工具只能感知到在定时器触发时执行的线程产生的事件等。
在大多数情况下,出现在剖析结果前面的方法仅占据了2-3%,所以即使我们努力将其加倍提速,也只能实现1%的优化。
具有检测功能的性能分析器
不同于采样型,还可以查看每个方法的调用次数,以及每秒平均调用次数等。
通过这样做,例如可以知道是否需要加快实现速度,或者改进以减少执行次数等。
instrumented模式下的性能分析器会通过修改字节码来实施诸如计算调用次数等的代码,因此在性能方面可能会存在不准确的情况。
例如,方法的代码大小增加可能会导致被判断为不需要内联化(在第四章会有详细说明)。
采样型分析器只能对处于安全点(被分配内存的状态)的线程进行分析,而仪器模式的分析器可以对其进行分析。
被阻止的方法和线程的时间线
阻塞方法(如LockSupport#park或Object#wait()等待的方法)不消耗CPU时间(不增加CPU使用率),因此即使它们在性能分析结果中排名靠前,也无法进行优化。
因此,大多数性能分析器默认情况下不会显示被阻塞的方法。
关于被阻塞的方法,查看线程的执行状态可以了解其运行情况。如果使用VisualVM,则可以查看Threads选项卡。
母语为中国的专业人士(Native Chinese Speaker)
使用本地配置文件,可以对JVM本身进行配置。
在中文中,有一个名为”Oracle Solaris Studio”的本地化性能分析工具。虽然它的名称中带有”Solaris”,但它也可以在Linux上运行。
如果在Solaris上运行,可以利用Solaris内核的内部结构来获取更多信息。
有提到这样的描述。由于我没有Solaris机器,所以无法尝试。
在使用本地工具时能获得的数据包括垃圾回收所花费的时间等。
3.4 Java任务控制中心
商用版本的Java中,以名为jmc的形式提供,不包含在开源版本中。要使用它,需要商用许可证。
JFR可以被改为:请给予您的反馈。
JMC的关键功能是JFR(Java Flight Recorder)功能,它可以了解线程是否阻塞和其他事件。
可以在JFR的内存视图中看到与垃圾收集有关的事件。在第5章和第6章中,建议在阅读时注意这个工具的作用。
在JFR的Code视图的Overview标签下,可以看到按包名汇总的值。这个功能非常罕见。
我们可以准确地获取有关锁膨胀的信息。
除此之外,还可以获取无法通过jcmd或jconsole获取的信息。
※ 正如第9章中所解释的,要获取一个锁,需要在一个简单的循环中等待(称为自旋),并使用特定于CPU和操作系统的代码来获取锁,这个过程被称为锁膨胀。
启用JFR
要启用,请在启动应用程序的命令行中附加以下标志:
-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder
JFR可以使用两种记录方法。
可以选择固定时间段的离散记录,或者连续记录,连续记录时使用环形缓冲区。
可以使用”-XX:+FlightRecorderOptions=${参数字符串}”来指定如何记录。这里有可用的选项。
即使对于正在运行的JVM,也可以使用以下命令进行设置(需要预先指定-XX:+FlightRecorder选项)。
jcmd ${プロセスID} JFR.start [ オプション ]
对于持续记录,可以使用以下命令将环形缓冲区内的数据输出到文件中。
jcmd ${プロセスID} JFR.dump [ オプション ]
使用以下命令可以输出关于正在运行的记录的信息(顺带一提,似乎可以在一个进程中进行多个记录,所以在进行多个记录时使用频率应该较高吧)。
jcmd ${プロセスID} JFR.check [verbose]
所以,停止记录。
jcmd ${プロセスID} JFR.stop [ オプション ]
JFR事件的选择
据说JFR可以进行扩展,并且可以创建独特的事件。
收集事件必然会带来一些额外负担。
然而,即使有额外负担,也有一些我们想要获取的事件。
例如,可以监视TLAB(线程本地缓冲区)的事件,以及对象是否直接分配到了旧区域等。