JVM第四篇:垃圾回收算法

什么是垃圾?

垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。

JVM实现自动管理内存,无需开发人员手动参与内存的分配与回收,这样就降低内存泄露和内存溢出风险,可以让程序员专注于业务开发。

  • 频繁收集Young区
  • 较少收集Old区
  • 基本不动Perm区

对象存活判断

在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC时,才会释放掉其所占用的空间,因此这个过程称之为垃圾标记阶段。


判断对象存活有两种算法:① 引用计数算法 ② 可达性分析算法

引用计数算法

引用计数算法比较简单,对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器值加1;当引用失效时,引用计数器就减1。只要对象的引用计数器值为0,即表示对象A不可能再被使用,可进行回收。

优点

  1. 实现简单,垃圾对象便于辨识。
  2. 判定效率高,回收没有延迟性。

缺点

  1. 需要为每个对象分配单独的字段存储计数器,增加了存储空间的开销。
  2. 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销。
  3. 存在致命问题,无法处理循环引用的情况。所以Java的垃圾回收器不使用此算法

可达性分析算法

可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。

优点:

  1. 有效的解决了引用计数法中循环引用的问题,防止内存泄露的发生。

在这里插入图片描述
GC Roots包括以下元素:

  1. 虚拟机栈引用的对象。比如:各个线程被调用的方法中使用到的参数、局部变量等。
  2. 本地方法栈内JNI(本地方法)引用的对象。
  3. 方法区中常量引用的对象。比如:字符串常量池(StringTable)里的引用。
  4. 所有被同步锁Synchronized持有的对象。
  5. Java虚拟机内部的引用。基本数据类型对象的Class对象,一些常驻的异常对象(如NullPointException、OutOfMemoryError)、系统类加载器。
  6. 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

对象的finalization机制

Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。


当垃圾回收器发现没有引用指向一个对象,即垃圾回收对象之前总会先调用这个对象的finalize()方法。
finalize()方法允许子类重写,用于对象被回收资源释放,通常这个方法进行一些资源释放和清理工作,比如关闭文件、套接字和数据库连接等。
永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用,理由如下:
  1. 在finalize()时,可能会导致对象复活。
  2. finalize()方法的执行时间是没有保障的,它完全由GC线程决定的,极端情况下,若不发生GC,则finalize()方法没有机会执行。
  3. 一个糟糕的finalize()方法会严重影响性能。

哪些对象一定会被回收?

如果从GC Roots都无法访问到某个对象,说明对象已经不再使用。一般来说,此对象需要被回收。但事实上,也并非是“非死不可”。这时候处于“缓刑”阶段。一个无法触及的对象可能再某个条件下“复活”自己。所以,定义虚拟机中对象可能有三种状态:

  1. 可触及的:从根节点开始,可以到达这个对象。
  2. 可复活的:对象的所有引用都被释放,但是对象可能再finalize()中复活。
  3. 不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()方法只会被调用一次。

具体过程
判断一个对象是否可回收,至少要经历两次标记过程:

  1. 如果对象A到GC Roots没有引用链,则进行第一次标记。
  2. 进行筛选,判断此对象是否有必要执行finalize()方法
    ① 如果对象A没有重写finalize()方法或finalize()方法已被虚拟机调用过,则虚拟机视为“没有必要执行”,对象A被判断不可触及
    ② 如果对象A重写了finalize()方法且还未被执行过,那么对象A会被插入到F-Queue队列中,由一个虚拟机自动创建的,低优先级的finalize线程触发其finalize()方法执行。
    ③ finalize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记。如果对象A在finalize()方法中与引用链上的任何一个对象建立联系,那么第二次标记时,对象A会被移除“即将回收”队列,复活成功。那些没有建立联系的对象,则直接变成不可触及状态。

JProfiler、MAT工具

三种垃圾算法

标记-清除算法

当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项为标记,第二项为清除。

  1. 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
  2. 清除:Collector对堆内存从头到尾进行线性遍历,如果被发现某个对象在其Header中没有标记为可达对象,则将其回收。

缺点:

  1. 效率不高。
  2. GC是需要停掉整个应用程序,导致用户体验差。
  3. 清理出的空间内存是不连续的,产生内存碎片,需要维护一个空闲列表。

何为清除?
这里的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。

在这里插入图片描述


标记-复制算法(空闲分配列表分配对象)

将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。

优点:

  1. 没有标记和清除过程,实现简单、运行高效。
  2. 复制过去保证空间的连续性,不会出现“碎片”问题。

缺点:

  1. 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  2. 对于G1这种分析成为大量的region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或时间开销也不小。特别是存活对象过多时,复制算法不理想,复制的存活对象过多。

在这里插入图片描述


标记-整理算法(指针碰撞分配内存)

如果标记-清除算法应用于老年代中,不进执行效率低,而且会产生内存碎片,所以JVM的设计者使用标记-整理算法

标记-清除算法与标记-整理算法的本质
标记-清除算法是一种非移动式的回收算法,标记-整理算法是移动式的,标记的存活对象会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需持有一个内存的起止地址即可,这样比维护一个空闲列表显然少了许多开销。

指针碰撞
如果内存空间规律有序分布,即已用和未用内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲位置上,这种分配方式叫做指针碰撞。

优点:

  1. 消除标记-清除算法中,内存区域分散的特点,我们需要给新对象分配内存时,JVM只需持有一个内存的起始地址。
  2. 消除标记-复制算法中,内存减半的代价。

缺点:

  1. 从效率上说,标记-整理算法要低于标记-复制算法。
  2. 移动对象的同时,如果此对象被其它对象引用,还需要调整引用的地址。
  3. 移动过程中,需要全程暂停用户应用程序,即STW(Stop The World)。
    在这里插入图片描述

分代收集算法

前面的三种算法中,他们都有各自的优势,都无法相互替代,所以分代收集算法应运而生。

因为不同对象的生命周期是不同的,因此,不同的生命周期的对象可以采用不同的收集方式,以提高回收效率。

年轻代 ——> 标记-复制算法
老年代 ——> 标记-清除算法 或者 标记-清除算法 + 标记-整理算法

在这里插入图片描述


增量收集算法

上述的所有算法中,在垃圾回收过程中,应用程序都会处于一种STW的状态。在STW状态下,应用程序的所有线程都会被挂起,暂停一切的正常工作,等待垃圾回收的完成。如果垃圾回收时间太长,应用程序就会被挂起很久,将严重影响用户体验或者系统的稳定性。为了解决这个问题,产生了增量收集算法。

增量收集算法的基本思想
如果一次性将所有垃圾进行处理,需要 造成系统长时间停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复直到垃圾收集完成。

缺点:

  1. 不断切换线程,造成系统吞吐量下降。

分区算法

相同条件下,堆空间越大,一次GC的时间就越长,那么GC产生的停顿时间就越长。为了更好的控制GC产生的停顿时间,将一大块的内存区域分割成多个小块,根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。

分区算法将整个堆空间划分成连续的不同小区间region,每个小区间都独立使用,独立回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值