垃圾收集器和内存分配策略(四)
垃圾收集器
G1收集器
概念
G1作为一个可以使用新生代和老年代两块不同内存区域的收集器(但是G1所管理的内存区域与传统的分代内存模型有一定的区别,它划分了很多块Region,每一块Region有新生代和老年代两块,所有Region一同组成新生代和老年代,同时作为新生代或者老年代内存可以是不连续的),保证了类似CMS的并发处理的特性,在JDK1.7后正式提供商用版本
使用算法
从整体(所有的Region)来看采用的“标记-整理”算法;从局部(两个Region)来说基于“复制”算法实现的;这样两种算法可以==避免CMS收集器所产生的内存碎片==
执行流程
- 初始标记:这个过程不能并发执行,需要“stop the world”,标记出需要回收的对象
- 并发标记:GC线程和用户线程同时执行,GC线程同步标记出需要回收的内存
- 最终标记:这个过程也是不能并发的,但是这一步需要记录对象的变化信息从Remembered Set Logs记录到==Remembered Set==中
- 筛选回收:根据用户期望的回收时间,最终进行回收,这个过程是并发处理的
如何在不同的Region中进行对象可达性分析
试想一下,作为一个GC无论采用什么方式去回收内存,那么无法避免的就是对象的可达性分析,那么假如我们现在采用的是使用物理上不连续的多个Region来存储对象,那么我们假设其中一个Region中的对象等于null,那么能不能直接断定这个对象不具有可达性呢?答案肯定是不行的,因为为了保证虚拟机的完整性和正确性,我们需要确定这个Region对象是否还在其他Region中还存在实例(或者说具有可达性),那么常规的做法是遍历所有的Region然后最终判断该对象是否还存在实例,但是这样做法效率太低,虚拟机在处理这个问题上加入一个==Remembered Set==,这个东西我们可以理解为一个对象引用信息的记录表,在每一个Region上维护一个Remembered Set避免GC去全堆扫描,判断对象是否具有可达性
其实作为其他GC收集器也是一个道理,只是因为只有一个整体的内存堆,那么只需要维护一个Remembered Set则可以避免全堆扫描
G1特点
- 并发和并行:G1可以充分利用多CPU的优势做“初始标记”和“最终标记”;同时并行在“并发标记”和“整理回收”两个过程可以用户线程同时进行
- 分代收集:作为G1也是分代收集的模式依然保留下来了,处理不同类型对象,还不同的应对方式
- 避免内存碎片:因为采用的是“标记-整理”和“复制”算法,那么G1可以避免内存出现碎片的情况
- 可以预测的停顿:G1和CMS都是最为追求低停顿的收集器,但是G1提供了一个提顿时间设置的功能,这样我们可以在一定程度上控制垃圾收集停顿时间
内存分配与回收策略
- 对象优先在Eden分配
- 大对象直接进入老年代,可以通过参数-XX:PretenureSizeThreshold设置直接进入老年代的对象
- 长期存活的对象将进入老年代,JVM通过对每一个对象提供一个年龄字段存放在对象头中,每当对象在通过一次Minor GC没有被回收就在年龄上+1,每一个对象初始化年龄是0,虚拟机默认进入老年代年龄是15,也可以通过-XX:MaxTenuringThreshold设置进入老年代的年龄
- 动态对象年龄判断,指的是当新生代中Survivor空间中相同年龄的所有对象大小的总和达到或者超过Survivor空间的一半,那么这个年龄或者大于这个年龄的所有对象都会直接进入老年代
- 空间分配担保模式,在进行Minor GC(也就是新生代GC),虚拟机会在去查看老年代最大连续空间是否大于当前所有新生代对象所占空间,如果大于,那么就直接开始Minor GC,但是如果不大于,==那么虚拟机就会查看-XX:HandlePromotionFailure是否是开启的,如果开启就分析之前每次进行Minor GC所存活对象平均占用空间数值,如果目前目前老年代空间大于之前平均大小,那么就冒险进行Minor GC(冒险是因为这一次GC有一定概念是无法成功的),如果小于那么直接停止Minor GC,先进行Full GC直到老年代腾出足够的空间==;如果-XX:HandlePromotionFailure是关闭的,那么就直接进行Full GC;但是虚拟机这个-XX:HandlePromotionFailure默认是开启的,避免每次进行Full GC