Java高阶私房菜:JVM垃圾回收机制及算法原理探究

news2024/11/17 11:30:01

目录

垃圾回收机制

什么是垃圾回收机制

JVM的自动垃圾回收机制

垃圾回收机制的关键知识点

初步了解判断方法-引用计数法

GCRoot和可达性分析算法

什么是可达性分析算法

什么是GC Root

对象回收的关键知识点

标记对象可回收就一定会被回收吗?

可达性分析算法为什么可以解决循环引用造成的内存泄漏问题?

​编辑

垃圾回收算法

标记-清除算法原理

标记-复制算法原理

标记-整理-压缩算法原理

几种算法对比


        本文主要讲解了什么是垃圾回收机制,进而了解它的底层架构原理,到核心的几种垃圾回收算法,逐步延申到它的应用场景和启发。

垃圾回收机制

什么是垃圾回收机制

        垃圾回收机制(Garbage Collection, 简称GC) 指自动管理动态分配的内存空间的机制,自动回收不再使用的内存,以避免内存泄漏和内存溢出的问题。

        最早是在1960年代提出的,程序员需要手动管理内存的分配和释放,这往往会导致内存泄漏和内存溢出等问题,同时也增加了程序员的工作量,特别是C++/C语言开发的时候,Java语言是最早实现垃圾回收机制的语言之一,其他编程语言,如C#、Python和Ruby等,也都提供了垃圾回收机制。

JVM的自动垃圾回收机制

        指Java虚拟机在运行Java程序时,自动回收不再使用的对象所占用的内存空间的过程。Java程序中的对象,一旦不再被引用会被标记为垃圾对象,JVM会在适当的时候自动回收这些垃圾对象所占用的内存空间。

其优点在于

  • 减少了开发人员的工作量,不需要手动管理内存;

  • 动态地管理内存,根据应用程序的需要进行分配和回收,提高了内存利用率;

  • 避免内存泄漏和野指针等问题,增加程序的稳定性和可靠;

缺点在于

  • 垃圾回收会占用一定的系统资源,可能会影响程序的性能;

  • 垃圾回收过程中会停止程序的执行,可能会导致程序出现卡顿等问题;

  • 不一定能够完全解决内存泄漏等问题,需要在编写代码时注意内存管理和编码规范;

垃圾回收机制的关键知识点

        垃圾回收机制需要判断哪些对象需要回收?即如何判读判断对象存活。其方法包括了有引用计数法可达性分析算法(JVM采用)。

        如何针对性进行回收?其收集死亡对象方法主要有三种,有标记-清除算法、标记-复制算法和标记-整理算法。每个中算法所针对的场景都不一样,没有最优解,只有最合适。

        了解垃圾回收算法和垃圾收集器的关系?两者没有可比性,是承先启后的关系,垃圾回收算法是垃圾回收的方法论,而垃圾收集器是算法的落地实现

初步了解判断方法-引用计数法

        简而言之就是跟踪每个对象被引用的次数,当引用次数为0时,就可以将该对象回收。在JVM中,每个对象都有一个引用计数器,当对象被引用时,引用计数器+1,当对象被取消引用时,引用计数器-1,当引用计数器为0时,该对象就可以被回收。

        其优点在于实现简单,回收垃圾的效率高。但缺点也显而易见循环引用无法回收。如果两个对象互相引用,它们的引用计数器永远不会为0,因此无法真正被回收,而且引用计数器开销大,每个对象都需要一个引用计数器,如果对象很多,开销就会很大。

什么是循环引用

public class Main {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.setB(b);
        b.setA(a);
        a = null;
        b = null;
        System.gc();
    }
}

class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }
}

        类A和类B相互引用,每个对象都持有对方的引用,形成了一个循环引用的环,当Main方法执行完毕后,a和b对象都置为null。由于它们相互引用,它们的引用计数器都不为0,无法被垃圾回收器回收,导致内存泄漏,但是上面代码却不会发生内存泄漏,因为多数jvm没有采用这个引用计数器方案,而是采用可达性分析算法

GCRoot和可达性分析算法

什么是可达性分析算法

        简而言之就是从一些“GC Roots”对象开始,通过搜索引用链的方式,找到所有可达对象。如果一个对象没有任何引用链与GC Roots相连,那么它就被判定为不可用的,是可以被回收的垃圾对象。

