JVM垃圾回收机制

article/2025/10/8 4:16:58

文章目录

  • JVM垃圾回收机制
    • 一、垃圾回收原理
    • 二、对象被标记为垃圾方法
      • 1. 引用计数器
      • 2. 可达性分析
    • 三、垃圾回收算法
      • 1. 标记-清除算法
      • 2. 复制算法
      • 3. 标记-整理算法
      • 4. 分代回收算法

JVM垃圾回收机制

一、垃圾回收原理

GC (Garbage Collection:即垃圾回收)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、老年代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
● 对新生代的对象的收集称为minor GC
● 对老年代的对象的收集称为Full GC
● 程序中主动调用System.gc()强制执行的GC为Full GC

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:
● 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
● 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
● 弱引用:在GC时一定会被GC回收
● 虚引用:由于虚引用只是用来得知对象是否被GC

二、对象被标记为垃圾方法

JVM的内存结构包括五大区域:程序计数器虚拟机栈本地方法栈堆区方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

1. 引用计数器

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

优点: 引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点: 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。

在这里插入图片描述
对以上代码通过引用计数法分析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 可达性分析

可达性算法是目前主流的虚拟机都采用的算法,程序把所有的引用关系看作一张图,从一个节点GC Roots开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:
● 虚拟机栈中引用的对象(栈帧中的本地变量表);
● 方法区中类静态属性引用的对象;
● 方法区中常量引用的对象;
● 本地方法栈中JNI(Native方法)引用的对象。
在这里插入图片描述
可以得出对象实例1、2、4、6都具有对象可达性,也就是存活对象,不能被GC回收的对象。而随想实例3、5虽然直接相连,但并没有任何一个GC Roots与之相连,即GC Roots不可达对象,就会被GC回收的对象。

三、垃圾回收算法

1. 标记-清除算法

标记/清除算法的基本思想就跟它的名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
标记阶段:
标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的 GC Roots 对象,对从 GCRoots 对象可达的对象都打上一个标识,一般是在对象的 header 中,将其记录为可达对象。
清除阶段:
清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header 信息),则将其回收。

在这里插入图片描述
上图是标记/清除算法的示意图,在标记阶段,从对象 GC Root 1 可以访问到 B 对象,从 B 对象又可以访问到 E 对象,因此从 GC Root 1 到 B、E 都是可达的,同理,对象 F、G、J、K 都是可达对象;到了清除阶段,所有不可达对象都会被回收。
在垃圾收集器进行 GC 时,必须停止所有 Java 执行线程(也称"Stop The World"),原因是在标记阶段进行可达性分析时,不可以出现分析过程中对象引用关系还在不断变化的情况,否则的话可达性分析结果的准确性就无法得到保证。在等待标记清除结束后,应用线程才会恢复运行。

标记/清除算法缺点:
效率问题
标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,这无疑很耗费时间,而且 GC 时需要停止应用程序,这会导致非常差的用户体验。
空间问题
标记清除之后会产生大量不连续的内存碎片(从上图可以看出),内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

2. 复制算法

复制算法是将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉
在这里插入图片描述
复制算法每次都是对整个半区进行内存回收,这样就减少了标记对象遍历的时间,在清除使用区域对象时,不用进行遍历,直接清空整个区域内存,而且在将存活对象复制到保留区域时也是按地址顺序存储的,这样就解决了内存碎片的问题,在分配对象内存时不用考虑内存碎片等复杂问题,只需要按顺序分配内存即可。

复制算法缺点:
复制算法简单高效,优化了标记清除算法的效率低、内存碎片多问题,存在缺点:
● 将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;
● 如果对象的存活率很高,极端一点的情况假设对象存活率为 100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的。

3. 标记-整理算法

标记-整理算法算法与标记/清除算法很像,事实上,标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
在这里插入图片描述
可以看到,回收后可回收对象被清理掉了,存活的对象按规则排列存放在内存中。这样一来,当我们给新对象分配内存时,jvm 只需要持有内存的起始地址即可。标记/整理算法弥补了标记/清除算法存在内存碎片的问题消除了复制算法内存减半的高额代价,可谓一举两得。

标记/整理缺点:
● 效率不高:不仅要标记存活对象,还要整理所有存活对象的引用地址,在效率上不如复制算法。

4. 分代回收算法

分代收集算法的思想是按对象的存活周期不同将内存划分为几块一般是把 Java 堆分为新生代和老年代(还有一个永久代,是 HotSpot 特有的实现,其他的虚拟机实现没有这一概念,永久代的收集效果很差,一般很少对永久代进行垃圾回收),这样就可以根据各个年代的特点采用最合适的收集算法。

特点:
新生代:朝生夕灭,存活时间很短。采用复制算法来收集
老年代:经过多次 Minor GC 而存活下来,存活周期长。采用标记/清除算法或者标记/整理算法收集老年代

