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的列表:
1 3824 org.jetbrains.idea.maven.server.RemoteMavenServer 2 2196 3 780 sun.tools.jcmd.JCmd
现在,你可以通过运行jcmd <PID> help命令来查看哪些诊断命令可用于给定的JVM。 这是VisualVM的示例输出:
1 >jcmd 3036 help 2 3 3036: 4 The following commands are available: 5 JFR.stop 6 JFR.start 7 JFR.dump 8 JFR.check 9 VM.native_memory 10 VM.check_commercial_features 11 VM.unlock_commercial_features 12 ManagementAgent.stop 13 ManagementAgent.start_local 14 ManagementAgent.start 15 GC.rotate_log 16 Thread.print 17 GC.class_stats 18 GC.class_histogram 19 GC.heap_dump 20 GC.run_finalization 21 GC.run 22 VM.uptime 23 VM.flags 24 VM.system_properties 25 VM.command_line 26 VM.version 27 help
键入jcmd <PID> <COMMAND_NAME>来运行诊断命令或得到一条错误消息,询问命令参数:
1 >jcmd 3036 GC.heap_dump 2 3 3036: 4 java.lang.IllegalArgumentException: The argument 'filename' is mandatory.
你可以使用以下命令获取有关诊断命令参数的更多信息:jcmd <PID>帮助<COMMANDNAME>。 例如,这是GC.heap_dump命令的输出:
1 >jcmd 3036 help GC.heap_dump 2 3 3036: 4 GC.heap_dump 5 Generate a HPROF format dump of the Java heap. 6 7 Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified. 8 9 Permission: java.lang.management.ManagementPermission(monitor) 10 11 Syntax : GC.heap_dump [options] <filename> 12 13 Arguments: 14 filename : Name of the dump file (STRING, no default value) 15 16 Options: (options must be specified using the <key> or <key>=<value> syntax) 17 -all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)
2.2. 进行堆转储
jcmd
为你提供了一个方便的界面,用于以
HPROF
格式进行堆转储。只需运行
jcmd <PID> GC.heap_dump <FILENAME>
。请注意,文件名是相对于正在运行的
JVM
当前目录而不是当前目录的,因此你可能需要指定完整路径。最好使用
.hprof
扩展名作为转储文件名。
线程转储完成后,你可以将文件复制到自己的盒子中,然后在
VisualVM
(它是
JDK
的一部分)中打开它,并使用其堆
walker
和查询语言功能,或将其加载到
Java Mission Control
的
JOverflow
插件中并对其进行分析各种内存问题。
注意1:当然,还有许多其他工具可以处理hprof文件:NetBeans,Eclipse Memory Analyzer,YourKit等。将.hprof文件下载到框中后,请使用你喜欢的工具。
注意2:你也可以使用jmap工具进行堆转储:jmap -dump:live,file = <FILE_NAME> <PID>。问题在于它被正式证明为不受支持。我们中的许多人都认为JDK中不受支持的内容将永远存在,但事实证明情况不再如此:JEP 240,JEP 241
2.3. 分析类直方图
如果你正在寻找内存泄漏,通常只对堆中某些特定类型的活动对象感兴趣。 例如,你可能知道一次只能拥有一个特定类型的对象(应用程序中的某种主要工作类)。 在旧的一代中可能还存在一个或多个相同类的实例,到目前为止,这些实例尚未进行垃圾回收,但是不应从应用程序根目录访问它们。
要打印类直方图,请运行以下两个命令之一(两个命令均打印活动对象的数量):
1 jcmd <PID> GC.class_histogram 2 jmap -histo:live <PID>
以下是示例输出的前几行:
1 num #instances #bytes class name 2 ---------------------------------------------- 3 1: 5923 5976952 [I 4 2: 50034 4127704 [C 5 3: 49465 1187160 java.lang.String 6 4: 188 1069496 [J 7 5: 3985 1067240 [Ljava.util.HashMap$Node; 8 6: 8756 982872 java.lang.Class 9 7: 2855 835792 [B 10 8: 23570 754240 java.util.HashMap$Node 11 9: 13964 671440 [Ljava.lang.Object; 12 10: 9642 308544 java.util.Hashtable$Entry 13 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(N
10
)
算法来处理数百万用户的请求,在所有这些情况下,你应该知道你的应用程序线程正在执行什么以及锁将执行什么操作他们持有。
有两种类型的锁:基于同步关键字和Object.wait / notifyAll方法的原始锁,以及Java 5中引入的java.util.concurrent锁。它们之间的主要区别是前者绑定到你输入的堆栈框架上同步部分,并且在线程转储中始终可用。另一方面,后者(java.util.concurrent)不受堆栈框架限制——你可以使用一种方法输入锁,然后将其保留在另一种方法中。结果,一段时间以来,它们根本没有在线程转储中打印,即使现在它们仍然是一个选项。但是,你需要在线程转储中同时使用两种锁,以正确调查线程问题。
有3种打印应用程序线程转储的方法。你可以在Linux上运行kill -3 <PID>。 或者,你可以在任何平台上运行以下命令之一:
1 jstack <PID> 2 jcmd <PID> Thread.print
2.5. 运行Java Flight Recorder
到目前为止,本文中提到的所有工具仅应用于快速调查。 为了进行更深入的分析,我建议使用内置的Java Flight Recorder。
运行JFR是一个三步过程:
- 你需要创建一个包含所需设置的JFR模板文件。为此,请运行jmc并转到“窗口”->“飞行记录模板管理器”菜单。配置文件准备就绪后,将其导出到文件中,然后将其发送到你正在使用的框中。
- JFR需要JDK商业许可证。现在,你需要在所需的JVM上解锁商业功能:
1 jcmd <PID> VM.unlock_commercial_features
- 之后,你可以启动JFR。 这是命令行示例:
1 jcmd <PID> JFR.start name=test duration=60s settings=template.jfc filename=output.jfr
此命令立即运行JFR(未设置delay属性),并使用template.jfc模板文件中的设置并将结果写入output.jfr(在两个文件中都使用绝对路径),收集JVM信息60秒钟。
录制完成后,你可以将.jfr文件复制到笔记本电脑并在jmcGUI中对其进行分析。 它包含几乎所有你需要对JVM进行故障排除的信息,除了完整堆转储,你可以单独创建并复制到你的机器中。
感谢阅读!最后奉上近期整理出来的一套完整的java架构思维导图,分享给大家对照知识点参考学习。