在虚拟机运行过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定帮助,本篇文章主要总结一些常用的参数,这样就可以在系统能够运行时监控垃圾回收,便于分析。

主要参数如下:

-XX:+PrintGC         	打印GC日志
-XX:+PrintGCDetails   	打印详细的GC日志
-XX:+PrintHeapAtGC  	打印堆内存GC前后的信息
-XX:+PrintGCApplicationConcurrentTime 	打印系统执行时间
-XX:+PrintGCApplicationStoppedTime  		打印应用程序由于GC而产生的停顿时间
-verbose:class   		跟踪类的加载和卸载,或者由下面两个参数代替
-XX:+TraceClassLoading    		跟踪类的加载
-XX:+TraceClassUnloading  		跟踪类的卸载
-XX:+PrintVMOptions				打印虚拟机接收到的命令行显示参数
-XX:+PrintCommandLineFlags			打印传递给虚拟机的显式和隐式参数,隐式参数指虚拟机启动自动设置的
-XX:+PrintFlagsFinal							打印所有的系统参数的值
-Xmx				设置堆内存最大值
-Xms				设置堆内存最小值
-Xmn				设置新生代大小
-XX:SurvivorRatio				设置新生代中eden空间和from/to空间的比例关系,等于eden/from
-XX:NewRatio					设置老年代和新生代的比例
-XX:+HeadpDumpOnOutOfMemoryError				内存溢出时导出整个堆的信息
-XX:HeapDumpPath						指定导出堆的存放路径
-XX:PermSize/-XX:PermSize				配置永久区大小/最大大小 (仅在JDK1.8之前的版本中可以使用,JDK1.8后移除了方法区,使用了元数据区)
-XX:MaxMetaspaceSize						指定元数据区的最大内存值
-XX:MaxDirectMemorySize						设置直接内存的最大值,不设置默认为最大堆内存空间
-XX:+PrintReferenceGC							跟踪系统内的软引用、虚引用、弱引用和Finallize队列的信息
-XX:+UseSerialGC							虚拟机Client模式下的默认垃圾回收器,使用Serial+Serial Old的收集器组合进行内存回收
-XX:+UseParNewGC						使用ParNew+Serial Od的收集器组合进行内存回收
+XX:+UseConcMarkSweepGC				使用ParNew+CMS+Serial Old的收集器进行内存回收,Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后 的后备收集器使用
-XX:+UseParallelGC							server模式下的默认值,打开此开关后,使用parallel Scavenge+Serial Old的收集器组合回收内存
-XX:+UseParallelOldGC					使用Parallel Scavenge+Parallel Old的收集器组合进行内存回收
+XX:+PretenureSizeThreshold				直接晋升到老年代的对象大小,大于该值的对象直接到老年代分配内存
-XX:+MaxTenuringThreshold					晋升到老年代的对象年龄,每个对象在坚持过一次minor GC之后,年龄就增加1,当超过这个参数值时进行老年代
-XX:+UseAdaptiveSizePolicy						动态调整java堆中 各个区域的大小以及进入老年代的年龄
-XX:+HandlePromotionFailture					是否允许分配担保失败
-XX:+ParallelGCThreads							设置并行GC时进行内存回收的线程数
-XX:+GCTimeRatio								GC时间占总时间比率,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge收集器时生效
-XX:+MaxGcPauseMills					设置GC最大停顿时间,仅在使用Parallel Scavenge收集器时生效
-XX:+CMSInitiatingOccupancyFraction			设置CMS收集器在老年代空间被使用多少后触发垃圾收集,默认值为68%,仅在CMS收集器时生效
-XX:+UseCMSCompactAtFullCollection			设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用CMS收集器时生效
-XX:+CMSFullGCsBeforeCompation				设置CMS收集器在 进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用CMS收集器时生效
-XX:+DoEscapeAnalysis 			启用逃逸分析技术
-XX:+EliminateAllocations			开启标量替换(默认打开)
-XX:+UseTLAB						使用TLAB(默认打开)

