JVM (一)gc日志分析与自动内存管理机制

理解GC日志

每一种垃圾收集器的日志形式都是由它们的自身实现决定的,所以每种垃圾收集器的日志格式都可能不一样,但是为了方便开发人员理解日志,各个收集器的日志格式都维持了一定的共性.
例如以下2段典型的GC日志:

(1)33.125:[GC [DefNew: 3324K->152K(3712K),0.0025925 secs] 3324K->152K(11904K),0.0031680 secs]
(2)100.667:[Full GC [Tenured: 0K->210K(10240K),0.0149142 secs] 4603K->210K(19456K),[Perm:2999K->2999K(21248K)],0.0150007 secs] [Times:user=0.01 sys=0.00,real=0.02 secs]

最前面的数字”33.125”和”100.667”:代表了GC发生的时间,这个数字的含义是从Java虚拟机启动以来经过的秒数.
GC日志开头的”[GC”和”[Full GC”说明了这次垃圾收集停顿的类型.如果有”Full”,说明此次GC是发生了”stop the world”的.分配担保失败,也会发生full gc.如果是调用System.gc()方法所触发的收集,那么这里将显示’[Full GC(System)]’;
注:Eden中比较频繁的GC,每次GC都会导致内存分布的变化.对象的计数器:对象的Age是标记的关键.
年轻代的GC又叫Minor GC.对象的GC年龄默认阀值是15.如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设定为1,对象在Survivor中每”熬过”一次Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15岁),将会被晋升到老年代中.
而实际运行的时候,Survive区域中,对象不一定要达到阀值就进入Old Generation.有这样一种情况,如果在Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄.这是虚拟机动态对象年龄判断.
新生代GC时,对象进入Old Generation不是一帆分顺的,如果要进入对象的大小超过old generation剩余空间的大小,就会发生full gc.full gc耗时是新生代GC的好几十倍,会发生STW,即’stop the world’.
另外还有一种情况发生full gc,由参数handlePromotionFailure决定.
空间分配担保(HandlerPromotionFailure):
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC是可以确保安全的.如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败.如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果小于,或者HandlerPromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC.
一般打开这个开关,有OOM的风险,冒风险来减少full gc.

接下来的”[DefNew”,”[Tenured”,”[Perm”表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的.
新生代名称和所使用收集器不同对应的GC发生区域:
Serial收集器->”Default New Generation”->”DefNew”
ParNew收集器->”Parallel New Generation”->”ParNew”
Parallel Scavenge收集器->”PSYoungGen”

后面方括号内部的”3324K->152K(3712K)”,含义是”GC前该内存区域已使用容量->GC后该区域已使用容量(该内存区域总容量)”.而在方括号之外的”3324K->152K(11904K)”表示”GC前java堆已使用容量->GC后java堆已使用容量(Java堆总容量)”.
再往后”0.0025925 secs”表示该区域内存GC所占用的时间.
tips:对象优先在Eden区域分配,大对象直接进入老年代.长期存活的对象将进入老年代.
举例分析:
JVM参数:

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC  -XX:PretenureSizeThreshold=3423454

代码:

 public class testPretenureSizeThreshold {
    private static final int _1MB = 1024*1024 ;
    public static void main(String[] args) {
        byte[] allaction ;
        allaction = new byte[4*_1MB];

    }
}

结果:

Heap
 par new generation   total 9216K, used 1147K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  14% used [0x00000000fec00000, 0x00000000fed1ef58, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 concurrent mark-sweep generation total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2659K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K

虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配.这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存).
看gc日志,老年代的10MB(10240K)空间被使用了4096K,也就是4MB的allocation对象直接在老年代分配,这是因为PretenureSizeThreshold被设置为3MB(3145728).

热评文章