JVM 垃圾回收机制:探秘对象生死判定与高效回收算法

        

目录

一、JVM 对象生死判定

        1.1 引用技术算法

        1.2 可达性分型算法

二、引用

三、 回收方法区

四、垃圾回收算法

        4.1 标记-清楚算法

        4.2 标记-复制算法

        4.3 标记-整理算法


        JVM 程序计数器、虚拟机栈、本地方法栈随着线程而生,随着线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作,因此这几个区域的内存回收都具备确定性。而 Java 堆和方法区则有着显著的不确定性:一个接口的多个实现类需要的内存可能不一样,一个方法锁执行的不同条件分支所需的内存也可能不一样。

一、JVM 对象生死判定

        在 Java 堆里存放着 Java 世界中几乎所有的实例对象,垃圾收集器对堆进行回收前,第一件事就是要确定这些对象之中哪些还存活着,哪些已经死亡了。     

        1.1 引用技术算法

        在对象中添加一个计数器,每当有一个地方引用它时,计数器就加一。当引用失效时,计数器值就减一。任何时刻计数器为零的对象就是不可能被使用的。

        引用计数算法虽然额外占用了一些内存空间来进行计算,但他的原理简单,判定效率高,也有些著名的应用案例。但在 Java 领域,至少主流的 Java 虚拟机里面都没有选用引用计数法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须配合大量额外处理才能保证正确的工作,比如单纯的引用计数就很难解决循环引用的问题。

        1.2 可达性分型算法

        当前主流的商用程序语言的内存管理子系统,都是通过可达性分析(Reachablity Analysis)算法来判断存活的。这个算法的基本思路是通过一些列称为 GC ROOT 的根对象作为起始节点,从这些节点开始,根据引用关系向下搜索,搜索过程所走的路径称为引用链,如果某个对象到GC Roots 之间有任何引用链相连,或者用图论的话说是从 GC Roots 到这个对象不可达,则证明此对象是不可能再被使用了。

       在 Java 技术体系里,固定可作为 GC Roots 的对象包括以下几种:

  • 在虚拟机栈中引用的对象,比如各个线程被调用的方法中使用的参数、局部变量、临时变量等;
  • 在方法区中类静态属性引用的对象;
  • 在方法区中常量引用的对象,比如字符串常量池里的引用;
  • 在本地方法栈中 JNI 引用的对象;
  • Java 虚拟机内部的引用,基本数据类型对应的 Class 对象,一些常驻的异常对象、类加载器等;
  • 所有被同步锁 synchronized 持有的对象;
  • 反应 Java 内部情况的 JMXBean、JVM 中注册的回调、代码缓存等。

        总结,GC Roots根主要包括:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等等。

        即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”,这时候他们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在可达性分析后没有与 GC Roots 相连的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,那么虚拟机将这两种情况均视为“没有必要执行”。

        如果这个对象被判定为有必要执行 finalize() 方法,那么该对象会被放置在一个名为 F-Queue 的队列之中并在稍后有一条虚拟机建立的、低调度优先级放入 Finalizer 线程区执行它们的 finalize() 方法。finalize() 方法是对象逃脱死亡的最后一次机会,稍后收集器将对 F-Queue 中的对象进行第二次小规模的标记,如果对象在 finalize() 成功拯救自己(只要重新与引用链建立关联即可),那在第二次标记时他将被移除即将回收的集合;如果这个时候还没有逃脱,那么基本上它真的要被回收了。

二、引用

        无论是通过引用计数器算法来判断对象的引用数量,还是通过可达性分析算法来判断对象是否引用链可达,判定对象的存活条件都和“引用”离不开关系。

        JDK1.2之后,Java 将引用分为强引用(Strongly Refrence)、软引用(Soft Refrence)、弱引用(Weak Refrence)和虚引用(Phantom Refrence)4种,这四种引用强度一次减弱。

  • 强引用:是最传统的引用定义,是指在程序代码中普遍存在的引用赋值,即 A a = new A();这种引用关系。无论在何种情况下,只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 软引用:是用来描述一些还有用,但并非必须的对象,只要软引用关联着对象,在系统将要发生内存溢出异常前,会把这些对象列进垃圾回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。JDK1.2 之后提供了SoftRefrence类来实现软引用。遇到 GC 先判断内存够不够,不够才会被回收。

        软引用非常适合做缓存,空间够用时不会回收,空间不够用了才被回收掉。

  • 弱引用:用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作时,无论当前内存是否足够,都会回收掉被弱引用关联的对象。在 JDK1.2 之后提供了 WeakRefrence 类来实现弱引用。只要遇到 GC 就会被回收。
  • 虚引用:也称为幽灵引用或者幻影引用,他是最弱的一种引用关系。一个对象有虚引用的存在,完全不会对其生存构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是为了在这个对象被收集器回收时收到一个系统通知。在 JDK1.2 之后提供了 PhantomRefrence 类来实现虚引用。