新生代中每次垃圾回收都发现有大量的对象死去,只有少量存活,因此采用复制算法回收新生代,只需要付出少量对象的复制成本就可以完成收集;
老年代中对象的存活率高,不适合采用复制算法,而且如果老年代采用复制算法,它是没有额外的空间进行分配担保的,因此必须使用标记/清理算法或者标记/整理算法来进行回收。
在这里插入图片描述
新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。
老年代中的对象因为对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。
新产生的对象优先进去Eden区,当Eden区满了之后再使用Survivor from,当Survivor from 也满了之后就进行Minor GC(新生代GC),将Eden和Survivor from中存活的对象copy进入Survivor to,然后清空Eden和Survivor from,这个时候原来的Survivor from成了新的Survivor to,原来的Survivor to成了新的Survivor from。复制的时候,如果Survivor to 无法容纳全部存活的对象,则根据老年代的分配担保(类似于银行的贷款担保)将对象copy进去老年代,如果老年代也无法容纳,则进行Full GC(老年代GC)。
大对象直接进入老年代:JVM中有个参数配置
-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。
长期存活的对象进入老年代:JVM给每个对象定义一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被移入Survivor并且年龄设定为1。没熬过一次Minor GC,年龄就加1,当他的年龄到一定程度(默认为15岁,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。但是JVM并不是永远要求年龄必须达到最大年龄才会晋升老年代,如果Survivor 空间中相同年龄(如年龄为x)所有对象大小的总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。

分代回收:

我们从一个object1来说明其在分代垃圾回收算法中的回收轨迹。

1、object1新建,出生于新生代的Eden区域。
在这里插入图片描述
2、minor GC,object1 还存活,移动到From suvivor空间,此时还在新生代。
在这里插入图片描述
3、minor GC,object1 仍然存活,此时会通过复制算法,将object1移动到ToSuv区域,此时object1的年龄age+1。
在这里插入图片描述
4、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象并没有达到survivor的一半,所以此时通过复制算法,将fromSuv和Tosuv 区域进行互换,存活的对象被移动到了Tosuv。
在这里插入图片描述
5、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象已经达到survivor的一半以上(toSuv的区域已经满了),object1被移动到了老年代区域。
在这里插入图片描述
6、object1存活一段时间后,发现此时object1不可达GcRoots,而且此时老年代空间比率已经超过了阈值,触发了majorGC(也可以认为是fullGC,但具体需要垃圾收集器来联系),此时object1被回收了。fullGC会触发 stop the world。
在这里插入图片描述
在以上的新生代中,我们有提到对象的age,对象存活于survivor状态下,不会立即晋升为老年代对象,以避免给老年代造成过大的影响,它们必须要满足以下条件才可以晋升:
1、minor gc 之后,存活于survivor 区域的对象的age会+1,当超过(默认)15的时候,转移到老年代。
2、动态对象,如果survivor空间中相同年龄所有的对象大小的综合和大于survivor空间的一半,年级大于或等于该年纪的对象就可以直接进入老年代。


http://chatgpt.dhexx.cn/article/demlxSSl.shtml

相关文章

Java垃圾回收(GC)机制

一、为什么要垃圾回收 如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。 哪些内存需要…

JVM:常用的四种垃圾回收机制

1.CMS (Concurrent Mark Sweep):并行 标记清除 老年代垃圾回收机制 cms是一个基于标记-清除 算法的综合多种算法的老年代垃圾回收器 适用场景:重视服务器响应速度,要求系统停顿时间最短。 这里要说明下,这是一个老年代算法&…

什么是垃圾回收机制(超详细)

垃圾回收机制 1.垃圾回收机制(Garbage Collction)简称GC,是JavaScript中使用的内存管理系统的基本组部分,是为了防止内存泄漏 2.JavaScript是在创建变量(对象、字符串等)时自动进行了分配内存,并…

深入理解JVM—垃圾回收机制

一、前言 明确垃圾收集器关注的部分:堆和方法区。着重学习如何确定哪些垃圾需要回收、垃圾回收算法以及GC触发条件。 二、如何确定哪些垃圾需要回收 1、引用计数算法 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一&…

JavaScript垃圾回收机制

JavaScript垃圾回收机制 1 垃圾为何要产生并回收2 垃圾回收机制2.1 标记清除法2.2 引用计数法 3 V8对垃圾回收机制的优化——分代式垃圾回收机制3.1 新生代与老生代3.2 新生代的垃圾回收3.3 老生代的垃圾回收 1 垃圾为何要产生并回收 当我们写代码时创建一个基本类型、对象、函…

mysql初学——“[ERROR] [MY-012271] [InnoDB] The innodb_system data file 'ibdata1' must be writable”

这个问题一般会在安装后,想要启动mysql的时候出现。之前在网上查到过处理这类问题的解决方案,是删除一些文件,后面发现没有用,并且导致其他一些问题的出现。在stackoverflowed网站中发现一种解释是mysql已经运行了,发现…

MySQL的ibdata1文件无了该怎么恢复

前段时间电脑突然坏了,系统盘全部格式化了,只剩data源文件,装上MySQL把源文件放进去后发现所有库的表打不了开了, 我的mysql是5.7的版本,看网上说是ibdata1文件的问题,以前的ibdata1文件已经没了&#xff…

windows mysql 启动失败 :The innodb_system data file ‘ibdata1‘ must be writable

net start mysql 启动失败 在安装目录如:D:\mysql8\bin 下按住shift点击右键,启动cmd或者PowerShell 执行命令:mysqld --console 查看日志 注意error级别的记录:The innodb_system data file ‘ibdata1’ must be writable 进入…

MySQL 5.6 如何给ibdata1瘦身

前不久刚给ibdata1瘦身,发篇文章总结下。 ibdata1是MySQL使用InnoDB引擎时所产生的文件,其一般存储数据、索引、结构、缓冲数据、共享数据和重做日志等。因为ibdata1只增不减,长期操作数据库,可能会使其越来越大,而浪费…

误删mysql8下ibdata1文件恢复数据

一位小伙伴不小心误删了生产环境上mysql数据下/data/mysql/ibdata1和ib_logfile0、ib_logfile1文件,并且mysql服务停止了,造成mysql重启不了,吓得小伙伴以为要“被删库跑路”了,于是赶紧帮忙”救火”。像这种误删数据或者删库的情…

MySQL ibdata1 文件“减肥”记

夏天来了,没想到连 ibdata1 文件也要开始“减肥”了~ 作者:杨彩琳 爱可生华东交付部 DBA,主要负责 MySQL 日常问题处理及 DMP 产品支持。爱好跳舞,追剧。 本文来源:原创投稿 有句话是这么说的:“在 InnoDB…

【无标题】1.[ERROR] InnoDB: The innodb_system data file ‘ibdata1‘ must be writable

问题原因: 文件的权限导致运行MySQL服务的用户无法对这些文件进行写入导致的报错. 解决方案: 方案一: 初始化MySQL服务时记得使用"–user"指定运行mysql服务的用户. 方案二: 直接进入到数据目录使用"chown"命令修改权限即可.

【MySQL】MySQL发生系统错误、The innodb_system data file ‘ibdata1‘ must be writable问题的解决

1. 启动mysql服务时:发生系统错误5 使用命令行 net start MySql 后报错:发生系统错误 5。 说明权限不够 ,需要使用管理员身份运行cmd。 但是每次都需要搜索后然后右键选择以管理员身份运行很麻烦,这里介绍一个一劳永逸的办法&am…

MySQL的ibdata1文件占用过大瘦身

处理MySQL的ibdata1文件过大问题 本人在对数据库进行大量的数据插入和删除的时候,发现ibdata1的占了将近一个T ibdata1文件是什么? ibdata1是一个用来构建innodb系统表空间的文件,这个文件包含了innodb表的元数据、撤销记录、修改buffer和双…

【数据库篇】MySQL InnoDB ibd 文件格式解析

mysql innodb的表由.frm .ibd 组成,frm:存了每个表的元数据,包括表结构的定义等;ibd:存了每个表的元数据,包括表结构的定义等; 1.0 ibd文件基本结构 ibd文件由Tablespaces,Segments,Extents,P…

mysql 里的 ibdata1 文件

为什么 mysql 里的 ibdata1 文件不断的增长? 转自:http://linux.cn/article-5829-rss.html ibdata1 file 我们在 Percona 支持栏目经常收到关于 MySQL 的 ibdata1 文件的这个问题。 当监控服务器发送一个关于 MySQL 服务器存储的报警时,恐慌…

mysql ibdata1

ibdata1是什么? Mysql ibdata1即Innodb data1缩写,是innodb引擎的表空间,用于存放 数据字典Data dictionary: 只读的表,存储对象的相关信息,如占用空间,列的缺省值,约束信息&…

MySQL 中的 ibdata1

系统表空间是InnoDB数据字典、双写缓冲区、更改缓冲区和撤消日志的存储区域 。如果表是在系统表空间中创建的,而不是在每个表文件或通用表空间中创建,则它还可能包含表和索引数据。 系统表空间可以有一个或多个数据文件。默认情况下,ibdata1…

分享篇 | MySQL的ibdata1是个啥,为啥越来越大,怎么缩小?

同事的一个问题: MySQL的ibdata1文件越来越大,这是为啥、 看着别扭,怎么搞小它? ibdata1文件是什么? ibdata1是一个用来构建innodb系统表空间的文件,这个文件包含了innodb表的元数据、undo日志、修改buffe…

js类型转换题

考察隐式类型转换: 1.号一侧出现了字符串,就用String()将不是字符串的变成字符串,最后拼接在一起 2.-号则会调用显示类型转换Number(),将非数字转换成数字,进行计算 true 0 和 true false 都是隐式调用Number()变成…