JVM核心参数配置常用调试命令工具与调优思路

Posted xpleaf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM核心参数配置常用调试命令工具与调优思路相关的知识,希望对你有一定的参考价值。

JVM核心参数配置、常用调试命令工具与调优思路

  • 所有JVM相关参数,比较好看的表格分类,参考:

https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

虽然是JDK 7的,但是也有较大的参考价值。

  • JDK官方技术手册 ,参考:

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html

这个就相当好了,第5章的java小节就有所有jvm相关参数。

1 JVM参数

1.1 日志参数

日志参数 作用
-XX:+PrintGC 查看GC基本信息
-XX:+PrintGCDetails 查看GC详细信息
-XX:+PrintGCDateStamps<br>-XX:+PrintGCTimeStamps 查看GC时间,单独使用时没有效果,可以与查看GC信息的参数结合使用
-XX:+PrintHeapAtGC 查看GC前后堆、方法区可用容量变化
-XX:+PrintGCApplicationConcurrentTime<br>-XX:+PrintGCApplicationStoppedTime 查看GC过程中用户线程并发时间以及停顿的时间
-XX:+PrintTenuringDistribution 查看熬过收集后剩余对象的年龄分布信息
-XX:+UseGCLogFileRotation 打开或关闭GC日志滚动记录功能,要求必须设置 -Xloggc参数
-XX:NumberOfGCLogFiles=5 设置滚动日志文件的个数,必须大于1,日志文件命名策略是,<filename>.0, <filename>.1, ..., <filename>.n-1,其中n是该参数的值
-XX:GCLogFileSize=20M 设置滚动日志文件的大小,必须大于8k,当前写日志文件大小超过该参数值时,日志将写入下一个文件
-Xloggc:/home/GCEASY/gc.log gc日志路径

可以用一个简单的demo验证下:

public class TmpTest 

    public static void main(String[] args) 
        byte[] obj = new byte[1024 * 1024];
        obj = null;
        System.gc();
    

1.1.1 -XX:+PrintGC

[GC (System.gc())  4352K->536K(125952K), 0.0007552 secs]
[Full GC (System.gc())  536K->395K(125952K), 0.0036377 secs]

1.1.2 -XX:+PrintGCDetails

包含的信息有:发生了GC的区域、该区域GC前后内存变化(清除了多少空间、增加了多少空间、该区域当前内存容量)、总的堆内存变化、Metaspace内存变化以及最后面的各区域内存情况。

[GC (System.gc()) [PSYoungGen: 4352K->528K(38400K)] 4352K->536K(125952K), 0.0016053 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 528K->0K(38400K)] [ParOldGen: 8K->392K(87552K)] 536K->392K(125952K), [Metaspace: 3237K->3237K(1056768K)], 0.0045798 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b20,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 392K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740062248,0x0000000745580000)
 Metaspace       used 3256K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 357K, capacity 388K, committed 512K, reserved 1048576K

1.1.3 -XX:+PrintGCDateStamps

与-XX:+PrintGCDetails结合使用的效果:

2020-01-13T01:10:45.422-0800: [GC (System.gc()) [PSYoungGen: 4352K->480K(38400K)] 4352K->488K(125952K), 0.0018664 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
2020-01-13T01:10:45.424-0800: [Full GC (System.gc()) [PSYoungGen: 480K->0K(38400K)] [ParOldGen: 8K->392K(87552K)] 488K->392K(125952K), [Metaspace: 3224K->3224K(1056768K)], 0.0071912 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b20,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 392K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740062110,0x0000000745580000)
 Metaspace       used 3256K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 357K, capacity 388K, committed 512K, reserved 1048576K

1.1.4 -XX:+PrintHeapAtGC

Heap before GC invocations=1 (full 0):
 PSYoungGen      total 38400K, used 3686K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 11% used [0x0000000795580000,0x0000000795919a38,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
  to   space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
 ParOldGen       total 87552K, used 0K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740000000,0x0000000745580000)
 Metaspace       used 3284K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 38400K, used 448K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 8% used [0x0000000797600000,0x0000000797670020,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
 Metaspace       used 3284K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

Heap before GC invocations=2 (full 1):
 PSYoungGen      total 38400K, used 448K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 8% used [0x0000000797600000,0x0000000797670020,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
 Metaspace       used 3284K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K
Heap after GC invocations=2 (full 1):
 PSYoungGen      total 38400K, used 0K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 395K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740062f80,0x0000000745580000)
 Metaspace       used 3284K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

1.1.5 -XX:+PrintGCApplicationConcurrentTime

Application time: 0.0031662 seconds
Total time for which application threads were stopped: 0.0043566 seconds, Stopping threads took: 0.0000299 seconds
Application time: 0.0016923 seconds

1.1.6 -XX:+PrintTenuringDistribution

Desired survivor size 5242880 bytes, new threshold 7 (max 15)

1.1.7 -Xloggc:filename

比如使用下面的gc参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/Users/yeyonghao/gc.log

那么在我的系统上会有下面的日志:

$ cat gc.log
Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for bsd-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 01:02:31 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(141640k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
2020-01-13T01:18:33.490-0800: 0.163: [GC (System.gc()) [PSYoungGen: 4352K->416K(38400K)] 4352K->416K(125952K), 0.0028668 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2020-01-13T01:18:33.493-0800: 0.166: [Full GC (System.gc()) [PSYoungGen: 416K->0K(38400K)] [ParOldGen: 0K->388K(87552K)] 416K->388K(125952K), [Metaspace: 3178K->3178K(1056768K)], 0.0043444 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
 PSYoungGen      total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b28,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 388K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740061000,0x0000000745580000)
 Metaspace       used 3192K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 388K, committed 512K, reserved 1048576K

1.1.8 生产环境推荐

可以给GC日志的文件后缀加上时间戳,当JVM重启以后,会生成新的日志文件,新的日志也不会覆盖老的日志,只需要在日志文件名中添加%t的后缀即可:

-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-Xloggc:/home/GCEASY/gc-%t.log

%t会给文件名添加时间戳后缀,格式是YYYY-MM-DD_HH-MM-SS。这样就非常简单了克服了UseGCLogFileRotation存在的所有的问题!

在我的系统上尝试,会生成下面的gc文件:

gc-2020-01-13_01-23-23.log

注意它表示的是当前系统时区的本地时间,不是UTC时间。

1.2 HeapDump参数

参数 作用
-XX:+HeapDumpOnOutOfMemoryError 发生java.lang.OutOfMemoryError时,将堆转储到文件中。
-XX:HeapDumpPath=./java_pid<pid>.hprof | 保存堆转储文件的路径。默认会在当前程序运行路径下生成dump文件,如:java_pid27023.hprof`
-XX:OnOutOfMemoryError="&lt;cmd args&gt;;&lt;br/&gt;&lt;cmd args&gt;" 抛出OutOfMemoryError时,运行用户定义的命令(比如可以删除之前的dump文件,避免占用较多磁盘空间,或者是其它策略)
-XX:ErrorFile=./hs_err_pid&lt;pid&gt;.log 如果发生错误,将错误数据保存到文件中。
-XX:OnError="&lt;cmd args&gt;;&lt;cmd args&gt;" 发生错误时,运行用户定义的命令。
-XX:+CrashOnOutOfMemoryError 会在当前运行目录生成hs_err_pid&lt;pid&gt;.log日志文件

使用下面的示例程序:

public class HeapOOM 

    static class OOMObject 

    

    public static void main(String[] args) 
        ArrayList<OOMObject> list = new ArrayList<>();
        while (true) 
            list.add(new OOMObject());
        
    

配置下面的jvm参数:

-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/log/MyApp_dump.hprof
-XX:OnOutOfMemoryError="mv /tmp/log/MyApp_dump.hprof /tmp/log/MyApp_dump_%p.hprof"

执行程序后,会有下面的提示:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to /tmp/log/MyApp_dump.hprof ...
Heap dump file created [27953782 bytes in 0.119 secs]
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="mv /tmp/log/MyApp_dump.hprof /tmp/log/MyApp_dump_%p.hprof"
#   Executing "mv /tmp/log/MyApp_dump.hprof /tmp/log/MyApp_dump_27587.hprof"...

这时可以看到在/tmp/log目录下有新生成的dump文件:

$ ls
MyApp_dump_27587.hprof

如果再发生溢出时,还会生成新的dump文件:

$ ls
MyApp_dump_27587.hprof MyApp_dump_27611.hprof

1.3 内存与收集器参数

1.3.1 收集器总览

其组合如下:

1.3.2 内存参数

关于Java虚拟机的内存分布情况,可以参考:

参数 作用
-Xms256m 设置初始堆内存大小
-Xmx512m 设置最大堆内存大小<br>需要注意的是,即使当前堆内存大小还没有达到最大堆内存大小,这部分内存也会被JVM虚拟机锁定
-Xss512k 设置java虚拟机栈大小
-XX:MaxMetaspaceSize=Nm 设置元空间最大值,默认是一个很大的值,即表示不限制,或者说只受限于本地内存大小
-XX:MetaspaceSize=Nm 指定元空间的初始大小,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值
-XX:MinMetaspaceFreeRatio 作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%(这个没验证过)。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存
-XX:MaxMetaspaceFreeRatio 用于控制最大的元空间剩余容量的百分比。当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%(这个没验证过)
-XX:ReservedCodeCacheSize=64m Java代码在执行时一旦被编译器编译为机器码,下一次执行的时候就会直接执行编译后的代码,也就是说,编译后的代码被缓存了起来。缓存编译后的机器码的内存区域就是codeCache。这是一块独立于Java堆之外的内存区域。除了JIT编译的代码之外,Java所使用的本地方法代码(JNI)也会存在codeCache中<br/>可参考:https://juejin.im/post/5aebf997f265da0ba76f99db

1.3.3 收集器参数

参数 作用 特定收集器
-XX:SurvivorRatio=n 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor=8:1 /
–XX:NewRatio=n 老年代与新生代的容量比值,默认为2,代表Old:Young=2:1 /
-XX:PretenureSizeThreshold=n 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。n为字节数,如3145728,即3MB 只对Serial和ParNew两款新生代收集器有效
-XX:MaxTenuringThreshold=n 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值时就进入老年代 /
-XX:+HandlePromotionFailure 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况。详细参考Note1的说明。 /
-XX:ParallelGCThreads=n 设置并行GC时进行内存回收的线程数,不同的JVM平台其默认值不同 /
-XX:GCTimeRatio=n GC时间占总时间的比率,默认值为99,即允许1%的GC时间。仅在使用Parallel Scavenge收集器时生效 Parallel Scavenge
-XX:+DisableExplicitGC 不允许通过调用System.gc()来触发GC /
下面参数是特定收集器
-XX:UseSerialGC 虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 Serial<br>Serial Old
-XX:UseParNewGC 打开此开关后,使用ParNew + Serial Old的收集器组合进行内存回收,在JDK 9后不再支持 ParNew<br>Serial Old
-XX:UseConcMarkSweepGC 打开此开关后,使用ParNew + CMS + Serial Old的收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现“Concurrent Mode Failure”失败后的后备收集器使用 ParNew<br>CMS<br>Serial Old
-XX:UseParallelGC JDK9之前虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合 Parallel Scavenge<br>Serial Old
-XX:UseParallelOldGC 打开此开关后,使用Parallel Scavenge + Parallel Old的收集器 Parallel Scavenge<br>Parallel Old
-XX:MaxGCPauseMillis 设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效 Parallel Scavenge
-XX:CMSInitiatingOccupancyFraction=70 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效 CMS
-XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整 CMS
-XX:+UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理,从JDK9开始废除该参数 CMS
-XX:CMSFullGCsBeforeCompaction=n 设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理,从JDK9开始废除该参数 CMS

1.4 默认参数与运行参数查看

1.4.1 默认参数查看

可以通过下面命令来获取当前使用的jdk的所有默认参数:

yeyonghao@yeyonghaodeMacBook-Pro:/tmp/log$ java -XX:+PrintFlagsInitial > defaultFlags.log
yeyonghao@yeyonghaodeMacBook-Pro:/tmp/log$ wc -l defaultFlags.log
     726 defaultFlags.log

可以看到在我使用的jdk环境有700多个参数。

比如想查看MetaspaceSize的默认大小是多少:

$ grep MetaspaceSize defaultFlags.log
    uintx InitialBootClassLoaderMetaspaceSize       = 4194304                             product
    uintx MaxMetaspaceSize                          = 18446744073709551615                    product
    uintx MetaspaceSize                             = 21810376                            pd product

可以看到MetaspaceSize的默认大小为20M:

>>> 21810376 / 1024 / 1024
20

比如再看一下CompressedClassSpaceSize

$ grep CompressedClassSpaceSize defaultFlags.log
    uintx CompressedClassSpaceSize                  = 1073741824                          product

它的默认大小为1G:

>>> 1073741824 / 1024 / 1024
1024

1.4.2 运行参数查看

使用jps -v可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了。

比如查看CompressedClassSpaceSize

$ jinfo -flag CompressedClassSpaceSize 16900
-XX:CompressedClassSpaceSize=1073741824

它的大小为1G:

>>> 1073741824 / 1024 / 1024
1024

相当于是使用了默认值。

比如查看MaxMetaspaceSize

$ jinfo -flag MaxMetaspaceSize 16900
-XX:MaxMetaspaceSize=18446744073709547520

它的默认最大空间为不限制,即使用了默认值,能分配多少空间取决于操作系统内存大小。

2 监控与调试工具

2.1 jps

jps [ options ] [ hostid ]

各参数说明如下:

选项 作用
-q 只输出LVMID,省略主类的名称
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类的全名,如果进程执行的是JAR包,则输出JAR路径
-v 输出虚拟机进程启动时的JVM参数

一般使用得比较多的是jps -lvm

2.2 jstat

jstat [ option vmid [interval[s|ms] [count]] ]

参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令是:

jstat -gc 2764 250 20

选项option代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状况。各参数说明如下:

选项 作用
-class 监视类加载、卸载数量、总空间以及类装载所耗费的时间
-gc 监视Java堆状况,包括Eden区、2个Survivor区、老年代、Metaspace等的容量,已用空间,垃圾收集时间合计等信息
-gccapacity 监视内容与-gc基本相同,但输出主要关注Java堆各个区域用到的最大、最小空间
-gcutil 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与-gcutil功能一样,但是会额外输出导致上一次垃圾收集产生的原因
-gcnew 监视新生代垃圾收集状况
-gcnewcapacity 监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间
-gcold 监视老年代垃圾收集状况
-gcoldcapacity 监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
-gcmetacapacity 输出方法区使用到的最大、最小空间
-compiler 输出即时编译器编译过的方法、耗时等信息
-printcompilation 输出已经被即时编译的方法

下面会详细介绍上面这些参数中部分个人目前用得比较多的,当然如果后面发现其它参数也很好用的话也会补充更新。

2.3.1 -class

监视类加载、卸载数量、总空间以及类装载所耗费的时间。

$ jstat -class 550
Loaded  Bytes  Unloaded  Bytes     Time
 47184 99829.0       13    17.6      46.01

2.3.2 -gc

监视Java堆状况,包括Eden区、2个Survivor区、老年代、Metaspace等的容量,已用空间,垃圾收集时间合计等信息。

$ jstat -gc 550
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
4352.0 4352.0  0.0   2853.3 34944.0    0.0     235236.0   205519.5  289032.0 277047.8 41412.0 36925.1     53    0.501   0      0.000    0.501

空间容量信息:

  • SOC:Survivor0区域空间容量(kB);
  • S1C:Survivor1区域空间容量(kB);
  • S0U:Survivor0区域空间使用量(kB);
  • S1U:Survivor1区域空间使用量(kB);
  • EC:Eden区域空间容量(kB);
  • EU:Eden区域空间使用量(kB);
  • OC:Old区域空间容量(kB);
  • OU:Old区域空间使用量(kB);
  • MC:Metaspace区域空间容量(kB);
  • MU:Metaspace区域空间使用量(kB);
  • CCSC:CompressedClass区域空间使容量(kB);
  • CCSU:CompressedClass区域空间使用量(kB);

垃圾收集信息:

  • YGC:Young GC事件次数;
  • YGCT:Young GC事件所用的总时间(s);
  • FGC:Full GC事件次数;
  • FGCT:Full GC事件所用的总时间(s);
  • GCT:GC事件所用的总时间(s),即YGCT + FGCT;

2.3.3 -gcutil

监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。

$ jstat -gcutil 692
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  84.73  84.61  62.84  95.91  89.39     49    0.408     0    0.000    0.408

使用表格来更好地展示与说明。

空间容量信息:

  • S0:Survivor0区域空间使用百分比;
  • S1:Survivor1区域空间使用百分比;
  • E:Eden区域空间使用百分比;
  • O:Old区域空间使用百分比;
  • M:Metaspace区域空间使用百分比;
  • CCS:CompressedClass区域空间使用百分比;

垃圾收集信息:

  • YGC:Young GC事件次数;
  • YGCT:Young GC事件所用的总时间(s);
  • FGC:Full GC事件次数;
  • FGCT:Full GC事件所用的总时间(s):
  • GCT:GC事件所用的总时间(s),即YGCT + FGCT;

2.3.4 -gccause

与-gcutil功能一样,但是会额外输出导致上一次垃圾收集产生的原因。

$ jstat -gccause 692
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
 32.10   0.00  88.35  63.60  95.90  89.42     52    0.434     0    0.000    0.434 Allocation Failure   No GC

主要对下面两个参数进行说明:

  • LGCC:触发上一次GC的原因(Cause of last garbage collection);
  • GCC:触发当前GC的原因(Cause of current garbage collection);

如果系统是通过调用System.gc()来触发GC的,那么也可以在这里看到原因。

2.3.5 -gcnew

监视新生代垃圾收集状况。

$ jstat -gcnew 962
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT  
4352.0 4352.0    0.0 4352.0  1   6 2176.0  34944.0  18666.0     53    0.502

各参数说明如下:

  • S0C:Survivor0区域空间容量(kB);

  • S1C:Survivor1区域空间容量(kB);

  • S0U:Survivor0区域空间使用量(kB);

  • S1U:Survivor1区域空间使用量(kB);

  • TT:Tenuring threshold,Survivor空间中,对象的age达到该阈值的大小就会直接晋升到老年代,其为动态计算;

  • MTT:Maximum tenuring threshold,Survivor空间中,TT的最大阈值大小;

  • DSS:Desired survivor size (kB);

    • ```c++
      // TargetSurvivorRatio默认50,意思是:在回收之后希望survivor区的占用率达到这个比例
      size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100)
      // 默认在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,年龄大于或等于该
      // 年龄的对象就可以直接进入老年代。这里为什么是一半?就是因为TargetSurvivorRatio设置为
      // 50,这个计算出来的空间就是Survivor的一半,可以参考后面提供的文章对JVM源码的分析。
  • EC:Eden区域空间容量 (kB);

  • EU:Eden区域空间使用量 (kB);

  • YGC:Young GC事件次数;

  • YGCT:Young GC事件所用的总时间(s);

2.3.6 -gcold

监视老年代垃圾收集状况。

$ jstat -gcold 962
   MC       MU      CCSC     CCSU       OC          OU       YGC    FGC    FGCT     GCT   
287592.0 275461.4  41104.0  36682.1    261292.0    186662.1     60     0    0.000    0.553

各参数说明如下:

  • MC: Metaspace区域空间容量(kB);
  • MU: Metaspace区域空间使用量(kB);
  • CCSC: CompressedClass区域空间使容量(kB);
  • CCSU: CompressedClass区域空间使用量(kB);
  • OC: Old区域空间容量(kB);
  • OU: Old区域空间使用量(kB);
  • YGC: Young GC事件次数;
  • FGC: Full GC事件次数;
  • FGCT: Full GC事件所用的总时间(s);
  • GCT: GC事件所用的总时间(s),即YGCT + FGCT;

2.3.7 -compiler

输出即时编译器编译过的方法、耗时等信息。

$ jstat -compiler 962
Compiled Failed Invalid   Time   FailedType FailedMethod
   22536      4       0    78.34          1 com/intellij/codeInspection/ex/InspectionProfileImpl addTool

各参数说明如下:

  • Compiled: 编译次数;
  • Failed: 失败的编译次数;
  • Invalid: 无效的编译次数;
  • Time: 编译所消耗的总时间;
  • FailedType: 最后一次编译失败的编译类型;
  • FailedMethod: 最后一次编译失败的类名和方法;

2.3.8 -printcompilation

输出已经被即时编译的方法。

$ jstat -printcompilation 962
Compiled  Size  Type Method
   22575     23    1 java/io/ObjectStreamClass$EntryFuture set

各参数说明如下:

  • Compiled: 最近编译的方法执行的编译次数(描述虽然跟-compiler的不同,但实际上看数值它们其实是一样的);

  • Size: 最近编译的方法的字节码的字节数;

  • Type: 最近编译的方法的编译类型;

  • Method: 类名和方法名标识最近编译的方法,其中类名使用/而不是.来分隔,方法名就是类中的方法名,这两项内容的输出格式跟虚拟机参数-XX:+PrintCompilation是一致的;

2.3 jinfo

Java配置信息工具。

jinfo [ option ] pid

主要介绍-flag参数,如果想查看一个已经运行的java程序的虚拟机参数值:

$ jinfo -flag MaxMetaspaceSize 962
-XX:MaxMetaspaceSize=18446744073709547520
$ jinfo -flag MaxTenuringThreshold 962
-XX:MaxTenuringThreshold=6

2.4 jmap

Java内存映像工具。

jmap [ option ] vmid

各参数说明如下:

选项 作用
-dump 生成Java堆转储快照。格式为-dump:[live,],format=b,file=&lt;filename&gt;,其中live子参数说明是否只dump出存活的对象
-heap 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台下有效
-histo 显示堆中对象统计信息,包括类、实例数量、合计容量
-F 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台下有效

因为整理该文档时使用的是Mac OS,所以只说明-dump和-histo的使用。

2.4.1 -dump

生成Java堆转储快照。

这个最常用,可以将当前java程序的堆内存dump下来,然后再做分析。

$ jmap -dump:format=b,file=idea_heap.hrpof 962
Dumping heap to /private/tmp/idea_heap.hrpof ...
Heap dump file created
$ ls -lh idea_heap.hrpof 
-rw-------  1 yyh  wheel   321M  2  5 22:56 idea_heap.hrpof

可以看到也是有一定空间大小的,取决于当前java程序所占用的堆内存空间大小,堆内存dump下来后就可以使用MAT打开进行相应的分析了。

2.4.2 -histo

显示堆中对象统计信息,包括类、实例数量、合计容量。

$ jmap -histo 962 | more
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:        826328       98417200  [B (java.base@11.0.5)
   2:        531346       12752304  java.lang.String (java.base@11.0.5)
   3:         36118       12640928  [I (java.base@11.0.5)
   4:        162897       10665248  [Ljava.lang.Object; (java.base@11.0.5)
$ jmap -histo 962 > instances.txt
$ wc -l instances.txt 
   26304 instances.txt

内容也是比较多的,所以可以保存到文件中,然后再进行相应分析。

2.5 jstack

Java堆栈跟踪工具。

jstack [ option ] vimd

用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。

各参数说明如下:

选项 作用
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息(如果有死锁,会分析出来,非常有用)
-m 如果调用到本地方法的话,可以显示C/C++的堆栈
$ jstack -l 962 | more
2020-02-05 23:18:07
Full thread dump OpenJDK 64-Bit Server VM (11.0.5+10-b520.30 mixed mode):

Threads class SMR info:
_java_thread_list=0x0000600002fdb0e0, length=42, elements=
0x00007f8b55009800, 0x00007f8b5500a800, 0x00007f8b53822800, 0x00007f8b53806000,
0x00007f8b53826000, 0x00007f8b53827000, 0x00007f8b5501f800, 0x00007f8b54009800,
0x00007f8b54003800, 0x00007f8b551c2800, 0x00007f8b53966800, 0x00007f8b54279000,
0x00007f8b53996000, 0x00007f8b5530e000, 0x00007f8b53bbe000, 0x00007f8b5445d800,
0x00007f8b53a01800, 0x00007f8b53a22000, 0x00007f8b53ced800, 0x00007f8b5811c000,
0x00007f8b57bdb000, 0x00007f8b57341000, 0x00007f8b57460000, 0x00007f8b588ee800,
0x00007f8b588f4800, 0x00007f8b541fc800, 0x00007f8b541fd800, 0x00007f8b57826800,
0x00007f8b56dd0000, 0x00007f8b62825800, 0x00007f8b5e932000, 0x00007f8b59872800,
0x00007f8b4407f000, 0x00007f8b443f5000, 0x00007f8b43d76800, 0x00007f8b44d42000,
0x00007f8b62971000, 0x00007f8b610d9000, 0x00007f8b44d48000, 0x00007f8b5f8b8000,
0x00007f8b555ec000, 0x00007f8b4436c800


"Reference Handler" #2 daemon prio=10 os_prio=31 cpu=31.02ms elapsed=82877.18s tid=0x00007f8b55009800 nid=0x3803 waiting on condition  [0x00007000036e3000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.5/Native Method)
        at java.lang.ref.Reference.processPendingReferences(java.base@11.0.5/Reference.java:241)
        at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.5/Reference.java:213)

   Locked ownable synchronizers:
        - None
$ jstack -l 962 > idea_stack.txt
$ wc -l idea_stack.txt 
     635 idea_stack.txt

2.6 jconsole

Java监视与管理控制台。

在jdk的bin目录是有jconsole的,可以直接打开,在Unix系操作系统上,直接输入jsconsole也可以直接打开。这里主要看一下其功能有哪些,因为实际上对于可视化监控,是更推荐VisualVM,jconsole本身个人用得也不多。

2.6.1 概览

2.6.2 内存

2.6.3 线程

2.6.4 类

2.6.5 VM概要

2.6.6 MBean

2.7 VisualVM

VisualVM是更常用的可视化监控工具,它本身还具有插件扩展功能,因此功能非常强大,目前使用非常多。

需要先说明的是一些安装上的问题,虽然安装好jdk之后是会有一个VisualVM的程序在jdk的bin目录下,但是不建议使用jdk提供的这个,建议直接去官网下载最新的版本使用。

下载:

http://visualvm.github.io/download.html

插件下载:

http://visualvm.github.io/pluginscenters.html

插件的安装这里就不做介绍了,实际上不复杂,可以查找相关资料。

下面会给出其几个比较主要的功能视图的基本情况。

2.7.1 Overview

2.7.2 Monitor

2.7.3 Threads

2.4.7 Visual GC

2.8 远程监控

2.8.1 JMX远程监控

JMX的全称为Java Management Extensions,是管理Java的一种扩展。这种机制可以方便的管理正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。

如果希望我们的Java程序被JMX管理,那么可以在启动Java程序时添加下面的参数:

-Dcom.sun.management.jmxremote.port=端口号  
-Dcom.sun.management.jmxremote.ssl=false  
-Dcom.sun.management.jmxremote.authenticate=false  
-Djava.rmi.server.hostname=远程主机ip

之后打开VisualVM,进行下面的操作即可进行远程监控:

  • 1.右键Remote,选择Add Remote Host,填写前面配置的远程主机IP;
  • 2.在Remote的下拉列表中,选择刚刚添加的远程主机,右键,并选择Add JMX Connection,然后在Connection中填写前面配置的端口号即可,之后可以看到远程主机的下拉列表中有JMX监控,双击就可以打开VisualVM的监控界面;

2.8.2 Jstatd远程监控

使用jmx可以很好地监控java程序,但其实也有一个问题:原来没有被监控的java程序要想被监控,则需要修改启动参数并重启服务。那有没有更好的办法呢?

可以使用jstat,前面已经介绍过jstat了,而jstatd可以理解为它的服务端,在一台主机上面启动了jstatd,我们就可以监控当前这台主机上面所有的java程序。

先找到tools.jar的路径:

$ pwd
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/lib
$ ls tools.jar 
tools.jar

创建jstatd启动所需的安全策略文件:

$ cat /tmp/jstatd.all.policy 
grant codebase "file:/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/lib/tools.jar" 
    permission java.security.AllPermission;
;

然后启动jstatd

$ jstatd -J-Djava.security.policy=/tmp/jstatd.all.policy -p 34893

打开VisualVM,执行下面的操作:

  • 1.右键Remote,选择Add Remote Host,填写前面配置的远程主机IP;
  • 2.在Remote的下拉列表中,选择刚刚添加的远程主机,右键,并选择Add jstatd Connection,然后在Connection中填写前面配置的端口号即可,之后可以看到远程主机的下拉列表中有很多java进程,双击就可以打开VisualVM的监控界面,并对某一个java进程进行监控;

3 JVM优化

这里只是介绍一些比较常规的优化方法和思路,真正在生产环境中进行优化时,还需要看具体的业务,多总结这方面的经验总是有好处的。

3.1 堆内存大小调优

3.1.1 -Xms和-Xmx

即初始化堆内存大小和最大堆内存大小。

(1)-Xms

初始化的堆内存大小配置多少比较合适?可以启动你的Java程序,看稳定下来后,堆内存大小是多少,那么其实就可以设定为这个值。不建议设置得太小或者远小于程序稳定后所占的堆内存空间,因为这样会导致程序在启动时会频繁对堆内存空间进行扩容,不断申请内存,这会对启动速度造成一定影响。

也看到有一些程序会把-Xms-Xmx配置成一样大的,这样就完全避免了申请空间的影响,因为可能会存在这样一种情况:

-Xmx配置为2G,程序启动后堆内存稳定在1G左右。

但程序本身有可能会承受突然的并发,假设1G的堆内存空间无法处理这些并发,而2G的堆内存空间是可以处理的。
这样可能会导致一个问题,就是并发过来的时候匆忙申请堆内存空间,但实际上请求已经处理不过来了,很有可能
在申请内存空间与处理请求的同一时间,程序就因为堆内存空间不足而OOM了。

当然,这只是假设有这样一种情况(实际也遇到过),生产环境上还是考虑你程序的实际场景和实现方式。

(2)-Xmx

它应该是根据你程序的实际需要而进行适当地设置,并不是越大越好的,因为即使当前堆内存大小还没有达到最大堆内存大小,这部分内存也会被JVM虚拟机锁定。

3.2 非堆内存大小调优

3.2.1 -Xss

即线程栈大小。

注意这使用的是本地直接内存的空间,线程在执行时占用的主要空间是方法执行时的本地变量表,这也就意味着,当前线程压栈越多,其占用的空间也就越大,所以不能无限制地进行压栈,或者说线程栈所占用的空间不能无限地大,应该要做一定的限制,这时就可以配置-Xss参数。

比较常见的是配置-Xss256k-Xss512k等,当然取决于你的程序,如果确定本身程序不会很复杂,调用栈也不会很深,调小一点可以节省一定的内存空间。

3.2.2 -XX:MaxMetaspaceSize

即最大元空间大小。

即用来保存类信息、

以上是关于JVM核心参数配置常用调试命令工具与调优思路的主要内容,如果未能解决你的问题,请参考以下文章

JVM虚拟机性能监控与调优(JDK命令行JConsole)

JVM 监控,调优,调试

JVM调优常用指令之jinfo

JVM调优系列:JVM常用调试参数和工具

Tomcat性能监控与调优

jvm性能参数与调优