ReferenceQueue<A> queue = new ReferenceQueue<>();
PhantomReference<A> phantom = new PhantomReference<>(new A(), queue);

        虚引用需要用到一个队列 queue,当虚引用的对象被回收的时候信息会被填到队列里。当对象被回收时知道它被回收了,可以收到一个通知。

        虚引用可以用来回收堆外内存。当对象被回收时,通过 queue 可以检测到,然后清理掉堆外内存。比如回收 NIO 的直接内存。

三、 回收方法区

        方法区的回收成果比较低。方法区回收主要回收废弃的常量和不再被使用的类型。回收废弃的常量比较好理解,没有任何对象引用这个常量了,且虚拟机没有其他地方引用这个字面常量。

        回收不再被使用的类型条件比较苛刻,需要同时满足三个条件:

  • 该类型所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非经过精心设计的可替换类加载起的场景,如OSGI、JSP 的重加载,否则很难达到
  • 该类对应的 Java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

        在使用反射、动态代理、CGLib 等字节码框架等场景中,通常都需要 Java 虚拟机具备类型卸载能力,以保证不会对方法区造成过大的内存压力。

四、垃圾回收算法

        当前商业虚拟机的垃圾收集器,大多数都遵循“分代收集”的理论。常用垃圾收集器的设计原则:收集器将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

        显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把他们集中在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低的代价回收较大的空间;如果剩下的都是难以消亡的对象,把他们集中在一起,虚拟机便可以用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

        在 Java 堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或某些部分的区域,因而才有了“Minor GC/Young Gc、Major Gc/Old Gc、Full GC”这些类型的划分。也才能够针对不同区域安排与存储对象的存亡特征相匹配的垃圾收集算法,因而发展出了“标记-复制算法、标记-清除算法、标记-整理算法”。Java 堆至少会划分为新生代(Young Generation)和老年代(Old Generation)两个区域。

        4.1 标记-清楚算法

        算法分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

        标记清楚算法存在如下缺点:

  • 执行效率不稳定,如果 Java 堆中存在大量对象,而其中大部分是需要回收的,这时就需要进行大量的标记和清除动作,导致标记和清除随着对象数量的增多而效率降低
  • 存在内存碎片化,标记、清楚后会存在大量不连续的内存碎片,空间碎片太多可能会导致以后再程序过程中需要分配较大对象时无法找到连续的内存空间而不得不提前触发垃圾收集

        4.2 标记-复制算法

        标记-复制算法常被称为复制算法。为了解决标记-清除算法面对大量可回收对象执行效率低的问题,提出了一种半区复制的垃圾收集算法,将可用内存分为大小相等的两块,每次只是用其中的一块。当这一块内存用完了,就将还存活的对象复制到另一块上面,然后在把已使用过的内存空间一次性清理掉。如果大多数对象是存活的,这种算法将产生大量的内存复制的开销。但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时就不用考虑空间碎片的复杂情况,只要移动栈顶指针,按顺序分配即可。

        标记-复制算法的缺点是:将可用内存缩小为原来的一半,空间浪费太多。

        后来又提出一种新的回收方式。将新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只是用Eden和其中的一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上然后直接清掉Eden和那块Survicor空间。HostSpot默认Eden和Survovor比例为8:1。

        4.3 标记-整理算法

        标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率降低。提出了标记-整理算法,其中标记过程一样,但后续步骤不同,而是让存活的对象向内存空间的一端移动,然后直接清理掉边界以外的内存。

        针对老年代对象的特征,提出了一种针对性的标记-整理算法。其中的标记过程依然与标记-清除算法中的标记一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间的一端移动,然后直接清理掉边界以外的内存。

        标记清除与标记整理算法的本质差异是前者是一种非移动式的回收算法,而后者是移动式的。

