一个java应用经常会出现许多性能上面的问题。大多的的性能问题是由不同的jvm调优引起的。JDK/JRE已经提供一些工具。作为一名开发者,最好需要有相关jvm高优的相关知识,这样可以帮助我们更好的创建高性能应用。显然,知识所有工具的知识是最好的,但是有一个工具是由java它自己提供的(java7开始提供),它可以获得java应用的重要信息。它就是jcmd,使用它可以获取许多信息。
-
什么是jcmd ?
在使用手册上面介绍,jcmd是向运行的JVM发诊断命令的一个工具。 这个工具可以获取JVM运行时的许多信息。获取这些信息是基于JVM是处于运行的状态。 -
如何使用 jcmd
要介绍如何使用这个工具,我们首先要JVM上面运行起一个java应用。
启动一个WEB应用
创建一个简单的web应用,里面有一个controller 返回一个字符串。
Fig1. Sample Request Controller
启动应用的时候带上以下 JVM 参数:
-Xss256k -Xms512m -Xmx512m -XX:MaxMetaspaceSize=256m
获取pid
每个进程都一个ID我们就叫作 pid. 获取我们应用关联的PID,我们可以使用 jcmd 它可以列出所有的java进程。
Fig2. jcmd to get pid
在例子中我们进程ID是2171.
获取jcmd相关的使用方法
Fig3. jcmd to get available command options
这里,我们可以看到jcmd命令的一些有效的操作。
jcmd 命令
接下来,我们可以使用有效的选项获取我们运行的JVM相关信息。 -
VM.version
这个参数是用来获取JVM基础详细信息。
Fig4. jcmd command - VM.version -
VM.flags
VM.flags是用来打印我们应用所有的VM参数,这些参数有可能是我们设置或者是jvm默认的。许多默认VM参数可以被查看。
Fig5. jcmd command - VM.flags
其他的命令参数,如:VM.system_properties, VM.command_line, VM.uptime, VM.dynlibs 也可以用来获取其他的基础信息并且获取其他属性的使用信息。 -
Thread.print
Thread.print 是用来获取线程的堆栈等信息。它会打印出所有正在运行线程的堆栈信息。使用这个方式,我们可以获取一定数量运行线程输出的信息。
Fig6. jcmd command - Thread. print -
GC.class_histogram
这个参数可以获取到重要的信息,堆的使用情况,列出所有类的实例数量,它们使用堆内存的排序。当这个列表非常长的时候,我们可以使用 grep 命令来过滤确定类的信息,或者把这个命令输出的信息写入到一个文件中。
Fig7. jcmd command - GC.class_histogram -
- Grep使用方式
Fig8. jcmd command with grep usage
- Grep使用方式
-
GC.heap_dump
如果想要立即获取jvm堆,这个命令可以满足。这个命令的使用方式,jvmTuningHeapDum 是一个文件名。注意,这里的文件最好使用全路径的文件名。否则,有可能会随机一个位置创造文件,但是我们使用“ find -name jvmTuningHeapDum ”在系统内进行搜索或者使用其他工具。
Fig9. jcmd command - GC.heap_dump
JFR command options JFR 命令选项
想要分析当前应用运行情况可以使用JFR( Java Flight Recorder),这是一个强大的工具,虽然JFR是一个商业版的,但是它在我们本地机器使用是免费的。
jcmd 命令有提供相关快速分析JFR文件。默认情况下,JFR功能是关闭的,要启用它可以使用下面的方式。
Fig10. jcmd command -JFR.start
jcmd 关闭JFR付费功能
Fig11. jcmd command to unlock commercial feautres
在启用JFR功能之后 ,开始JFR的记录。例子中,在延迟10秒后记录30秒。这些我们在使用过程可以进行配置。我们可以使用JFR.check 命令来检测记录的情况。
Fig12. jcmd command - JFR.start sample usage
JFR的记录文件可以使用其他的工具如JMC 进行分析。
VM.native_memory (Native Memory Tracking)
这个命令是最好的一个,它可以获取堆或者非堆内容的详细使用信息。经常用来优化内存使用和监测内存泄漏。我们知道,JVM内存使用依赖许多的内存区域,宽泛的分为堆和非堆内存。这个工具可以所有JVM内存的使用情况。这有助于我们定义应用容器的大小。如果创建的是分布式的应用,优化正确是可以节省开销的。
使用这个属性我们重启应用添加另外的参数:XX:NativeMemoryTracking=summary 或者-XX:NativeMemoryTracking=detail. 检查新的PID和新添加的VM参数。
Fig13. jcmd command to get new pid
jcmd 获取新的JVM参数
Fig14. jcmd command to get updated VM flags
运行VM.native_memory 可以获取如下详细信息。
Fig15. jcmd command - VM.native_memory
Java 堆 内存的组成部分,Thread指定使用的内存;Class 指定的内存是用来存储类的元数据;Code 获取的内存是用来存储JIT 生成的代码;Compiler 它本身有使用的内存空间,和 GC复本内存空间类似。这些都是实际内存的使用情况。
经过上面所有的内存的总和我们可以评估出我们应用所需要的内存大小。但是还有许多的VM参数可以控制这些内存,大部分情况下我们使用的是默认的参数,所有的内存优化都是需要分析的。
参照, 所有的 VM 参数在 这里.
ps.committed 显示的是真实使用内存的情况和reserved 显示的是JVM随着时间失衡期望使用的内存大小。
Plain Text
1
Java Heap (reserved=524288KB, committed=524288KB)
2
(mmap: reserved=524288KB, committed=524288KB)
我们指定堆内存 Xms = 512m 等于524288KB,因为提交的内存大小和 Xms一样。类似的,Xmx映射的就是保存的内存空间大小。这个命令获取当前内存使用快照信息。用来内存内存泄漏,应当在应用启动后再使用这个命令baseline 。
Fig16. jcmd command - VM.native_memory baseline
diff 经常用来观察变化,准确的分析内存被使用情况。要注意GC超时时间的增加和减少。如果只存在增加内存使用的情况,有可能是内存泄漏的原因。分析的内存泄漏的区域如 heap,thread,code,class等等。如果是应用需要更多的内存,就优化调整VM参数来实现。
内存泄漏是在heap,获取heap的dump文件进行分析,或者使用线程池,线程的数量不断增加。任何线程增加都会引起OOM,Xss需要被优化。
下面的快照信息中,可以发现内存的增加和其它地方的不一样。
Fig17. jcmd command - VM.native_memory diff
Note:
Plain Text
1 Thread (reserved=11895KB +529KB, committed=11895KB +529KB)
2 (thread #20 +2)
3 (stack: reserved=11812KB +520KB, committed=11812KB +520KB)
4 (malloc=61KB +7KB #101 +10)
5 (arena=22KB +2 #35 +4)
‘stack’ 是显示线程的 栈内存。并且它可能是使用不匹配的 Xss 内存空间启动应用引起的。 在一些jvm系统中所有的线程,每个线程分配的栈空间并且不能重写 Xss。
我们指定线程的栈的大小如Xss=256k, 大约有18+2 = 20 个线程。这里另外的2个线程是应用指定用来处理请求的。分配2*(Xss=256k ) ~ 520k 的栈空间。