GC日志介绍:
每一种回收器的日志格式都是由其自身的实现决定的,换而言之,每种回收器的日志格式都可以不一样。但虚拟机设计者为了方便用户阅读,将各个回收器的日志都维持一定的共性。JavaGC日志 中简单介绍了这些共性
GC日志如何查看
可以通过在java命令种加入参数来指定对应的gc类型,打印gc日志信息并输出至文件等策略
GC的日志是以替换的方式写入的,而不是追加,因此如果下次写入到同一个文件中的话,以前的GC内容会被清空。
参数列表
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:…/logs/gc.log 日志文件的输出路径
GC日志格式
GC日志一般都是看两种GC:Minor GC(young GC)和Full GC
这里使用如下的参数来进行日志的打印:
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
对于新生代回收的一行日志,其基本内容如下:
[GC 611.633: [DefNew: 843458K->2K(948864K), 0.0059180 secs] 2186589K->1343132K(3057292K), 0.0059490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
其含义大概如下:
[GC(表示Young GC) 611.633: [DefNew(单线程Serial年轻代GC): 843458K(年轻代垃圾回收前的大小)->2K(年轻代回收后的大小)(948864K(年轻代总大小)), 0.0059180 secs(本次回收的时间)] 2186589K(整个堆回收前的大小)->1343132K(整个堆回收后的大小)(3057292K(堆总大小)), 0.0059490 secs(回收时间)] [Times: user=0.00(用户耗时) sys=0.00(系统耗时), real=0.00 secs(实际耗时)]
FullGC
Full GC日志介示例:
[Full GC (Metadata GC Threshold) [PSYoungGen: 5104K->0K(132096K)] [ParOldGen: 416K->5453K(50176K)] 5520K->5453K(182272K), [Metaspace: 20637K->20637K(1067008K)], 0.0245883 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
我们使用一个例子来对GC过程进行分析:
假设我们通过JVM参数 -Xms20M、-Xmx20M、-Xmn10M 把Java堆大小设置为20MB,不可扩展,其中10M分配给新生代,另外10M分配给老年代,使用
-XX:+PrintHeapAtGC参数,将发生GC前后的堆使用情况打印出来,使用
-XX:+PrintGCDetails参数,将程序执行之后的堆内存使用情况打印出来
然后我们通过-XX:SurvivorRatio=8来分配新生代各区的比例,设置为8,表示eden与两个survivor区的空间比例为8:1:1
然后我们尝试分配三个2MB的对象到Eden,会发现触发了一次Young GC,是因为Eden区域本身就存放了类对象占据了一定的空间(30%左右),因此Eden区只能存放5M的新创建对象,具体代码如下:
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[2*_1MB];
allocation2 = new byte[2*_1MB];
allocation3 = new byte[2*_1MB];
}
GC发生之前堆内存使用情况
{Heap before GC invocations=1 (full 0):
PSYoungGen total 9216K, used 7165K
eden space 8192K, 78% used #eden区域系统对象占据30%,剩下的内存存储两个2M的byte数组
from space 1024K, 0% used
to space 1024K, 0% used
ParOldGen total 10240K, used 0K
object space 10240K, 0% used #old区域使用0%
Metaspace used 3088K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 323K, capacity 392K, committed 512K, reserved 1048576K
发生一次young GC
[GC (Allocation Failure) [PSYoungGen: 7165K->1023K(9216K)] 7165K->5179K(19456K), 0.0024131 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC发生之后堆内存使用情况
Heap after GC invocations=1 (full 0):
PSYoungGen total 9216K, used 1023K
eden space 8192K, 0% used
from space 1024K, 99% used
to space 1024K, 0% used
ParOldGen total 10240K, used 4156K
object space 10240K, 40% used
Metaspace used 3088K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 323K, capacity 392K, committed 512K, reserved 1048576K
}
程序执行完成的时候堆内存使用情况
Heap
PSYoungGen total 9216K, used 3317K
eden space 8192K, 28% used
from space 1024K, 99% used
to space 1024K, 0% used
ParOldGen total 10240K, used 4156K
object space 10240K, 40% used
Metaspace used 3095K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 324K, capacity 392K, committed 512K, reserved 1048576K
分析上面的GC日志我们可以看到:
GC发生之前,Eden区存放了系统本身的对象和,两个2M的byte[]数组,内存使用情况是78%,当Eden区尝试给第三个byte【】数组分配内存的时候,发现内存不够(Allocation Failure)引发了一次young GC,GC首先将系统对象放入了from区域,然后发现byte[]数组放不下,于是将两个byte[]数组放入到了old区,因此发生GC之后堆内存是old区使用了10M的40%,而Eden区为0%
GC执行完毕之后,Eden区有空间了,于是给最后一个byte【】数组分配2M内存空间,此时Eden使用率为28%,而一部分系统对象存在于from区中,old区域仍然是两个byte【】数组
当然Eden区的内存分配还有另一种判断,如果放入的对象的大小超过Eden区的一半,那么直接将此对象放入老年代,这就是大对象直接放入老年代的原理
也就是说如果第三个对象是5M,那么其会被直接放入老年代,而不会触发young GC
GC日志如下:
Heap
PSYoungGen total 9216K, used 6602K
eden space 8192K, 80% used
from space 1024K, 0% used
to space 1024K, 0% used
ParOldGen total 10240K, used 5120K
object space 10240K, 50% used # 大对象直接放入老年代
Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 348K, capacity 388K, committed 512K, reserved 1048576K
没有打印GC前后堆内存使用信息,说明没有触发young GC