什么是GC Root

        指一些被JVM认为是存活的对象,它们是垃圾回收算法的起点,可以理解为由堆外指向堆内的引用, 本身是没有存储位置,都是字节码加载运行过程中加入 JVM 中的一些普通引用。通俗的例子可以是一个树形结构,树的根节点就是GC Roots,是垃圾回收器的起点,如果一个节点没有任何子节点与根节点相连,那这个节点就被认为是不可达的,可以被回收器回收。

        举个例子,将GC Roots比喻成一座城市,城市中有很多建筑物,这些建筑物就是内存中的对象,GC Roots就像城市的卫生局、消防局等,它们直接或间接地与城市中的建筑物相连,从这些机构出发,通过道路、桥梁等连接,最终能够到达所有的建筑物,如果一个建筑物没有与这些机构相连,那么它就被认为是废弃的,可以被清理掉。

JVM中的GC Roots对象包括以下几种:

        1)虚拟机栈(栈帧中的本地变量表)中引用的对象。

        2)方法区中类静态属性引用的对象。JDK 1.7 开始静态变量的存储从方法区移动到堆中,比如你定义了一个static 的集合对象,那里面添加的对象就是可以被GC Root可达的

        3)方法区中常量引用的对象。字符串常量池从 JDK 1.7 开始由方法区移动到堆中,本地方法栈中JNI(即一般说的Native方法)引用的对象。

小技巧:由于GC Roots采用栈方式存放变量和指针,如果一个指针它保存了堆内存里面的对象,但是自己又不能存放在堆内存里面,那么它就是一个GC Roots。

代码举例

// product 是栈帧中的本地变量,指向了 title = CSDN 这个 Product 对象
// 此时 当product 充当了 GC Root 的作用
// 当product = null; ,那么product 与原来指向product 对象断开了连接
// 所以这个 new Product("CSDN") 对象会被回收

public class GCTest {
    public static void main(String[] args) {
        Product product = new Product("CSDN");
        product = null;
    }
}

对象回收的关键知识点

标记对象可回收就一定会被回收吗?

        不一定会回收,对象的finalize方法给了对象一次最后一次存活的机会。当对象不可达(可回收)并发生 GC 时,会先判断对象是否执行了 finalize 方法,如果未执行则会先执行 finalize 方法。前对象与 GC Roots 关联,执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收!需要注意的是 finalize 方法只会被执行一次,如果第一次执行 finalize 方法,对象变成了可达,则不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被直接回收掉!

可达性分析算法为什么可以解决循环引用造成的内存泄漏问题?

        当两个或多个对象相互引用时,它们的引用链会形成一个环,但是由于这个环中的对象与GC Roots没有任何引用链相连,所以JVM会将这些对象判定为不可用的,从而回收它们。如下图所示。

垃圾回收算法

标记-清除算法原理

         是一种常见的垃圾回收算法,它的基本思路是分为两个阶段:标记阶段和清除阶段

        在标记阶段,垃圾回收器会从一些GC Roots对象开始,遍历整个对象图,标记所有被引用的对象。被标记的对象会被打上标记,表示这些对象是“活”的对象,需要保留下来,未被标记的对象就是垃圾对象,可以被回收。

        在清除阶段,垃圾回收器会对所有未被标记的对象进行回收。        

        其优点在于可以回收不连续的内存空间。其缺点显而易见,标记和清除两个步骤,都需要垃圾回收器遍历整个对象图,耗费时间较长,并且会产生内存碎片,当频繁进行垃圾回收时,内存碎片会越来越多导致可用内存空间不足,一次次不连续的内存使用,会影响程序的性能和稳定性。

        该算法应用场景应用在实际应用中,标记清除法一般用于不需要频繁进行垃圾回收的场景,比如在Java堆中大对象的分配和回收。其实后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进。

标记-复制算法原理

        是一种常见的垃圾回收算法,它的基本思路是将Java堆分为两个区域:一个活动区域和一个空闲区域。在垃圾回收过程中,首先标记所有被引用的对象,然后将所有被标记的对象复制到空闲区域中,最后交换两个区域的角色,完成垃圾回收

        从下图可以看出复制到空闲区域后的内存对象是连续的,以及未使用的内存空间也被重新分配。

