这篇文章优锐课主要和大家讲讲Java性能调优指南——有关提高Java代码性能的各种技巧。

1. 介绍

Java世界中,我们大多数人习惯于在Java应用程序开发的所有阶段使用GUI工具:编写代码,对其进行调试和分析。我们通常更喜欢在开发环境中设置服务器环境,并尝试使用熟悉的工具在本地重现问题。不幸的是,由于各种原因,通常不可能在本地重现一些问题。例如,你可能无权访问服务器应用程序处理的真实客户端数据。

在这种情况下,你需要在服务器盒上远程对应用程序进行故障排除。你应该记住,你无法使用裸露的JRE来正确地对应用程序进行故障排除:它包含所有故障排除功能,但是实际上无法访问它。结果,你需要在同一盒子上使用JDK或某些第三方工具。本文将介绍JDK工具,因为与许多组织中需要安全审核的任何第三方工具相比,你可能被允许在生产环境中使用它。

通常,仅需将JDK发行包解压缩到你的包装盒中就足够了——你不需要出于故障排除的目的而正确安装它(实际上,在很多情况下不希望正确安装)。 对于基于JMX的功能,你实际上可以安装任何Java 7/8 JDK,但是某些工具无法识别将来的JDK,因此我建议你安装最新的Java 7/8 JDK或与服务器JRE完全匹配的内部版本-它允许 你会为当前没有访问安全点的应用程序转储应用程序堆(某些处于空闲模式的应用程序是“无安全点”应用程序的简单示例)。


2. 故障排除方案

2.1. 获取正在运行的JVM的列表

为了开始工作,你几乎总是需要获取正在运行的JVM,它们的进程ID和命令行参数的列表。 有时可能就足够了:你可能会发现同一应用程序的第二个实例同时执行相同的工作(并损坏输出文件/重新打开套接字/执行其他一些愚蠢的操作)。

只需运行jcmd而无需任何参数。 它将向你显示正在运行的JVM的列表:

3824 org.jetbrains.idea.maven.server.RemoteMavenServer
2196
780 sun.tools.jcmd.JCmd

 

现在,你可以通过运行jcmd help命令来查看哪些诊断命令可用于给定的JVM 这是VisualVM的示例输出:

 

>jcmd 3036 help
 
3036:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help

 

键入jcmd 来运行诊断命令或得到一条错误消息,询问命令参数:

 

>jcmd 3036 GC.heap_dump
 
3036:
java.lang.IllegalArgumentException: The argument 'filename' is mandatory.

 

你可以使用以下命令获取有关诊断命令参数的更多信息:jcmd 帮助 例如,这是GC.heap_dump命令的输出:

 

>jcmd 3036 help GC.heap_dump
        
3036:
GC.heap_dump
Generate a HPROF format dump of the Java heap.
 
Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified.
 
Permission: java.lang.management.ManagementPermission(monitor)
 
Syntax : GC.heap_dump [options] 
 
Arguments:
filename :  Name of the dump file (STRING, no default value)
 
Options: (options must be specified using the  or = syntax)
-all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)


2.2. 进行堆转储

jcmd为你提供了一个方便的界面,用于以HPROF格式进行堆转储。只需运行jcmd GC.heap_dump 。请注意,文件名是相对于正在运行的JVM当前目录而不是当前目录的,因此你可能需要指定完整路径。最好使用.hprof扩展名作为转储文件名。

线程转储完成后,你可以将文件复制到自己的盒子中,然后在VisualVM(它是JDK的一部分)中打开它,并使用其堆walker和查询语言功能,或将其加载到Java Mission ControlJOverflow插件中并对其进行分析各种内存问题。

注意1:当然,还有许多其他工具可以处理hprof文件:NetBeansEclipse Memory AnalyzerYourKit等。将.hprof文件下载到框中后,请使用你喜欢的工具。

注意2:你也可以使用jmap工具进行堆转储:jmap -dumplivefile = 。问题在于它被正式证明为不受支持。我们中的许多人都认为JDK中不受支持的内容将永远存在,但事实证明情况不再如此:JEP 240JEP 241


