首页 > 其他 > 详细

垃圾回收策略与经典垃圾回收器

时间:2021-08-30 14:53:42      阅读:60      评论:0      收藏:0      [点我收藏+]

一、垃圾回收器

技术分享图片技术分享图片

 

1.1 哪些对象可以回收?

1.1.1 引用计数算法

引用分析算法指的是对每一个对象进行计数,对象被引用时进行+1,引用失效时进行-1,当进行垃圾回收时,如果计数为0
    • 缺点:如果存在对象相互引用的情况,如果两个对象都已不再使用,但是由于引用关系,两个对象都不会被回收导致内存泄露
 

1.1.2 可达性分析算法

可达性分析指的是对象如果可以通过被引用找到属于根节点的对象,那么任务该对象不可回收
    • 属于根节点的对象: GCroots
      • 虚拟机栈中局部变量表所引用的对象
      • 静态属性引用的对象
      • 常量引用的对象
      • 本地方法引用的对象
      • 同步锁持有的对象
 

1.1.3 对象回收过程

如果该对象重写了finalize()方法,回收过程会执行finalize() 方法,但是jvm只会执行一次finalize()方法,可以在这个方法中对该对象进行引用,那么此时该对象就不会被回收。如果下一次在被回收就不会执行finalize()方法
    • 两次标记:
      • 第一次通过可达性算法时,发现对象不在GCRoot上就会标记该对象,然后执行finalize(),如果该对象再次被引用就不会被回收
      • 在第一次基础上进行回收标记,进行回收

1.2 垃圾收集算法

1.2.1 分代理论

三个假说:
  • 弱分代假说:大多数对象都是很快可以被回收的
  • 强分代假说:很多次垃圾收集后还存活的对象后面几乎很难被回收
  • 跨代引用假说:跨代引用相对于同代引用只占用少数
    跨代引用也会在多次回收后变成同代引用,使用记忆集,将跨代引用的对象作为GCRoots的部分,避免了对整个老生代进行扫描

1.2.2 标记-清除算法

标记-清除算法的两个过程:
    1. 判断该对象是否需要回收,不需要回收的在对象头header进行标记
    2. 该阶段并不是真正意义上的垃圾回收,而是将没有标记的对象放入free_list中,新建对象是就从该list中拿出一块合适的空间。对于空闲列表中的内存查询一般有以下三种策略:
      1. First_fit : 遍历free_list,返回第一块大于等于需求内存大小的内存地址
      2. Best_fit :遍历free_list,找出所有符合需求内存大小的内存地址并返回最小的一块内存
      3. Worst_fit : 遍历整个free_list ,找出最大的内存分块对于需求内存大小进行切割,并返回切割的内存地址。
优点:
    1. 逻辑简单
    2. 不需要移动对象,不需要进行移动对象后的操作
但是该算法存在一定的缺陷:
    • 碎片化的空间过多,导致空间浪费
    • 如果进行回收垃圾进行标记,那么标记和清除的过程变量,效率变低
    • 分配速度慢,每次分配内存都需要遍历一个空闲列表,如果想要优化改方式的话可以使用多空闲列表,对于大小在某个范围的分块放在同一个空闲列表中。
    • STW

1.2.3 复制算法

标记-复制算法:对于内存空间进行划分,每次只使用一般的内存,当需要进行垃圾回收时,将不需要的回收的对象放入另一部分内存中。这样就解决了内存碎片化问题
缺点:
    • 内存使用效率低,每次只是用一半的内存
    • 需要对对象进行复制,导致对象的内存地址发生变化,需要更改引用该对象的地址(对象直接访问方式,对于句柄方式 的只需要修改句柄列表中对于的属性)
Apple式标记复制算法,使用eden,ser区的方式,当不需要回收的数量大与10%,就会出现异常

1.2.4 标记-整理算法

标记-整理算法过程:先对对象进行标记,然后对不需要的对象进行移动到内存的另一端,然后删除不需要的对象,当进行垃圾回收时,会STW。
    • 在垃圾回收过程中需要移动对象,效率降低
    • 整理过程中的算法:
      • 任务顺序:
      • 滑动顺序:按照原先堆中的顺序滑动排序
      • 线性顺序:考虑对象的引用关系,尽可能的将引用的对象放在一起