为更深入了解该算法,我们详细看看它的实现步骤:

        1)在初始化环境下会将Java堆分为两个区域:一个活动区域一个空闲区域。初始时,所有对象都分配在活动区域中

        2)从GC Roots对象开始,遍历整个对象图,标记所有被引用的对象;

        3)对所有被标记存活的对象进行遍历,将它们复制到空闲区域中,并更新所有指向它们的引用,使它们指向新的地址

        4)对所有未被标记的对象进行回收,将它们所占用的内存空间释放;

        5)交换活动区域和空闲区域的角色,空闲区域变为新的活动区域,原来的活动区域变为空闲区域;

        6)当空闲区域的内存空间不足时,进行一次垃圾回收,重复以上步骤

        这样的方式其优点在于,如果内存中的垃圾对象较多,需要复制的对象就较少,则效率高,清理后,内存碎片少。其缺点也不少,虽然标记复制算法的效率较高,但是预留一半的内存区域用来存放存活的对象,占用额外的内存空间。如果出现存活对象数量比较多的时候,需要复制较多的对象效率低,假如是在老年代区域,99%的对象都是存活的,则性能低,所以老年代不适合这个算法。

        该算法应用场景应用在新生代的垃圾回收,因此需要对新生代的对象进行分代管理,虚拟机多数采用这个算法,对新生代进行内存管理,因为多数这个新生代区域的存活对象数量少。国外有公司统计过多数业务,98%撑不过一次GC,所以不用1:1比例分配新生代的空间

        这么分配的原因在于,当发生GC时, 将Eden和Survivor中存活对象一次性复制到另外一块Survivor空间上, 然后清理掉Eden和已用过的那块Survivor空间,每次新生代中可用内存空间为整个新生代容量的90% (Eden的80% + Survivor的 10%) ,只有一个Survivor空间, 即10%的新生代是会被浪费而已。

标记-整理-压缩算法原理

        从根节点开始对所有可达对象做一次标记,但之后并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端,然后清理边界外的垃圾,避免了碎片的产生,也不需要两块相同的内存空间,因此性价比比较高。

        其优点在于,解决了标记清除算法的碎片化的问题。和对比标记-复制算法来看,该算法不用浪费额外的空间,因为前者算法需要预留一部分空闲区域用于复制。和对比标记-清除算法来看,前者是一种非移动式的回收算法,而该算法是移动式的回收,且解决了内存碎片化的问题。

        其缺点就是效率相比于标记复制算法和标记清除算法低一些,在整理存活对象时,因对象位置的变动,需要调整该虚拟机栈中的引用地址。

         该算法应用场景应用在老年代的内存回收,它在标记-清除算法的基础上做了部分优化。 

几种算法对比

        标记-复制算法适合在存活对象少、垃圾对象多的场景,即新生代空间,朝生夕灭的场景

        标记-整理-压缩算法适合在存活对象多、垃圾对象少的场景,即老年代空间,都是历经多次GC,依旧存活的对象。

        标记-清除算法作为基础算法,其实也适合于在老年代空间,但不同的是在处理后会有碎片化空间,使用标记-整理-压缩算法效果会更佳。

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

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

相关文章

NeRF项目代码详解

1 项目结构 开源代码:https://github.com/yenchenlin/nerf-pytorch 在上述框架图中,首先重config_parse 中读取文件参数, 然后通过load_blender加载数据,加载的数据包括训练集、验证集和测试集以及摄像机的内外参数; …

淘宝、京东、拼多多纷争:“造节”过气,“制剧”当红

经过多年发展,消费者对国内电商三巨头形成了固有印象:拼多多价格低、京东物流快、淘宝生态完善。 消费者的固有印象是淘宝、京东、拼多多在市场上建立的“安全区”,安全区之内已没有挑战,安全区之外才是它们想要征服的新领地。而…

计算机视觉——使用OpenCV GrabCut算法从图像中移除背景

GrabCut算法 GrabCut算法是一种用于图像前景提取的技术,由Carsten Rother、Vladimir Kolmogorov和Andrew Blake三位来自英国剑桥微软研究院的研究人员共同开发。该技术的核心目标是在用户进行最少交互操作的情况下,自动从图像中分割出前景对象。 在Gra…

每日一题:视频拼接

你将会获得一系列视频片段,这些片段来自于一项持续时长为 time 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。 使用数组 clips 描述所有的视频片段,其中 clips[i] [starti, endi] 表示:某个视频片段开始于 starti 并于 …

LeetCode39题: 组合总和(原创)

【题目描述】 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复…

技术速递|利用 Redis 使 AI 驱动的 .NET 应用程序更加一致和智能

作者:Catherine Wang 排版:Alan Wang Redis 是一种流行的内存数据存储,可用于解决构建和扩展智能应用程序的关键挑战。在本文中,你将了解如何使用 Redis 的 Azure 缓存来提高使用 Azure OpenAI 的应用程序的效率。 Redis 的 Azur…

聚观早报 | 生数科技推出Vidu;2024款欧拉好猫正式上市

聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 4月28日消息 生数科技推出Vidu 2024款欧拉好猫正式上市 雷诺与小米汽车洽谈技术合作 微软张祺谈未来AI如何发展 …

机器学习/算法工程师面试题目与答案-数学基础部分

