【JVM】垃圾回收机制(Garbage Collection)

目录

一、什么是垃圾回收?

二、为什么要有垃圾回收机制(GC)?

三、垃圾回收主要回收的内存区域

四、死亡对象的判断算法

a)引用计数算法

b)可达性分析算法

五、垃圾回收算法

a)标记-清除算法

b)复制算法

c)标记-整理算法

d)分代算法

六、垃圾回收器


一、什么是垃圾回收?

垃圾回收指的是自动管理和释放不再被程序使用的内存资源的过程。在程序运行过程中,动态分配的内存会被使用,但也需要在不再需要时释放,以免占用过多的内存空间,导致内存泄漏或内存溢出等问题。

二、为什么要有垃圾回收机制(GC)?

早在学习C语言中,有这么一块内容:动态内存管理。动态内存管理的主要内容就是通过 malloc 申请内存,free 释放内存。

  • 此处 malloc 申请到的内存,生命周期是跟随整个进程的。
  • 这一点对于服务器程序就很不友好了,服务器每个请求都去 malloc 一块内存,如果没有 free 释放,就会使申请的内存越来越多,后续想要申请内存就无法申请了。这就是内存泄漏问题。

而在实际开发中,很容易出现 free 不小心就忘记调用了,或者因为一些情况导致 free 没有被执行到,例如代码块中存在 if 导致的 return 或者 抛出异常了(当前C语言没有异常这一说)。因此,内存泄漏是一个很大的问题。

能否让释放内存的操作,由程序自动负责完成,而不是依赖程序员手动释放呢?

  • 此时就引入了垃圾回收机制(GC),来解决上述问题。

三、垃圾回收主要回收的内存区域

JVM 在运行 Java 程序时,将内存划分为不同的区域,称为运行时数据区。这些区域包括方法区、堆、栈(虚拟机栈)、本地方法栈和程序计数器等。程序在执行之前先要把 Java 代码转换成字节码(.class文件),JVM 首先需要把字节码通过一定的方式(类加载器)把文件加载到内存中(即运行时数据区)。

对于程序计数器、虚拟机栈、本地方法栈这三部分区域而言,其生命周期与相关线程有关,随线程而生,随线程而灭。并且这三个区域的内存分配与回收具有确定性,因为当方法结束或者线程结束时,内存就自然跟着线程回收了。

而对于方法区,方法区主要存储的是类加载后的元数据信息(静态变量,常量池等),这些信息在程序运行期间基本上是不会发生变化的。

因此,在Java虚拟机中,垃圾回收主要针对的是 堆 区域进行回收。

前面说到,垃圾回收指的是自动管理和释放不再被程序使用的内存资源的过程,在Java堆中,存放着几乎所有的对象实例,因此内存回收,也可以叫做死亡对象的回收。

垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经“死去”。判断对象是否已“死”有以下几种算法。

四、死亡对象的判断算法

a)引用计数算法

引用计数法的描述如下: 

  • 引用计数算法是一种简单的垃圾回收算法,它基于对象的引用计数来确定何时可以回收对象。在引用计数算法中,每个对象都会有一个与之关联的引用计数,用来记录有多少个指针指向该对象。当引用计数为0时,表示该对象不再被引用,可以安全地回收。
  • 这种算法的实现相对简单,通常在对象创建和销毁时维护引用计数。当有新的指针指向对象时,引用计数加1;当指针不再指向对象时,引用计数减1。当引用计数减到0时,垃圾回收器会立即回收该对象。

引用计数算法的优点是实现简单,且判定效率也比较高,可以及时回收不再使用的对象。比如Python语言就采用引用计数法进行管理内存。

但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题。即如果两个或多个对象之间存在循环引用,它们的引用计数永远不会为0,即使它们已经不再被程序使用,也无法被回收。这会导致内存泄漏,占用大量的内存空间。

b)可达性分析算法

JVM 就是采用“可达性分析”来判断对象是否存活(同样采用此法的还有C#、Lisp),该算法可以有效处理循环引用问题,其核心思想为:

  • 通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索(遍历),搜索走过的路径称之为“引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达),证明此对象是不可用的。
  • 一旦完成了可达性分析,JVM 就可以安全地回收那些不可达的对象,释放它们所占用的内存空间。

