在虚拟机运行过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定帮助,本篇文章主要总结一些常用的参数,这样就可以在系统能够运行时监控垃圾回收,便于分析。
主要参数如下:
-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模式,像这样,还有很多参数,详情读者可以使用类似的方式去查找比较。