可查看的网页链接:https://www.jianshu.com/p/698eb5e1ccb9

 

   1.2.5 垃圾回收算法细节

  • GC根节点枚举
    在进行标记的过程中通过可达性分析判断对象是否存活,因此需要对GC根节点进行枚举,然后再通过引用链获取存货对象进行标记。为了保证这个过程中对象引用不发生变化,需要进行STW,因此通过安全点和安全区域的方式去解决引用一致性,使用GCmap快速查找。
    在hotstop中,使用OOPmap的方式减少根节点枚举时间。在类加载的时候,就会计算出当前对象中进行了那些引用,在编译过程中,也会记录在特定的位置记录栈和寄存器中存在引用。因此在进行根节点枚举时,就可以判断出哪里有引用,从而提高枚举效率 
  • 安全点
    • 安全点的使用场景
      • oopmap中存储的是对象的引用地址,如果当前对象的引用还在寄存器中进行处理,没有返回到内存中被引用,这个时候如果进行GC操作,就会导致该对象直接被回收,每次进行穷举根节点的时候需要在安全地啊进行。一般的安全点是在循环跳转、异常调换等情况下。
    • 安全点的两种中断方式
      • 抢断式中断:当需要进行垃圾回收时,系统将所有线程中断,然后进行扫描,如果该线程没有达到安全点,就执行该线程直到安全点进行中断。
      • 主动式中断:在安全点进行标记,当线程进行到安全点时,就去判断是否需要进行中断,这就比抢断式中断减少了中断-重启-中断的步骤
    • 问题
      • 什么地方是安全点?
        防止oopMap记录太多指令,减少内存的耗用,因此只有在安全点的时候才会进行指令的记录
  • 安全区域
    • 安全区域解决的问题
      为了防止线程由于睡眠或者阻塞导致没有办法运动到安全点或者继续执行导致引用链的改变,因此把一部分没有改变引用链的指令视为安全区域,睡眠和阻塞可以视为安全区域,在进行GCroot遍历时不会考虑到处于安全区域的线程,如果当线程准备出安全区域时,如果GCroot遍历没有结束,则会暂停该线程。
  • 记忆集与卡表
    当存在跨代引用时,为了保证跨代引用中的对象不被清除,但是有不去检索整个老生代,因此在新生代中开辟了一段空间,用于 存储非收集集合指向收集集合
    • 字节进度
    • 对象进度
    • 卡进度:所谓的卡精度 就是只想一片区域,表示这一片需求的对象存在一个或者多个被跨代引用
    • 存在的问题(缺点):
      • 占用一定的内存空间
      • 对于跨代引用而言,一个分代进行垃圾回收,但是引用分代不会进行垃圾回收,因此出现了垃圾回收的滞后性。
  • 写屏障
    类似于环绕通知的方式,在对象复制前后对卡表进行操作
    如果多线程对卡表对于的同一地区进行操作,那么就会存在冲突,并且操作的次数也过于频繁,存在通过或者无效化的问题,因此需要进行有条件的写屏障,也就是如果当前卡表元素没有变脏,才会变脏。但是由于会进行是否变脏判断,也会消耗性能。在1.7之后,提供了-XX:+UseCondCardMark参数进行有条件的写判断   (伪共享) (QUESTION:伪共享)
  • 标记过程中存在的问题
    在查询引用链过程中,如果在这个过程不是STW后进行的,那么并发的情况下,进行引用增加和引用解除,就会出现两个问题:
    • 将本来需要进行删除的数据再次引用不能进行删除,这个情况可以接受,下次进行删除即可
    • 某个对象被引用了,但是由于此过程引用被删除,导致对象被删除,从而引用删除,需要两个必要条件:
      • 已扫描的对象对未扫描的对象进行引用
      • 即将被扫描的对象取消了对上个条件对象的引用
      • 对于的解决方法
        • 增量更新:针对第一个条件,当存在新增引用时,先记录该新增,然后在扫描完后,再对该对象进行扫描
        • 原始快照:增对第二个条件,当存在删除引用时,先记录该删除,然后扫描,对该对象再次扫描

技术分享图片技术分享图片

 

1.3 经典的垃圾回收器

技术分享图片技术分享图片

1.两个收集器之间有连线,表明它们可以搭配使用。

2.其中Serial Old作为CMS出现"Concurrent Mode Failure"失败的后备预案。

3.(红色虚线)由于维护和兼容性测试的成本,再JDK8时将Serial+CMS、ParNew+Serial Old这两个组合声明为废弃,并在JDK9完全取消了这些组合的支持,即移除。

4.(绿色虚线)JDK14中,弃用Parallel Scavenge+SerialOld组合。删除CMS垃圾回收器。

1.3.1 Serial收集器

  • 新生代的单线程收集器
  • 在进行垃圾回收器过程中,会进行STW,直到完成垃圾回收为止
  • 所有垃圾回收器中,额外消耗内存最少的
  • 采用标记复制算法
