【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 语言就有了垃圾回收器,这么多垃圾回收器其实是历史发展的产物。随着时间的推移和技术的进步,人们对垃圾回收器的性能、吞吐量、停顿时间等方面提出了更高的要求,因此就产生了更多更新的垃圾回收器。

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

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

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

相关文章

GDPU 天码行空11

(一)实验目的 1、掌握JAVA中IO中各种类及其构造方法; 2、重点掌握IO中类所具有的IO操作方法; 3、熟悉软件中登录模块的开发方法; 4、掌握IO中读写常用方法。 5、进一步熟悉正则规则的使用方法。 (二&…

上岸武汉顶级985!苦尽甘来的那一天,山河星月都作贺礼。

今天分享经验的同学是小马哥梦马强化班的学员,一战高分上岸武汉大学! 经验分享 很高兴有机会在这里给大家分享我的考研历程。 首先介绍一下我自己,我本科就读于吉林大学空间信息与数字技术,成绩不是很好。六级是在考研期间12月…

C语言逻辑语句

叠甲:以下文章主要是依靠我的实际编码学习中总结出来的经验之谈,求逻辑自洽,不能百分百保证正确,有错误、未定义、不合适的内容请尽情指出! 文章目录 1.何为控制语句2.控制语句-分支语句2.1.if2.2.switch3.控制语句-循环语句3.1.while3.2.do...while3.3.for4.控制语句-转向…

ue引擎游戏开发笔记(32)——为游戏添加新武器装备

1.需求分析: 游戏中角色不会只有一种武器,不同武器需要不同模型,甚至可能需要角色持握武器的不同位置,因此需要添加专门的武器类,方便武器后续更新,建立一个武器类。 2.操作实现: 1.在ue5中新建…

如何用DeepDiff测接口数据源变更?

开发同学最近变更了部分业务查询接口底层的数据源,希望测试同学能够针对这些接口进行一些回归验证,校验底层数据源更新前后业务查询接口返回的一致性,保证更新后对正常业务没有影响。 这个回归测试和一般接口测试有所区别,不仅仅…

C++基础-编程练习题2

文章目录 前言一、查找“支撑数”二、数组元素的查找三、爬楼梯四、数字交换五、找高于平均分的人 前言 C基础-编程练习题和答案 一、查找“支撑数” 【试题描述】 在已知一组整数中, 有这样一种数非常怪, 它们不在第一个, 也不在最后一个&…

使用Simulink Test进行单元测试

本文摘要:主要介绍如何利用Simulink Test工具箱,对模型进行单元测试。内容包括,如何创建Test Harness模型,如何自动生成excel格式的测试用例模板来创建测试用例,如何手动填写excel格式的测试用例模板来手动创建测试用例…

精通 Docker:简化开发、部署与安全保障

踏上 Docker 之旅,每一条命令都是高效与可靠的新境界。Docker 彻底改变了软件开发,为构建、部署和保障应用程序提供了前所未有的便利。从打造精益敏捷的镜像到编排复杂的微服务架构,Docker 让开发人员和运维人员都倍感轻松。让我们深入探索 D…

Redis---------实现查询缓存业务

目录 数据库与缓存之间的工作业务逻辑: 接下来看查询缓存代码实现,主要是捋清楚业务逻辑,代码实现是死的: Controller: Service: P37作业实现:总体逻辑跟上面的业务逻辑差不多 Controller: Service&#…

头歌-Java编程基础进阶

第1关 两整数的四则运算 package step1;import java.util.Scanner;public class Arithmetic2Num {public static void main(String[] args) { // ---------------------Begin------------------------Scanner scnew Scanner(System.in);int asc.nextInt();int bsc.nextInt();…

对象存储的几个概念

媒体存储-帮助中心 - 天翼云 媒体存储 对象存储(经典版)I型 -帮助中心- 天翼云 OOS https://www.ctyun.cn/document/10026735/10028989 ZOS 成长地图_对象存储服务 OBS OBS 以上是对象存储 的几个概念,可以反复的理解下。

Azure AKS日志查询KQL表达式

背景需求 Azure(Global) AKS集群中,需要查询部署服务的历史日志,例如:我部署了服务A,但服务A的上一个版本Pod已经被杀掉由于版本的更新迭代,而我在命令行中只能看到当前版本的pod日志&#xff…

SparkSql介绍

概述 SparkSQL,顾名思义,就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL,而叫Shark,最开始的时候底层代码优化,sql的解析、执行引擎等等完全基于Hive&#xff0c…

使用 Lambda 匿名函数在 Python 中轻松地进行排序

排序是 Python 编程中的常见操作,Python 提供了许多强大的函数方法方便的完成排序功能,比如 sorted() 和 sort()。在本文中,我们将结合 lambda 函数进行排序。它被称为匿名函数,可以灵活轻松地完成各种形式的排序。 字符串列表排…

前端安全防护实战:XSS、CSRF防御与同源策略详解(react 案例)

前端安全防护实战中,主要涉及三个方面:XSS (Cross-Site Scripting) 攻击的防御、CSRF (Cross-Site Request Forgery) 攻击的防御,以及浏览器的同源策略。以下是这三个方面的详细说明: XSS 防御详解 XSS 概述 XSS攻击是一种让攻…

科创板门槛升级!解析中国量子企业的上市之路与国际比拼

4月30日晚,中国证监会于发布了修订后的《科创属性评价指引(试行)》(以下简称“新指引”),该指引自发布日起正式生效。本次修订对原有指引中的部分标准进行了调整,具体如下: 1&#x…

如何用Kimi,5秒1步生成流程图

引言 在当前快节奏的工作环境中,拥有快速、专业且高效的工具不可或缺。 Kimi不仅能在5秒内生成专业的流程图(kimi),还允许实时编辑和预览,大幅简化了传统流程图的制作过程。 这种迅速的生成能力和高度的可定制性使得…

有哪些方法可以在运行时动态生成一个Java类?

使用 Java 反射 API🚩: Java 的反射 API 允许在运行时查询和操作类和对象。虽然反射 API 本身不直接提供生成新类的功能,但可以用于动态调用构造函数、方法和访问字段,这在某些情况下可以作为动态生成类的一部分。 字节码操作库&…

Day28:ElasticSearch入门、Spring整合ES、开发社区搜索功能

ElasticSearch入门 Elasticsearch简介 一个分布式的、Restful风格的搜索引擎。支持对各种类型的数据的检索(非结构化的也可以)。搜索速度快,可以提供实时的搜索服务。便于水平扩展(集群式部署),每秒可以处…

一文读懂计算机视觉4大任务:分类任务、检测任务、目标分割任务、关键点检测任务

《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…