JVM(三)—— 垃圾收集算法

垃圾收集算法

3.1 概述

  • 垃圾收集器(GC)是比Java语言更早的出现。
  • 对于Java虚拟机运行时数据区的程序计数器、虚拟机栈和本地方法栈是线程私有的,也就是说是随着线程的创建而创建,随线程的销毁而销毁,所以它们属于自动垃圾回收。对于Java堆和方法区(非堆,老年代)是线程共享的,随着虚拟机的启动而创建,所以需要进行垃圾回收。

3.2 对象的死亡

  • 判断对象是否还活着可以使用引用计数算法和可达性分析算法。

3.2.1 引用计数算法

  • 原理:每创建一个对象,就给这个对象关联一个计数器,如果对象被引用一次,计数器就加1,对象引用失效,计数器就减1,当计数器的值为0时,就代表当前对象是不可用的。(注意此时对象并不是死了,还需要两次标记过程)
  • 缺点:如果两个对象循环引用,那么此种方法就失效了。

3.2.2 可达性分析算法

  • 原理:从一个称为GC Roots的对象作为起始点,然后向下进行搜索,搜索经过的路径叫做引用链,当一个对象到GC Roots没有任何引用链相连,则此对象是不可用的。(注意此时对象并不是死了,还需要两次标记过程)
  • 可以作为GC Roots对象:
    • 虚拟机栈(局部变量表)reference类型引用的对象。
    • 方法区中类变量应用的对象(static String str = new String())
    • 方法区中常量引用的对象(String str = new String(“123”))
    • 本地方法栈JNI(Native方法)引用的对象

3.2.3 四种引用

  • 传统的引用定义:对于reference类型的值代表的是一块内存的起始地址,那么这块内存就代表一个引用。
  • JDK1.2之后引入了四种引用:强引用、软引用、弱引用、虚引用。
    • 强引用:在Java程序中最普遍,形如“Object obj = new Object()”这种就是强引用,只要是强引用还在,垃圾收集器就不会对其回收。
    • 软引用:软引用是用来描述一些有用但非必需的对象。对于软引用关联着的对象,在内存溢出将要发生之前,对此类对象进行第二次垃圾回收。如果此时内存还是不够,才会抛出内存溢出异常。通过SoftReference类来实现。
    • 弱引用:弱引用也是用来描述一些非必需的对象。对于弱引用关联着的对象,在第二次垃圾回收的时候对其进行回收,不管内存够不够(会不会发生内存溢出)。通过WeakReference类来实现。
    • 虚引用:又叫做幽灵引用或者是幻影引用。它是和对象关联最弱的一种引用,并不能通过此类引用创建对象实例,唯一的作用是标记对象在对象被回收的时候收到一个系统通知。可以通过PhantomRefenrence类来实现。

3.2.4 对象死亡的两次标记过程

  • 之前的引用计数算法和可达性分析算法判断对象是否还可用,要判断对象是否死亡还需要经过两次标记过程。
  • 第一次标记:
    • 如果对象对象在可达性分析之后没有到达GC Roots的引用链,那么将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize方法。当对象没有覆盖finalize方法或者是虚拟机的finalize方法已经执行,那么对象将没有必要执行finalize方法。对象死亡。
  • 第二次标记:
    • 如果判定对象有必要执行finalize方法,那么这个对象会放置在一个F-Queue队列中,并在稍后虚拟机自动创建的、低优先级的Finalizer线程去执行它。finalize方法是对象逃脱标记的唯一机会,稍后GC 将对F-Queue队列中的对象进行二次小规模的标记,如果此时对象想拯救自己——只需要重新与引用链中的任何一个对象建立关联即可。比如把自己(this)赋值给类变量或者对象的成员变量。此时将会移除标记队列。对象成功逃脱。否则对象死亡。
  • 注意:
    • 一个对象的finalize方法只会执行一次
    • 通过筛选F-Queue队列中的对象,让对象进行自我拯救的方法不鼓励。

3.2.5 回收方法区

  • 方法区又称之为永久代,在Java虚拟机规范中说过可以不要求虚拟机在方法区实现垃圾收集,并且在方法区进行垃圾收集的性价比比较低,以为对方法区的垃圾收集主要是针对废弃的常量和无用的类。对堆的垃圾收集一般可以回收70-95%的空间,但是对方法区的效率很低。
  • 废弃常量的回收:
    • 判断方法:回收废弃常量与回收Java堆中的对象非常相似。
      • 当前系统中没有一个对象引用了常量池中的常量。
  • 无用的类的回收:
    • 判断方法:
      • 1、该类的所有实例对象都已经回收,在Java堆中此时不存在任何该类的对象实例
      • 2、加载该类的ClassLoader已经被回收
      • 3、该类对应的java.lang.Class对象没有任何在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    • 在JVM中,对象没用了就一定被回收,满足上面三个条件的类说明是无用的类,但是只是说明该类是可以被回收的。另外还可用通过参数配置来决定要不要对该类进行回收。
    • 类回收的相关参数:
      • -Xnoclassgc:控制要不要对类回收
      • -verbose:class以及-XX:TraceClassLoading:可以在product版虚拟机中查看类加载的消息
      • -verbose:class以及-XX:TraceClassUnLoading:可以在FastDebug版的虚拟机中查看类卸载的消息
  • 虚拟机在何时需要具备类卸载功能?
    • 在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,来保证方法区(永久代)不会溢出。

3.3 垃圾收集算法

  • 在虚拟机中有多种不同的垃圾收集算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法

