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