GC收集器介绍及GC日志阅读

GC收集器:

(1)Serial收集器(新生代收集器)

单线程收集垃圾,在其回收垃圾的时候必须暂停其他所有工作进程

(2)ParNew收集器(新生代收集器)

Serial收集器的多线程版本,除了Serial收集器之外,目前只有它能和CMS收集器配合工作

(3)Parallel Scavenge收集器(新生代收集器)

吞吐量优先收集器,吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)

自适应调节参数设置开关: -XX: +UseAdaptiveSizePolicy

控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis

设置吞吐量大小:-XX:GCTimeRatio(默认值为99,即允许最大1/(1+99)的垃圾收集时间)

(4)Serial Old收集器(老年代收集器)

Serial收集器的老年代版本,同样是一个单线程收集器,两种用途:在jdk1.5版本及以前版本与Parallel Scavenge收集器组合使用;作为CMS收集器的后备方案

(5)Parallel Old收集器(老年代收集器)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,jdk1.6开始提供,在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old的组合

(6)CMS收集器(老年代并发收集器)

CMS收集器是基于“标记-清除”算法实现的,过程分四步:初始标记——>并发标记——>重新标记——>并发清除;初始标记和重新标记仍需STW,耗时最长的并发标记和并发清除可以与用户线程并发执行。

CMS收集器有三个明显的缺点:

①CMS收集器对CPU资源非常敏感,在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分CPU资源而导致应用程序变慢,总吞吐量会降低.

②CMS收集器无法收集浮动垃圾(在并发过程中用户线程新产生的垃圾),可能会出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生;由于老年代没有分配担保机制,需要预留一部分内存作为并发收集时的程序使用,如果预留的内存无法满足程序需要,将会启动后备预案:临时启动Serial Old收集器对老年代重新进行垃圾收集,这样导致的停顿时间就会变得很长了。

③CMS收集器会导致空间碎片过多提前触发一次Full GC,因为它是基于“标记-清除”算法实现的。

(7)G1收集器(老年代并发收集器)

G1收集器是一款面向服务端应用的垃圾收集器,与其他的GC收集器相比,GC收集器具备以下特点:

①并行与并发

②分代收集

③空间整合

④可预测的停顿

 

————————————————————————————————————————————

内存与回收策略:

 

3.6.1对象优先在Eden区分配内存

测试代码:

public class test0305 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 参数:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

* 对象优先在Eden分配

*/

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];

allocation4 = new byte[4 * _1MB];//出现一次Minor GC

System.out.println("hello");

}

}

 

打印GC结果:

 

参数说明:

设置虚拟机的堆内存为20M不可扩容,其中新生代内存为10M,Eden块:Survivor块=8:1,打印GC日志,GC收集器采用Serial+Serial old组合

GC结果阅读:

Eden块已使用内存由7292k变为625k(总大小9216k),堆内存由7292K变为6769K(总大小19456k)

Eden块内存大小 8192k 已使用52%

Survivor块1大小为1024k 已使用61%

Survivor块2大小为1024k 已使用0%

老年代内存大小为10240k,已使用60%

GC结果分析:

Eden块内存被GC收集时,allocation1,allocation2,allocation3三个对象转移到了老年代,allocation4被放入到Eden块,GC收集前后堆内存减小了600k的原因是 当前堆内存 = Eden+Survivor2+老年代,减小的内存跑到了Survivor1里,没有被计算进堆内存。

 

———————————————————————————————————————————————————

 

3.6.2大对象直接进入老年代

测试代码:

 

public class test0306 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 参数:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

* -XX:pretenureSizeThreshold=3145728

*/

public static void main(String[] args) {

byte[] allocation;

allocation = new byte[4 * _1MB];

System.out.println("hi");

}

 

}

测试结果:

参数说明:

-XX:pretenureSizeThreshold=3145728,当对象大于3M时直接进入老年代;

GC结果阅读:

新生代内存大小9216k,已使用1312k

Eden区内存大小8192k,已使用16%

Survivor区1内存大小1024k,已使用0%

Survivor区2内存大小1024k,已使用0%

老年代内存大小10240k,已使用40%

GC结果分析:

allocation数组对象大小为4M,大于3M,直接进入老年代,占比40%。

_________________________________________________________________________________________________

3.7.1长期存活的对象直接进入老年代

测试代码:

 

public class test0307 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 参数:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

* -XX:+PrintTenuringDistribution

*/

public static void main(String[] args) {

byte[] allocation1,allocation2,allocation3;

allocation1 = new byte[_1MB/4];

//什么时候进入到老年代取决于MaxTenuringThreshold设置

allocation2 = new byte[4 * _1MB];

allocation3 = new byte[4 * _1MB];

allocation3 = null;

allocation3 = new byte[4 * _1MB];

System.out.println("hi");

}

 

}

测试结果1(设置年龄为1时进入老年代):

参数说明:

-XX:MaxTenuringThreshold=1 设置对象年龄为1时进入到老年代 -XX:+PrintTenuringDistribution 显示对象的年龄

GC结果阅读:

Survivor区大小的一半是524288bytes,Survivor容纳的对象年龄为1时转移到老年代

第一次回收:新生代内存大小5500k变成881k(总大小9216k),堆内存大小5500k变成4977k(总大小19456k)

第二次回收:新生代内存大小4977k变成0k(总大小9216k),堆内存大小9073k变成4976k(总大小19456k)

新生代总内存大小9216k,已使用4337k

Eden块内存大小8192k,已使用52%

Survivor1内存大小1024k,已使用0%

Survivor2内存大小为1024k,已使用0%

老年代内存大小10250k,已使用48%

GC结果分析:

第一次GC发生在allocation3对象分配内存的时候,此时Eden块8M内存(已分配1/4M+4M)不足以再分配足够内存给allocation3,Survivor区可以容纳下allocation1对象,复制到Survivor2区,allocation2对象通过分配担保机制转移到到老年代,allocation3对象进入到Eden块。

第二次GC发生在第二次给allocation3分配内存的时候,因为allocation3=null的语句,导致之前的4M内存失去了引用(可被回收,但没触发GC),因为Eden区存在初始时内存占用几百k的原因,不足以再分配一个4M内存给allocation3(Eden块已分配几百k+4M),触发第二次GC,将Survivor中的allocation1和失效的allocation3的4M内存回收,最后为allocation3分配新的4M内存

 

测试结果2(设置年龄为15时进入老年代):

参照《深入理解Java虚拟机》P96结果(jdk1.7之后对虚拟机进行了改动。jdk1.8版本实验结果有误差)

具体详情解释见:https://www.cnblogs.com/zyh186/p/7349707.html

GC结果阅读:

Survivor区大小的一半是524288bytes,Survivor容纳的对象年龄为1时转移到老年代

第一次回收:新生代内存大小4859k变成404k(总大小9216k),堆内存大小4859k变成4500k(总大小19456k)

第二次回收:新生代内存大小4500k变成404k(总大小9216k),堆内存大小8596k变成4500k(总大小19456k)

新生代总内存大小9216k,已使用4582k

Eden块内存大小8192k,已使用51%

Survivor1内存大小1024k,已使用39%

Survivor2内存大小为1024k,已使用0%

老年代内存大小10250k,已使用40%

GC结果分析:

第一次GC和测试1一致,第二次GC在没有足够内存分配给allocation3时,没有转移Survivor块中的allocation1对象(因为此时allocation1的年龄为1,不满足转移到老年代条件),释放了Eden块无效的4M内存,重新分配了4M内存给allocation3

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值