jvm垃圾回收算法

如何判断一个对象是否需要回收

  • 1 引用计数法
  • 实时记录每一个对象被引用的次数,有一个对象a,任何对象对a的引用,那么对象a的引用计数器+1.如果对象a的引用计数器为0说明对象a没有被引用,那么就可以回收对象a。
  • 这种方法实时性高,无需等到内存不够的时候才开始回收,只要引用计数器为0就可以直接回收。而且更新计数器时只会影响该对象,不会扫描其他对象。
  • 此种方法最大的缺点是无法解决循环引用问题。当两个垃圾对象互相引用,那么这两个对象无法判定为垃圾。jvm中没有使用这种方法。
  • 2 可达性分析算法
  • 通过一系列称为根的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为为引用链。当一个对象到根对象不存在任何引用链时,即从根对象到这个对象不可达,则证明此对象是不可用的。
  • 根对象就是肯定不会作为垃圾的对象,包括 方法区中常量和静态属性引用的对象 虚拟机栈中本地变量表引用的对象等。
  • 判断一个对象是否是垃圾,首先对这个对象进行可达性分析,如果不可达,判断当前对象是否重写finalize方法。如果重写了finalize方法并且finalize方法没有执行过,那么该对象会放到一个虚引用队列,由一个低优先级线程执行finalize方法。稍后一段时间会对虚引用队列的对象进行第二次标记。
  • 对象在执行finalize方法时也可能重新恢复引用关系,即在执行finalize方法时复活了,此时在第二次标记时会将复活对象移除即将回收的集合。
  • 如果没有重写flnalize方法或finalize方法执行过了,那么会进行垃圾回收。
  • 一个对象不一定只有引用和没有引用两种极端状态,Java提供了 强引用 软引用 弱引用 虚引用四种级别,描述更多对象的其他状态。
    在这里插入图片描述

方法区的回收

  • 对方法区的回收主要是类的卸载,将加载的没有用的类进行回收。
  • 判断一个对象是否是垃圾对象比较简单,而判断一个类是否是无用的类 条件苛刻很多,需要满足以下三点:
    1 该类的所有实例对象被回收。
    2 该类对应的类加载器被回收。
    3 该类对应的class对象没有任何地方引用它。

常见垃圾收集算法

  • 标记清除法
    此种算法分两个阶段,分别是标记和清除。首先从根节点开始标记引用的对象,然后未被标记引用的对象就是垃圾对象,可以进行清除。
    最大缺点是清理出来的内存是不连贯的,存在内存碎片化的问题。
    在这里插入图片描述

  • 标记整理算法
    标记整理算法是对标记清除算法的改进,标记的过程一样,但当进行清除操作时,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决碎片化问题。
    这样解决了标记清除算法碎片化的问题,但是在移动存活对象的过程有开销。适用于垃圾对象较少的情况。
    在这里插入图片描述

  • 复制算法
    将原有的内存空间一分为二,每次只使用其中的一块,在垃圾回收时将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾回收。
    如图,绿色表示存活的对象占用的内存,灰色表示需要回收的内存,我就把第一个内存空间中绿色的对象内存复制到第二个内存空间。
    在这里插入图片描述
    然后将第一个内存空间的内存清空,并交换两个内存空间的角色。
    在这里插入图片描述
    如果内存中存活对象比较少,需要复制的对象比较少,此时适合使用该方式效率比较高。但是需要占用双倍内存。

  • 分代算法
    垃圾回收算法,每一种算法都有它的优点和缺点,分代算法就是根据回收对象的特点选择不同的算法。
    比如年轻代存活对象比例小使用复制算法,老年代存活对象比例高 使用标记清除法或标记压缩法。
    在这里插入图片描述
    刚创建的对象一般会存放在eden区,如果eden区满了就会触发一次minor GC,将eden区的垃圾对象回收并将eden区存活对象复制到幸存区to中,并且存活的对象寿命+1然后交换幸存区from和to的位置,保证from一直指向有存活对象区域。
    如此循环往复,每次minorGC都会采用复制算法进行垃圾回收并将存活对象寿命+1,直到存活对象寿命达到一个阈值(默认15),会将存活对象放到老年代中。另外如果幸存区内存紧张会导致晋升阈值降低,很多存活时间短的对象晋升到老年代。
    当创建一个对象在eden区,幸存区,老年代都放不下时说明整个堆空间内存不足,此时就会触发一次full GC,full GC会对堆内存新生代 老年代都会进行清理,
    如果新创建对象很大,我们可能会直接将其分配到老年代。
    动态对象年龄判定:假如servivor空间中年龄为a的对象占用空间总和大于survivor空间一半,那么年龄大于等于a的对象会直接进入老年代,无需等到指定阈值。这样可以避免很多对象在survivor来回复制拷贝。
    空间分配担保:在发生miniorGC时,首先检测之前每次晋升到老年代的平均大小是否大于老年代剩余空间大小,如果大于直接进行一次Full GC。

对象进入老年代的几种情况

  • 大对象直接进入老年代
  • 年龄判定:新生代对象的年龄计数器达到指定阈值,进入老年代
  • 空间分配担保:新生成的对象如果在eden区和survivor区没有空间存放时会进入老年代

新生代和老年代的区别
新生代是绝大多数对象出生的地方,存储几乎所有的新生对象,年老代存放的是survivor区熬过来的对象,不会轻易死亡,一些比较大的对象也会直接存放在老年代。
新生代的对象创建销毁比较频繁,所以新生代gc执行的非常频繁。老年代的对象不容易销毁,所以老年代gc执行没有那么频繁。
新生代对象回收代价小,回收频率高,老年代对象回收代价大,回收频率低。

新生代空间大小的权衡

  • 当新生代的内存空间比较小时,创建新对象存放在新生代时更容易发生mintorGC,造成mintorGC频率比较高,进而造成用户线程停顿时间比较长,加大新生代内存可以让mintorGC频率变低。
  • 但是新生代内存也不是越大越好,因为新生代内存越大,相对来说老年代内存空间就会变小,这样老年代内存不足概率变高,从而造成发生fullGC频率变高,fullGC代价要比mintorGC代价高得多,这样反而得不偿失。
  • 所以新生代内存空间不宜过大也不能过小,一般建议在四分之一到二分之一之间。
  • 理想情况下要求新生代能够容纳 一次请求响应数据 * 并发量

幸存区大小的权衡

  • 幸存区要去需要能够保留 当前活跃对象 + 需要晋升的对象
  • 如果幸存区较小,一旦内存不够就会造成本来存活时间短的对象提前晋升到老年代,这样它要等到老年代内存不足触发fullGC时才能回收。这样变相延长对象生存时间,白白占用了老年代的空间。
  • 如果幸存区太大,一些长时间存活的对象不能及早晋升到老年代,就会停留在幸存区复制来复制去,加重了复制算法的负担。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值