理解GC日志

以下面一段GC日志为例:

[GC (System.gc()) [PSYoungGen: 1730K->504K(2560K)] 1730K->724K(9728K), 0.0012169 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 220K->563K(7168K)] 724K->563K(9728K), [Metaspace: 2687K->2687K(1056768K)], 0.0059673 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 0K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 0% used [0x00000000ffd00000,0x00000000ffd00388,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 563K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 7% used [0x00000000ff600000,0x00000000ff68ce68,0x00000000ffd00000)
 Metaspace       used 2693K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 298K, capacity 386K, committed 512K, reserved 1048576K

如上一段GC日志,前面GC/Full GC代表GC的类型,PSYoungGen是因为笔者机器上采用Parallel Scavenge收集器,因此新生代叫做PSYoungGen,如果使用的是Serial收集器新生代为DefNew,采用ParNew收集器,新生代为ParNew。

接着1730K->504K(2560K)代表回收前新生代大小为1730K,回收后为504K大小,括号里面代表新生代可用内存大小,接着1730K->724K(9728K)代表整个堆内存从回收前的1730K变为724K,整个堆的可用内存大小为9728K,约10M(这里笔者设置-Xmx10m -Xms10m)。

这里容易让人想起一个问题,为什么实际可用的堆大小与设置的堆大小不同,这是因为分配给堆的内存空间和实际可用的内存空间不是一个概念,由于垃圾回收的需要,虚拟机会对堆空间进行分区管理,不同的区域采用不同的回收算法,一些算法会使用空间换时间的策略,因此存在可用内存的损失。

接着0.0012169 secs是指垃圾回收浪费的时间,[Times: user=0.00 sys=0.00, real=0.00 secs] 显示GC所花费的时间,其中user代表用户态CPU耗时,sys代表系统CPU耗时,real代表GC实际经历的时间。

PSYoungGen      total 2560K, used 0K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 0% used [0x00000000ffd00000,0x00000000ffd00388,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

这段是指新生代的GC信息,总的大小为2560K,使用了0K,使用0K是因为笔者代码中设置了大对象,直接分配到了老年代,所以这不影响我们分析GC日志。

[0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)分别代表新生代/或者老年代的下界,当前上界和上界。使用上界减去下界可以得到当前堆内存的最大值,使用当前上界减去下界,就是当前虚拟机已经为程序分配的空间大小。如果当前上界等于上界,代表堆内存已经不能扩大。上面相等 是因为这里设置-Xmx和-Xms相等。

ParOldGen       total 7168K, used 563K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 7% used [0x00000000ff600000,0x00000000ff68ce68,0x00000000ffd00000)
 Metaspace       used 2693K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 298K, capacity 386K, committed 512K, reserved 1048576K

上面代表老年代以及元数据区的内存使用信息,和上面的类似。

Client和Server工作模式

目前java虚拟机支持client和server两种运行模式,使用-server参数可以指定server模式,使用-client参数可以指定使用server模式,使用-version参数可以查看当前的模式。

与client模式相比,server模式的启动比较慢,因为server模式会尝试收集更多的系统性能信息,使用更加复杂的优化算法对程序进行优化,所以,系统server模式在启动时间上比client模式要慢一些,但是当它启动完成并且进入稳定运行期之后,server模式的执行速度会远远快于client模式。

所以server模式更适用于后台长期运行的系统,对于一些运行周期并不长,但是又经常使用的系统,比如用户界面程序,选择client模式可能性能更好一些。

java虚拟机在这两种不同的模式下,有些参数也有所不同,可以使用上述的参数-XX:PrintFlagsFinal观察一些参数的不同值。在client模式下,CompileThreshold的默认值为1500,也就是函数被调用1500次后,就会进行JIT编译,而在server模式下,该值为10000,所以,server模式下系统更有可能执行解释执行,但是一旦编译,其优化效果要更优于client模式,像这样,还有很多参数,详情读者可以使用类似的方式去查找比较。