在单线程的垃圾回收器中,serial算是效率最高的垃圾回收器了。基本上200M的内存回收也只需要100ms以内
新生代与老年代使用serical与Serial Old收集器:-XX:+UseSerialGC
 

1.3.2 ParNew收集器

  • 多线程收集器,但是在单核情况下,及时使用超线程技术的情况下,用伪双线程的方式效率也没有Serial效率高
使用ParNew收集器参数:
  • -XX:+UseParNewGC: 设置新生代的收集器魏ParNew收集器
  • -XX:ParallelGCThreads:设置使用的线程数,默认与CPU核心数量一样
  • 采用标记复制算法

1.3.3 Parallel Scavenge收集器

  • 更注重吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾回收时间)
  • 自适应调节策略,自动的条件新生代老生代的大小
  • 使用标记复制算法
  • 配置参数:
    • -XX:MaxGCPauseMillis: 垃圾回收最大时间
    • -XX:GCTimeRatio:吞吐量设置,设置为19,吞吐量就是19/(1+19)
    • -XX:+UseAdaptiveSizePolicy:开启自适应调节策略

技术分享图片技术分享图片技术分享图片技术分享图片技术分享图片技术分享图片

1.3.4 Serial Old收集器

Serial给老年提供的一块收集器,其特点与serial一样,但是采用的是标记整理算法。
Serial Old是运行在Client模式下默认的老年代垃圾回收器。Serial Old在Server模式下主要有两个用途:①与新生代的Parallel Scavenge配合使用 ②作为老年代CMS收集器的后备来及手机方案。
 

1.3.5 Parallel Old收集器

 

1.3.6 CMS收集器

针对老年代的收集器,大致过程是:
    1. 初始标记:标记根节点直接相连的对象
    2. 并发标记:查询整个引用链
    3. 重新标记:使用增量更新的方式,进行再次标记
    4. 并发清理:清除标记的垃圾
值得注意的是初始标记和重新标记需要STW,CMS收集器也存在一定的弊端:
    • 由于在并发标记和并发清除过程中需要使用多线程的方式,因此需要花费部分线程,用户的可使用的线程减少,就使得吞吐量降低,默认并发使用的线程数量是
      (CUP核心数量+1)/4
    • 由于在收集过程中使用的是增量更新的方式处理并发的可达性分析,会存在在并发过程中失去引用链的对象,这些对象被称之为浮动垃圾,如果一次收集过程中浮动垃圾太多,导致并发过程中有新的对象没有空间可以使用,那么就会再次触发Full GC,因此将Serial old收集器作为CMS的备用收集器。但也会造成STW,是的体验感下降。
    • 由于CMS使用的是标记清除算法,因此会出现内存碎片,当内存不够时会触发full GC,因此CMS提供了两个参数:
      • -XX:CMSFullGC-CompactAtFullGCCollection:用于开启FullGC后的内存整理,并且该过程也是STW的
      • -XX:CMSFullGCsBefore-Compaction:设置多少次FullGC后进行内存整理

可查看链接:https://blog.csdn.net/weixin_44568697/article/details/109030617?spm=1001.2014.3001.5501

 

1.3.7 G1收集器

        一款基于局部收集的收集器,可以指定收集所需要的时间,增加吞吐量的收集器。可以建立停顿时间模型。而实现停顿模型的方式就是用过对内存进行分块形成Region而实现的。也就是说对于G1回收器没有固定的内存认定是那个分代,对于新生代,老生代等都是动态划分的。一般来说一个Region的内存大小是1-32MB之间的2的倍数。如果说对象的大小超过了Region的一半,会使用到内存较大的Humongous Region进行存储,如果大于region的内存,就会使用多个连续的Humongous Region存储。、
因此G1收集器也有以下的问题需要妥善考虑:
    1. 跨代引用问题,还是使用记忆集,但是相比于CMS只有一个记忆集,G1对于每一个Region都会维护一个记忆集,并且key时内存开始的地址指针,value是一个卡表,可以理解为这个记忆集不仅仅有我指向谁,还会记录谁指向我。
    2. 并发标记过程中的用户创建对象引用与引用解除,使用原始快照SATB的方式进行处理的,对于用户创建对象的地址分配是使用两个指针TAMS,可用的地址在这两个指针指向的内存之上即可分配。
    3. 如何建立时间停顿模型。