机器学习/算法工程师面试题目--数学基础部分 一、数学基础1、微积分SGD,Momentum,Adagard,Adam原理L1不可导的时候该怎么办sigmoid函数特性 2、统计学,概率论求 Max(a, b) 期望拿更长的玫瑰花的最好策略最大化工作天数的员工数切比雪夫不等式随机截成三段组成三角形…

基于MSP430F249的电子钟仿真(源码+仿真)

目录 1、前言 2、仿真 3、程序 资料下载地址&#xff1a;基于MSP430F249的电子钟仿真(源码仿真&#xff09; 1、前言 基于MSP430F249的电子钟仿真&#xff0c;数码管显示时分秒&#xff0c;并可以通过按键调节时间。 2、仿真 3、程序 #include <MSP430x24x.h> #def…

Jenkins集成Terraform实现阿里云CDN自动刷新

在互联网业务中&#xff0c;CDN的应用已经成了普遍&#xff0c;SRE的日常需求中&#xff0c;CDN的刷新在前端需求逐渐中占了很大比例&#xff0c;并且比较琐碎。做为合格的SRE&#xff0c;把一切自动化是终极使命&#xff0c;而今天就分享通过JenkinsTerraform实现阿里云的CDN自…

java-动态代理

为什么需要代理&#xff1f; 如何创建代理 注意&#xff1a;实现类和代理需要实现同一个接口 接口 public interface Star {String sing(String song);void dance(); }实现类 public class BigStar implements Star {private String name;public BigStar(String name) {this.…

2024Mac系统热门游戏排行榜 Mac支持的网络游戏有哪些?mac能玩哪些大型网游 苹果电脑Mac游戏资源推荐 Mac玩Windows游戏

“游戏是这个世界上唯一能和女性争夺男朋友的东西&#xff08;/滑稽&#xff0c;有不少女生也喜欢玩游戏&#xff09;。” 虽然只是一句玩笑话&#xff0c;不过也可以看出游戏对大多数男生来说是必不可少的一项娱乐活动了。而网络游戏是游戏中的一大分支&#xff0c;能让玩家们…

uniapp问题归类

最近使用uniapp中&#xff0c;遇到了一些问题&#xff0c;这边mark下。 1. 启动页变形 设置启动页的时候发现在部分android手机上启动页被拉伸了&#xff0c;最后看了下官方建议使用9.png图 生成9.png地址&#xff0c;推荐图片大小为1080x2340 uniapp推荐官方地址传送门 我…

Thread类的基本用法

1.线程创建 这里介绍线程创建常用的五种方法 1.继承Thread&#xff0c;重写run class MyThread extends Thread{public void run(){//这里写的代码就是线程要完成的任务while (true){System.out.println("hello thread");try {Thread.sleep(1000);//线程会休眠一秒…

Springboot+Vue项目-基于Java+MySQL的家政服务平台系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

FreeRTOS:3.信号量

FreeRTOS信号量 参考链接&#xff1a;FreeRTOS-信号量详解_freertos信号量-CSDN博客 目录 FreeRTOS信号量一、信号量是什么二、 FreeRTOS信号量1、二值信号量1、获取信号量2、释放信号量 2、计数信号量3、互斥信号量1、优先级反转2、优先级继承3、源码解析1、互斥量创建2、获取…

[蓝桥杯2024]-PWN:fd解析(命令符转义,标准输出重定向)

查看保护 查看ida 这里有一次栈溢出&#xff0c;并且题目给了我们system函数。 这里的知识点没有那么复杂 完整exp&#xff1a; from pwn import* pprocess(./pwn) pop_rdi0x400933 info0x601090 system0x400778payloadb"ca\\t flag 1>&2" print(len(paylo…

2024.04.28 Typecho管理视频文件,出现预览功能

需求原因原版的Typecho不支持在线视频预览,只有一个图片预览功能, 所以为了实现可以在线预览视频功能, 修改 typecho/admin/media.php 在大概19行的时候,追加如下内容 <?php if ($attachment->attachment->isImage): ?><p><img src"<?php $att…

装饰器模式【结构型模式C++】

1.概述 装饰器模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 2.结构 抽象构件&#xff08;Component&#xff09;角色&#xff1a;定义一个抽象接口以规范准备接收附加责任的对象。具体构件&#xff08;Concre…

关于文档中心的英文快捷替换方案

背景&#xff1a;文档中心需要接入国际化&#xff0c;想节省时间做统一英文方案处理&#xff1b; 文档中心是基于vuepress框架编写的&#xff1b; 1、利用百度翻译 API 的接口去做底层翻译处理&#xff0c;https://api.fanyi.baidu.com/需要在该平台上注册账号&#xff0c;个人…