往期经典推荐

JVM内存模型深度解读-CSDN博客

Synchronized同步锁的全方位剖析与实战运用-CSDN博客

Spring Cloud全方位解读——构建微服务架构的利器-CSDN博客

你真的了解Tomcat一键启停吗?-CSDN博客

Redis缓存危机大揭秘:雪崩、击穿与穿透——从理论到实战防御策略_redis缓存雪崩、穿透以及技术预防-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

选数异或 (AcWing 4645)

题目链接: https://www.acwing.com/problem/content/description/4648/ 题目描述: 评价: 这道题感觉还是蛮有意思的&#xff0c;难度适中&#xff0c;而且有一定的思维含量&#xff0c;值得反复品味。 思路: 首先我们定义一个数组g[N], 其中的每个元素g[i] 表示在所有 i<j…

Java中的JVM加载机制。

在Java中&#xff0c;JVM&#xff08;Java虚拟机&#xff09;的类加载机制是Java平台的核心组件之一。它负责在运行时动态地将类的.class文件加载到内存中&#xff0c;并为这些类创建对应的java.lang.Class对象。这个机制确保了类的正确性和安全性&#xff0c;并为Java的“一次…

flink重温笔记(十八): flinkSQL 顶层 API ——实时数据Table化(涵盖全面实用的 API )

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 18 天啦&#xff01;很多小伙伴私信说&#xff0c;自己只会SQL语法来编写flinkSQL&#xff0c;如何使用代码来操作呢&#xff1f;因为工作中都是要用到代码编写的。还有小伙伴说&#xff0c;想要实现表是动态变化的&#…

STM32通信协议

STM32通信协议 STM32通信协议 STM32通信协议一、通信相关概念二、通信协议引脚作用三、通信方式四、采样方式五、电平信号六、通信对象 一、通信相关概念 通信接口 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 通信协议&#xff1a;制定…

Python 全栈体系【四阶】(十六)

第五章 深度学习 一、基本理论 2. 深度神经网络结构 2.1 感知机 2.1.1 生物神经元 感知机&#xff08;Perceptron&#xff09;&#xff0c;又称人工神经元&#xff08;Artificial neuron&#xff09;&#xff0c;它是生物神经元在计算机中的模拟。下图是一个生物神经元示意…

JavaScript高级(十六)---Iterator迭代器/Generator生成器

什么是迭代器&#xff1f; 迭代器是一种特殊的对象&#xff0c;这个对象需要符合迭代协议&#xff08;iterator protocol&#xff09;&#xff0c;这个对象具有以下特点。 该对象有一个特定的next方法 const obj { next() { } } const obj { …

FFmpeg-- c++实现:音频流aac和视频流h264封装

文章目录 流程api核心代码muxer.hmuxer.cpp aac 和 h264 封装为视频流&#xff0c;封装为c的Muxter类 流程 分配视频文件上下文 int Init(const char *url); 创建流&#xff0c;赋值给视频的音频流和视频流 int AddStream(AVCodecContext *codec_ctx); 写视频流的head int Se…

wayland(xdg_wm_base) + egl + opengles 使用 Assimp 加载带光照信息的材质文件Mtl 实现光照贴图的最简实例(十七)

文章目录 前言一、3d 立方体 model 属性相关文件1. cube1.obj2. cube1.Mtl3. 纹理图片 cordeBouee4.jpg二、实现光照贴图的效果1. 依赖库和头文件1.1 assimp1.2 stb_image.h2. egl_wayland_obj_cube1.cpp3. Matrix.h 和 Matrix.cpp4. xdg-shell-client-protocol.h 和 xdg-shell…

推免保研夏令营/预推免面试记录—北航网安

目录 0x00简述 0x01 面试经历 0x02 相关资料下载 0x00简述 没有夏令营,直接就是预推免,门槛基本等同于华五,或者说略弱于华五。招生规模较小,而且感觉导师方向一半是密码学,一半是那种类似于电信工的偏硬件方面。该校导师教授可以带两个学生,而副教授可以带一个。考…

