2.垃圾收集器与内存分配策略

一,如何判断对象是否应该回收

①引用计数法

为每个对象分配一个空间专门记录该对象被引用的次数,当被引用的时候加一,当被取消引用的时候减一,当该值等于0时表示该对象没有被引用,可以回收.

致命的缺点是,当对象与对象之间存在循环引用时,无法得到正确地回收

②可达性分析

就是对gc root中的对象的引用链进行分析,没有被引用链串到的就是不可达对象,也就是可回收对象 ,gc root主要包括栈帧里面变量的对象引用,方法区中常量池里面的静态变量的引用等

二,垃圾收集算法

①分代收集理论

新生代回收的频率可以频繁一点,而且只标记存活的对象,因为新生代回收利润比较高,且因存活的对象相对较少,所以标记存活的对象会更省时省力

老年代回收的频率可以低一点,因为老年代存活的稳定性相对较高,扫描的成本也比较高(扫的多,回收的少)

②标记清除算法(可达性分析)

利用可达性分析标记出引用到的对象,分析完后清除未标记的对象(当然,也可以反过来)

缺点是产生大量内存碎片

③标记复制算法(可达性分析)

对标记清除产生碎片的优化,将内存划分为相等的两块,其中一块用于分配内存,当垃圾回收时也只针对这块内存进行标记回收,并将存活的对象复制到另一块内存中,缺点是内存可分配空间减半,

为了优化这一点,引入了Eden和Survivor,Eden:Survivor:Survivor = 8:1:1,也就是每次的可用空间是Eden和其中的一块Survivor,回收的时候把存活的对象复制到另外一块Survivor,

但是,当回收的存活对象大于一个Survivor空间时,启用分配担保机制,将多出的存活对象放到老年代那里

④标记整理算法(可达性分析)

针对老年代回收的存活对象比较多,复制会消耗比较多的性能,专门设计的算法,通过标记出存活的对象,并将这些对象往同一侧移动,就可以在另一侧腾出规整的空间,又可以减少复制的开销

缺点是,移动的过程需要stop the world,即停用用户线程,这会造成卡顿,影响体验

 三,HotSpot的算法细节实现

①根节点枚举 

利用OOPMap直接得出引用的gcroot,而不需要真的从整个方法区和栈里面遍历所有的位置来获取有引用的位置

②安全点

指设置OOPMap的地方,也是线程指定到这里可以进行垃圾回收的地方

③安全区域

不会发生引用变化的代码指令区域

④记忆集与卡表

记忆集指的是用来存放存在从非收集区指向收集区的存在跨代引用的内存区域的概念

卡表是对记忆集的一种实现,通过一个数组的每一个索引位对每一个卡页,如果该卡页存在跨代引用,那么标识为1,不存在则标识为0,这样子 垃圾收集器在根枚举的时候只需要遍历一遍卡表的每一个元素,筛选出值为1的元素,就可以定位到有跨代引用的内存地址了

⑤写屏障

通过写屏障来为每一次的引用赋值操作的前后来更新卡表的状态

⑥并发的可达性分析

 三色标记法:白色,灰色,黑色

白色表示这个对象还没进行可达性分析过

 灰色表示该对象涉及的引用分支还未全部遍历分析完,只分析了部分

黑色表示该对象涉及的引用链都分析完了

那么结果将会是,一轮完整的可达性分析完之后,白色对象就是可以回收的对象

三色标记法并发问题

如果在标记过程中,一些已经分析完的黑色对象的引用发生变化,那么将导致这些引用不会被分析到,导致误删

增量更新

将标记过程中,产生的从黑色对象产生的新引用都记录下来,在重新标记的过程中,再遍历一遍

原始快照

将标记过程中,灰色对象产生的删除的引用记录下来,在重新标记的过程中,再遍历一遍,相当于保存了当时的引用链

 四,经典的垃圾收集器

①Serial收集器,单线程收集,新生代,标记复制

②ParNew收集器,多线程并行收集,新生代,标记复制

③Parallel Scavenge收集器,多线程并发收集,新生代,标记复制,可以调节吞吐量

④Serial Old收集器,单线程收集,老年代,标记整理

⑤Parallel Old收集器,多线程并行收集,老年代,标记整理

⑥CMS收集器,初始标记,并发标记,重新标记,并发清除,老年代,标记清除

⑦G1收集器,初始标记,并发标记,最终标记(并行不并发),筛选回收(并行不并发) ,region(老年代region,年轻代region),回收的时候将筛选出来的要回收的region将存活的对象复制到其他region上,再回收掉旧region

 

posted @ 2021-11-11 20:45  Kyhoon  阅读(26)  评论(0编辑  收藏  举报