如上图所示,对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此它们会被判定为可回收对象。

★在Java语言中,可作为GC Roots的对象包含下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中 JNI(Native方法)引用的对象。

通过可达性分析算法,JVM 就能够将死亡对象标记出来了,标记出来之后就要进行垃圾回收操作。在介绍垃圾回收器之前,先介绍垃圾回收器使用的几种算法。

五、垃圾回收算法

a)标记-清除算法

“标记-清除”算法是最基础的垃圾回收算法。算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

后续的回收算法都是基于这种思路,并对其不足加以改进而已。

“标记-清除”算法的不足主要有两个:

  1. 效率问题:标记和清除这两个过程的效率都不高,特别是在堆中对象数量庞大的情况下。
  2. 空间问题:标记清除后会产生大量不连续的内存碎片,内存碎片太多可能会导致以后在程序运行中,需要分配较大对象时,无法找到足够连续的内存而导致内存分配失败,或不得不提前触发另一次垃圾回收。

b)复制算法

“复制”算法解决了“标记-清除”算法两个主要问题,即内存碎片化问题和效率问题。

  • 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
  • 当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。

这样做的好处就在于不仅解决了“标记-清除”的主要问题,且算法实现简单,运行高效。解决内存碎片化问题主要体现在,复制过程中通过移动堆顶指针,按顺序进行分配,使碎片化内存被复制到连续的一块空间。

不过,复制算法也有一些缺点:例如在一轮GC中,大部分对象都是长期存活的,只有少数对象需要回收,那么复制算法就会造成大量的复制操作,导致影响性能。

c)标记-整理算法

“标记-整理”算法的提出,则是为了解决“复制”算法在对象存活率较高时,会进行比较多的复制操作这样的问题。

  • 标记过程仍与“标记-清除”算法的标记过程一致;
  • 但后续步骤不是直接对可回收对象进行回收,而是让所有存活对象都向一端移动,以便整理出一块连续的内存空间。这个思想类似于双指针算法,但其移动对象的过程更复杂。

上述过程仍有一些缺点:即移动存活对象的过程会带来较大的开销。

d)分代算法

综合以上三种算法,JVM并没有直接使用其中的一种,而是结合这三种算法,搞出了一个“综合性”方案,即分代算法。

实际上,堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象才放入老生代。新生代还有三个区域:一个 Endn + 两个 Survivor(S0/S1).

分代算法就是通过区域划分,实现不同区域使用不同的的垃圾回收策略,从而实现更好的垃圾回收。

这就好比中国的一国两制方针一样,对于不同的情况和地域设置更符合当地的规则,从而实现更好的管理,这就是分代算法的设计思想。 

当前 JVM 垃圾回收都采用的是“分代”算法。在新生代中,每次垃圾回收都有大批的对象死去,只有少量存活,因此采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清除”或者“标记-整理”算法。

  • 对于新生代来说,98%的对象都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将新生代内存分为一块比较大的Eden(伊甸园)空间和两块比较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域,一个称为From区,一个称为To区)。
  • 当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor上,最后清理掉Eden和刚才使用过的Survivor空间。完成一次对象复制后,From区和To区的角色会互换。
  • HotSpot虚拟机默认Eden与Survivor的大小比例是8:1,也就是说Eden:Survivor From:Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。

哪些对象会进入新生代?哪些对象会进入老年代?

  • 新生代:一般新创建的对象都会进入新生代;
  • 老年代:大对象和经历了 N 次(一般情况默认是 15 次)垃圾回收依然存活下来的对象会从新生代移动到老年代。

面试题:请问了解 Minor GC 和 Full GC 吗?这两种GC有什么不一样吗?

  • Minor GC又称为新生代GC,指的是发生在新生代的垃圾回收。因为新生代中的Java对象大多都具有朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
  • Full GC又称为老年代GC或者Major GC:指发生在老年代的垃圾回收。出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对)。Major GC的触发频率通常比较低,且速度一般会比Minor GC慢10倍以上。