小程序跨端组件库 Mpx-cube-ui 开源:助力高效业务开发与主题定制

Mpx-cube-ui 是一款基于 Mpx 小程序框架的移动端基础组件库&#xff0c;一份源码可以跨端输出所有小程序平台及 Web&#xff0c;同时具备良好的拓展能力和可定制化的能力来帮助你快速构建 Mpx 应用项目。 Mpx-cube-ui 提供了灵活配置的主题定制能力&#xff0c;在组件设计开发阶…

网络工程师练习题4

网络工程师 通过拨号远程配置Cisco路由器时&#xff0c;应使用的接口是&#xff08;&#xff09;。 A.AUXB.ConsoleC. EthernetD.VTY 【答案】A 【解析】使用AUX端口连接一台Modem&#xff0c;通过拨号远程配置路由器。 在Cisco路由器上配置RIPv1路由协议&#xff0c;参与…

【计算机视觉】Gaussian Splatting源码解读补充

本文旨在补充gwpscut创作的博文学习笔记之——3D Gaussian Splatting源码解读。 Gaussian Splatting Github地址&#xff1a;https://github.com/graphdeco-inria/gaussian-splatting 论文地址&#xff1a;https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gauss…

Minio的安装和Java使用示例

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Minio 是个基于 Go…

三段提交的理解

三阶段提交是在二阶段提交上的改进版本&#xff0c;3PC 最关键要解决的就是协调者和参与者同时挂掉的问题&#xff0c;所以3PC把2PC的准备阶段再次一分为二&#xff0c;这样三阶段提交。 处理流程如下 &#xff1a; 阶段一 协调者向所有参与者发出包含事务内容的 canCommit …

【MySQL】知识点 + 1

# &#xff08;1&#xff09;查询当前日期、当前时间以及到2022年1月1日还有多少天&#xff0c;然后通过mysql命令执行命令。 select curdate() AS 当前日期,curtime() AS 当前时间,datediff(2022-01-01, curdate()) AS 距离2022年1月1日还有天数;# &#xff08;2&#xff09;利…

知识点拓展——rsync同步命令的应用

Linux目录下有100万个文件&#xff0c;如何快速删除 当然了&#xff0c;首先我们想到的一定是rm -f删除&#xff0c;但是rm -f删除的话 会报错&#xff0c;提示无法删除&#xff0c;因为太长了&#xff0c;rm命令无法删除 [rootlocalhost opt]#mkdir files [rootlocalhost o…

Windows10蓝屏报错 inaccess boot device

当Windows 10操作系统启动时出现蓝屏错误“INACCESSIBLE_BOOT_DEVICE”时&#xff0c;这通常表明系统在尝试启动过程中无法识别或访问引导设备上的基本系统文件。这个问题可能是由以下几个原因引起的&#xff1a; 硬件兼容性问题&#xff1a; 硬盘控制器驱动不正确或丢失&#…

Go语言学习04~05 函数和面向对象编程

Go语言学习04-函数 函数是一等公民 <font color"Blue">与其他主要编程语言的差异</font> 可以有多个返回值所有参数都是值传递: slice, map, channel 会有传引用的错觉函数可以作为变量的值函数可以作为参数和返回值 学习函数式编程 可变参数 func s…

三种方式,浅谈 Cocos Creator 的动画添加

前言 虽然 Cocos 的官方文档对动画系统做了较详细的介绍&#xff0c;但是对于刚接触的同学&#xff08;比如我&#xff09;来说还是不太友好。尽管如此&#xff0c;我就按文档加社区帖子一起实践了一下。为了方便忘记后能快速捡起&#xff0c;所以就用我的方式结合使用场景&am…

python内存管理和垃圾回收一文详解(基于c语言源码底层逻辑)

引用计数器 首先我们大概回忆一下C语言中的环状双向链表&#xff0c;如图&#xff0c;在双向链表中对于一个结点来说会有前驱和后继&#xff1a; C语言中基本的定义方式如下&#xff1a; typedef struct {ElemType data; // 数据域Lnode* prior; // 前驱指针域Lnode* next;…