JAVA的垃圾收集器与内存分配策略【一篇文章直接看懂】( 二 )


由此,只需在新生代上建立一个全局的数据结构(记忆集, Set),该结构把老年代划分为若干小块,标识出哪块存在跨代引用 。因此发生Minor GC时,只将这些小块放入GC Root中进行扫描 。
不同分代的名词:
·部分收集( GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:
■新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集 。
■老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集 。目前只有CMS收集器会有单独收集老年代的行为 。另外请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,需按上下文区分到底是指老年代的收集还是整堆收集 。
■混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集 。目前只有G1收集器会有这种行为 。
·整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集 。
标记-清除算法(存活率低时较好)
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象
也可以反过来,标记存活的对象,统一回收所有未被标记的对象 。
主要缺点有两个:
执行效率不稳定 。若被回收的过多,则需要进行大量的标记和清除工作,导致执行效率随数量变化内存空间碎片化 。在标记清楚后,出现不连续的内存空间,在分配大对象时候,需提前出发GC操作
标记-复制算法(存活率低时较好)
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块 。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉 。当下,多数采用此方法回收新生代
主要缺点:
将可用的内存缩小了一半,浪费空间
改进后的Appel式回收:
把新生代分为一块较大的Eden空间和两块较小的空间,每次分配内存只使用Eden和其中一块 。发生垃圾搜集时,将Eden和中仍然存活的对象一次性复制到另外一块空间上,然后直接清理掉Eden和已用过的那块空间 。
标记-整理算法(存活率高时较好)
标记过程与“标记-清除”一直,但后续让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,是移动式的,而清除是非移动式的
移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而清除会产生碎片化空间 。
因此 。是否移动对象都存在弊端,移动则内存回收时会更复杂,不移动则内存分配时会更复杂,但是因内存分配和访问相比垃圾收集频率要高得多,这部分的耗时增加,总吞吐量仍然是下降的,因此移动相对划算 。
也有一种“和稀泥的方法”:平时多数时间都采用标记-清除算法,的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间
算法细节实现 根节点枚举
在帮助下,可以快速准确的完成GC Roots枚举
在根节点枚举时,必须暂停用户线程,但枚举必须在一个保障一致性的快照中进行,即不会在分析过程中,引用关系还在变化 。即使在号称停顿时间可控/几乎不停顿的CMS\G1\ZGC中,根节点枚举也是必须要停顿的 。
但,目前JAVA虚拟机中采用的都是准确式垃圾收集``,故可以在停顿下来后,检查所有的上下文/全局的引用位置,可以直接得到对象引用(使用一组为的数据结构存放) 。
在类加载完成后,会把对象中的偏移量对应的类型数据计算出来,在即时编译过程中,在特定位置会记录下栈里的寄存器哪些位置是引用 。