G1收集器大概的过程如下:
  • 初始标记:标记根节点直接相连的对象,由于对象的分配会重新指定TAMS的两个指针
  • 并发标记:查询整个引用链,并使用SATB原始快照的方式处理并发上的用户线程对象的引用与解除
  • 重新标记:使用原始快照的方式,进行再次标记
  • 筛选回收:垃圾回收器通过对每个区域进行分析,筛选出需要进行回收的区域,然后将该区域不需要删除的对象复制到一个空的Region区域中,并将原来的区域中的数据全部删除清空,由于需要移动对象,需要进行STW
    需要注意的是的复制移动的对象之后需要进行引用的移动
        在Eden区与Survivor1区数据收集方式:
    1. 查询所有的根节点已经Rset中的数据
    2. 更新Rset中的数据以保证数据的引用链的准确性
    3. 处理Rset,Rset中Eden区被引用的数据都视为隐藏标记,不进行垃圾回收
    4. 进行复制,将Eden区中和Survivor1中的不需要回收对象防区Survivor2中
    5. 引用处理,将指向原Eden区的引用指向新的位置
        在老年代的数据回收过程:
    1. GCRoot的直接引用对象遍历,STW
    2. 获取所有Survivor2d在老生代中被引用的关系,为什么需要在Young GC之前完成?
    3. 引用链的完全遍历
    4. 再次标记,使用原始快照的方式进行再次标记
    5. 独占清理:通过计算分析每一个region中的数据,获取最合适的停顿时间模型的区域进行分析 STW
        混合收集过程:
        在了解该收集器之前需要知道该收集器对内存的划分,把内存划分成2的次方大小区域,区域的大小是1M-32M,该收集器也使用了分代策略,但是分代方式更为灵活,每一个区域都可以作为新生代或者老生代。如果存在了超过区域大小一半的对象就称之为大对象,使用Humongous区域进行存储,如果对象的大小超过了区域的大小,就会使用N个连续的HUmongous区域来存储,G1中很多的操作会将Humongous区域视为老生代
        由于设置了收集时间,因此需要保证在该收集时间内完成垃圾收集,如何去保证时间的可行度了,在垃圾回收时对每一个区域都进行数据收集,记录回收时间,卡表的脏元素数量等可以测量的值,然后通过衰减平均值进行分析,各个区域数据越新越有价值,因此然后对所有的区域和设定的回收时间进行分析,计算出可以回收的区域。如果回收时间设置过小,会导致每次回收只会回收少部分的区域,使得分配器分配的频率大于回收 的频率,从而频繁的触发full GC。
        由于对内存进行分区之后,区域与区域之间跨代引用也出现了问题,因此对每一个区域进行卡表设置,相比CMS只对老生代进行卡表操作而言,G1收集器需要更多的内存空间和资源去维护每个区域的卡表。可能会多出10%~20%的内存占用。
       由于收集过程是并行的,因此对于收集过程中存在的创建分配的对象,G1采用对每个区域分配一段小的空间进行存储这些区域,使用两个指针进行标记,TAMS(top at mark start),在垃圾收集过程中默认这些对象是被隐式标记过的。
G1收集器与CMS收集器的比较:
    • G1在收集过程中不会产生内存碎片
    • 由于G1卡表机制,会耗费更多堆内存和其他资源
    • 由于G1使用的是原始快照的方式来解决并发问题下的可达性分析,需要使用写前屏障来来跟踪引用的指针发生的变化,因此过程中还会产生别的消耗。
    • 对于小内存的程序来说CMS可以很好的发挥作用,但是对于大内存来说,G1的性价比更高,这个平衡点大概是6~8G之间
    • CMS相比G1对于垃圾回收有更低的内存占用和运行负载(需要维护卡表和写屏障)
导致Full GC的原因有以下情况:
    • 复制对象时没有空间可用
    • 并发标记过程中由分配器分配的对象没有空间存储
 

1.4 低延迟收集器(暂时先不了解)

内存占用,吞吐量,延迟  不可能三角形

1.4.1 Shenandoah收集器

 

1.4.2 ZGC收集器

 
 

1.5 对象内存分配

1.5.1 对象年龄判断机制

        正对parNew收集器与serail收集器,如果对象的大小大于一个指定值,就会直接放入老生代中。
        如果Survivor区的中低于或等于某个年龄的对象占用内存综合超过了Survivor空间的一半,就会直接将对象放入老生代。
 

1.5.2 空间分配机制

如果老生代剩余空间小于新生代对象空间总和或者小于历代晋升到老生代对象的平均值,就会进行full GC
 

垃圾回收策略与经典垃圾回收器

原文:https://www.cnblogs.com/standbyme/p/15196716.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!