六、垃圾回收器

如果说上面所讲的垃圾回收算法是内存回收的方法论,那么垃圾回收器就是内存回收的具体实现。

垃圾回收器的作用:垃圾回收器是为了保证程序能够正常、持久运行的一种技术,它是将程序中的死亡对象,也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间。

以下这些回收器是 HotSpot 虚拟机随着不同版本推出的重要的垃圾回收器:

上图展示了7种作用于不同分代的垃圾回收器,如果两个回收器之间存在连线,就说明它们之间可以搭配使用。所处的区域,表示它是属于新生代回收器还是老年代回收器。

为什么会有这么多垃圾回收器?

  • 其主要原因是:自从有了 Java 语言就有了垃圾回收器,这么多垃圾回收器其实是历史发展的产物。随着时间的推移和技术的进步,人们对垃圾回收器的性能、吞吐量、停顿时间等方面提出了更高的要求,因此就产生了更多更新的垃圾回收器。

此处就不展开介绍每种垃圾回收器了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OSPF虚链路

原理概述 通常情况下,一个OSPF网络的每个非骨干区域都必须与骨干区域通过ABR路由器直接连接,非骨干区域之间的通信都需要通过骨干区域进行中转。但在现实中,可能会因为各种条件限制,导致非骨干区域和骨干区域无法直接连接&#x…

Redis缓存雪崩,击穿,穿透问题

缓存雪崩、击穿、穿透、发生的背景 ​ 作者最近在写一个社区论坛项目,初始设想将论坛里用户发布的帖子内容存到数据库中,当用户访问论坛里的帖子时,帖子信息都从数据库中查。众所周知数据库的帖子数据是存在磁盘中的,而磁盘读写数…

串口初始化自己独立的见解--第九天

1.SM0,SM1 我们一般用 8位UART,波特率可变 (方式1的工作方式) SCON :SM2 一般不用,SM0 0 ,SM1 1 PCON : 有两位 我们不动它,不加速,初始值 TMOD:8位自动重装定时器&#xff0…

ADS基础教程11 - TouchStone文件的导出及导入