2.3. 分析类直方图

如果你正在寻找内存泄漏,通常只对堆中某些特定类型的活动对象感兴趣。 例如,你可能知道一次只能拥有一个特定类型的对象(应用程序中的某种主要工作类)。 在旧的一代中可能还存在一个或多个相同类的实例,到目前为止,这些实例尚未进行垃圾回收,但是不应从应用程序根目录访问它们。

要打印类直方图,请运行以下两个命令之一(两个命令均打印活动对象的数量):

jcmd  GC.class_histogram
jmap -histo:live 

以下是示例输出的前几行:

 num     #instances         #bytes  class name
----------------------------------------------
   1:          5923        5976952  [I
   2:         50034        4127704  [C
   3:         49465        1187160  java.lang.String
   4:           188        1069496  [J
   5:          3985        1067240  [Ljava.util.HashMap$Node;
   6:          8756         982872  java.lang.Class
   7:          2855         835792  [B
   8:         23570         754240  java.util.HashMap$Node
   9:         13964         671440  [Ljava.lang.Object;
  10:          9642         308544  java.util.Hashtable$Entry
  11:          4453         213744  java.util.HashMap

 

请注意,以字节为单位的已占用大小是一个较浅的大小–它不包含任何子对象。 很容易从char [](类名= [C]String stats)中注意到这一事实–尽管实例数是相似的(尽管char []-s总是比String多,但是char []-的大小 s明显更大,如果String的大小包含基础char []的大小,则情况并非如此。

现在,你可以grep /搜索你感兴趣的类名称,并检查活动实例的数量。 如果看到的实例超出预期,请进行堆转储并在任何堆遍历器中对其进行分析(请参见上文)。

2.4. 进行线程转储

有时,你的应用程序可能会报告为“not doing anything/got stuck”。有很多种“stuck”的情况——死锁,高资源争用或仅是O(N10)算法来处理数百万用户的请求,在所有这些情况下,你应该知道你的应用程序线程正在执行什么以及锁将执行什么操作他们持有。

有两种类型的锁:基于同步关键字和Object.wait / notifyAll方法的原始锁,以及Java 5中引入的java.util.concurrent锁。它们之间的主要区别是前者绑定到你输入的堆栈框架上同步部分,并且在线程转储中始终可用。另一方面,后者(java.util.concurrent)不受堆栈框架限制——你可以使用一种方法输入锁,然后将其保留在另一种方法中。结果,一段时间以来,它们根本没有在线程转储中打印,即使现在它们仍然是一个选项。但是,你需要在线程转储中同时使用两种锁,以正确调查线程问题。

3种打印应用程序线程转储的方法。你可以在Linux上运行kill -3 或者,你可以在任何平台上运行以下命令之一:

jstack 
jcmd  Thread.print

2.5. 运行Java Flight Recorder

到目前为止,本文中提到的所有工具仅应用于快速调查。 为了进行更深入的分析,我建议使用内置的Java Flight Recorder

运行JFR是一个三步过程:

  1. 你需要创建一个包含所需设置的JFR模板文件。为此,请运行jmc并转到“窗口”->“飞行记录模板管理器”菜单。配置文件准备就绪后,将其导出到文件中,然后将其发送到你正在使用的框中。

  2. JFR需要JDK商业许可证。现在,你需要在所需的JVM上解锁商业功能:

jcmd  VM.unlock_commercial_features

  1. 之后,你可以启动JFR 这是命令行示例:

jcmd  JFR.start name=test duration=60s settings=template.jfc filename=output.jfr

 

此命令立即运行JFR(未设置delay属性),并使用template.jfc模板文件中的设置并将结果写入output.jfr(在两个文件中都使用绝对路径),收集JVM信息60秒钟。

录制完成后,你可以将.jfr文件复制到笔记本电脑并在jmcGUI中对其进行分析。它包含几乎所有你需要对JVM进行故障排除的信息,除了完整堆转储,你可以单独创建并复制到你的机器中。

 

感谢阅读!更深入探讨欢迎留言或私信。


抽丝剥茧 细说架构那些事——【优锐课】