3.3.1 标记-清除算法

  • 原理:
    • 该算法分为“标记”和“清除”两个过程,标记的过程就是判断对象是否无用的两次标记过程。清除的过程就是释放无用对象占用的内存。
  • 缺点:
    • 1、效率问题,标记和清除的效率都不高
    • 2、空间问题,标记完直接清除会产生很多的内存碎片,也就是很多的内存不连续空间,如果接下来在程序运行期间产生大的对象内存分配的时候,出现找不到足够大的内存空间,这样会触发二次垃圾回收。
      在这里插入图片描述

3.3.2 复制算法

  • 原理:
    • 复制算法会把内存分成两块等大小的区域,每次使用其中的一块区域,当这一块区域内存使用完了,就会把可用的对象复制到另一块内存,然后一次性清理掉这一块内存,解决了标记-清除算法的内存碎片问题。
  • 缺点:
    • 将内存一分为二,产生了内存缩小的问题。
  • 应用:
    • 复制算法主要是当前商业虚拟机针对回收新生代。
    • 因为新生代的98%都是属于朝生夕死,此时不需要对内存按照1:1的比例划分,而是将内存划分为Eden空间和From Survivor和To Survivor空间,每次只使用Eden空间和其中的一块Survivor空间,当回收的时候,由于大部分的新生代对象都无用了,所有将存活的对象复制到另一块的Survivor空间。然后清理掉Eden空间和刚才使用的Survivor空间。如果Survivor空间不够用,会依赖其他内存(老年代)进行分配担保。此时这些存活的对象会直接进入到老年代。
    • 在HotSpot虚拟机中三者的比例大小为8:1:1。
  • 缺点:
    • 如果对象的存活率高,需要大批的对象需要复制,不适合。
      在这里插入图片描述

3.3.3 标记-整理算法

  • 原理:
    • 标记的过程和标记-清除算法的标记过程一样,然后会把存活的对象依次依次向一端移动,最后清理掉端外的内存。
      在这里插入图片描述

3.3.4 分代收集算法

  • 原理:
    • 分代收集算法不是一种新的算法,它只是把Java堆中的对象分成为新生代和老年代。针对新生代复制算法进行回收。针对老年代使用标记-清除或标记-整理算法。

3.4 HotSpot的算法实现

这里是选用了HotSpot虚拟机实现垃圾回收算法。

3.4.1 枚举根节点

  • 为什么要枚举根节点?
    • 垃圾回收会有标记过程,第一次标记过程会通过可达性分析判断对象是否有到GC Roots的引用链,所以首先要堆GC Roots根节点进行枚举。
    • 小注(GC Roots的类型)
      • 全局性引用(常量和类静态变量引用的对象)
      • 执行上下文(栈帧中局部变量表中的reference类型引用的对象)
      • 本地JNI引用的对象
  • 枚举根节点为什么要停顿?
    • 可达性分析执行对时间的敏感性体现在GC停顿上,因为可达性分析工作需要在一个能保证一致性(见下)的快照中进行,此时GC会停止所有的Java线程。
    • 一致性:整个可达性分析期间的整个系统就好像是停顿在某个时间点,不可以出现分析过程中对象引用关系还在发生变化的情况,所以导致了GC会停顿所有的Java线程。
  • 枚举根节点时间很长?
    • 当前主流的JVM都是使用的准确式GC,所以系统停顿下来后,不需要检查所有的引用,虚拟机是可以知道哪些地方存储着引用。
    • 在HotSpot虚拟机中通过OopMap的数据结构实现的,在类加载完成之后,会把每个对象多少偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定位置(安全点)记录下栈和寄存器中哪些位置是引用。GC扫描时就可以直接知道哪里是引用。

3.4.2 安全点

  • 枚举根节点给每条指令使用OopMap结构会增加GC的内存空间?
    • HotSpot虚拟机并没有给每条指令都生成一个OopMap,只是在安全点才会生成。
    • 安全点(Safe point):
      • 定义:程序执行过程中,遇到这些点才会停下来执行GC过程。
      • 安全点的选取规则:能让程序长时间执行的地方——方法调用、循环跳转和异常跳转。
    • 安全点如何实现停下来所有的线程?
      • 抢先式中断:不需要线程代码主动去配合,当GC发生时,让所有的线程中断,没有到达安全点的线程就让恢复该线程并执行到安全点。
      • 主动式中断:当GC需要中断线程时,不直接对线程操作,仅仅设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起,轮询标志和安全点时重合的。
        • 过程:首先是设置一个标志位,线程执行到标志位处,如果标志位为真,那么线程会产生一个自陷异常信号,在预先注册的异常处理器中暂停线程等待,这样就会触发线程中断。

3.4.3 安全区域

  • 为什么会引入安全区域?
    • 安全点使得程序在执行过程中保证了进入GC过程,但是如果是程序在不执行的时候,会通过设置安全区域进入GC过程。
    • 程序不执行:就是处理器没有给程序分配CPU时间,比如线程进入sleep和blocked状态
  • 安全区域:
    • 定义:在一段代码中,引用关系不发生变化。在这个区域的任何一个地方都是安全的。
    • 在执行到安全区域的代码,首先标识了自己进入了安全区域,当在这段时间里,如果JVM发起GC,就不用管标识自己为safe region状态的线程了,在线程离开safe region时,会检查系统是否已经完成了根节点枚举,如果完成了线程就继续执行,如果没有完成就必须等待直到收到可以安全离开safe region的信号为止。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值