目录 一、 T o u c h S t o n e 介绍 \color{#4285f4}{ \mathbf{ 一、TouchStone介绍}} 一、TouchStone介绍 二、文件导出、导入方式 \color{#4285f4}{ \mathbf{ 二、文件导出、导入方式}} 二、文件导出、导入方式1.原理图操作1)原理图中导出2.原理图中导入 3.DDW中…

Python图形复刻——绘制母亲节花束

各位小伙伴,好久不见,今天学习用Python绘制花束。 有一种爱,不求回报,有一种情,无私奉献,这就是母爱。祝天下妈妈节日快乐,幸福永远! 图形展示: 代码展示: …

详解分布式锁

知识点: 单体锁存在的问题: 单体锁,即单体应用中的锁,通过加单体锁(synchronized或RentranLock)可以保证单个实例并发安全 单体锁是JVM层面的锁,只能保证单个实例上的并发访问安全 如果将单…

消除试卷手写笔迹的软件免费的有哪些?这几款都不错

消除试卷手写笔迹的软件免费的有哪些?在数字化学习的浪潮中,学生们越来越频繁地利用电子设备来完成学习任务。然而,当纸质试卷需要被数字化并再次利用时,如何高效地消除手写笔迹便成为了一个有待解决的问题。那么,今天…

【Linux】简易进度条的实现

🎉博主首页: 有趣的中国人 🎉专栏首页: Linux 🎉其它专栏: C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好,本片文章将会讲解Linux中进度条的实现的相关内容。 如果看到最后您觉得这篇文章写得…

coherence protocal基础

一致性整体抽象 coherence protocol是通过保证如下的两点invariants,来实现一致性的; SWMR invariant, 对于任何一个地址,任何时刻,都只会有一个core写,可能会有多个core读;Data-value invariant&#xf…

每天五分钟深度学习:数学中的极值

本文重点 在数学领域中,极值是一个极其重要的概念,它不仅在纯数学理论研究中占据核心地位,而且在工程、物理、经济等实际应用领域也发挥着不可替代的作用。极值问题涉及函数的最大值和最小值,是微积分学中的一个基本问题。本文旨在详细介绍数学中的极值概念、性质、求解方…

Python查询PostgreSQL数据库

哈喽,大家好,我是木头左! Python与PostgreSQL的连接 需要了解如何在Python中连接到PostgreSQL数据库。这通常涉及到使用一个库,如psycopg2,它是Python中用于PostgreSQL的最流行的适配器。安装psycopg2非常简单&#x…

202466读书笔记|《一天一首古诗词》——借问梅花何处落,风吹一夜满关山

202466读书笔记|《一天一首古诗词》——借问梅花何处落,风吹一夜满关山 上册下册 《一天一首古诗词》作者李锡琴,蛮早前加入书架的已购买书籍,这次刚好有点时间,利用起来读完。 赏析没有细看,只读了诗词部分&#xff0…

Cocos creator实现《战机长空》关卡本地存储功能

Cocos creator实现《战机长空》关卡本地存储功能 Cocos creator在开放小游戏过程中,经常会出现设置关卡,这里记录一下关卡数据本地存储功能。 一、关卡设置数据 假如我们有关卡数据如下, let settings [ { level: 1, // 第1关 score: 0,…

uniapp的app端推送功能,不使用unipush

1&#xff1a;推送功能使用htmlPlus实现&#xff1a;地址HTML5 API Reference (html5plus.org) 效果图&#xff1a; 代码实现&#xff1a; <template><view class"content"><view class"text-area"><button click"createMsg&q…

Spring框架学习笔记(一):Spring基本介绍(包含IOC容器底层结构)

1 官方资料 1.1 官网 https://spring.io/ 1.2 进入 Spring5 下拉 projects, 进入 Spring Framework 进入 Spring5 的 github 1.3 在maven项目中导入依赖 <dependencies><!--加入spring开发的基本包--><dependency><groupId>org.springframework<…

SpringBoot集成微信支付V3版本流程(商户平台篇)

一、前言 微信支付账号类型分为商户平台和合作伙伴平台&#xff0c;今天主要是梳理商品平台微信支付流程。 商品平台文档地址&#xff0c;(在接入前建议仔细阅读这份文档&#xff0c;会少走很多弯路!!!) 小程序下单 - 小程序支付 | 微信支付商户文档中心 二、接入流程 以下…

qml 和 c++类的数据交互

1、 新建一个需要交互的C++类 1)添加QObject头文件 2)添加自QObject的继承 3)添加Q_OBJECT宏 4)使用Q_PROPERTY,定义两个交互的属性,并设置读写的方法和变更属性的信号。 5)添加方法、槽函数和变量 2、在main.cpp中添加实例化对象的QML上下文 1)添加需要QML交互的…

多个.C文件被编译为一个可执行文件的详细过程

多个.C文件被编译为一个可执行文件的详细过程 文章目录 多个.C文件被编译为一个可执行文件的详细过程前言一、一个.C文件的编译过程二、多个.C文件的链接过程1.文件信息2.链接过程3.makefile 总结 前言 C语言经典的 “hello world ” 程序从编写、编译到运行&#xff0c;看到屏…

html实现网页插入音频

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要介绍html中 如何插入音乐和视频 视频插入 标签:<video></video> 兼容格式:mp4,因为别的浏览器都有不兼容的格式&#xff0c;唯一对mp4全都兼容。所以尽量使用mp4格式。 属性: 属性属性值…

249 基于matlab的MED、OMEDA、MOMEDA、MCKD信号处理方法

基于matlab的MED、OMEDA、MOMEDA、MCKD信号处理方法。最小熵反褶积(MED)&#xff0c;最优最小熵反卷积调整卷积 (OMEDA),多点最优最小熵解卷积调整&#xff08;Multipoint Optimal Minimum Entropy Deconvolution Adjusted&#xff0c;MOMEDA&#xff09;&#xff0c;最大相关峭…