9. 垃圾收集器与内存分配策略

news2024/12/23 6:38:58

整体思路

先考虑3个问题

  1. 哪些内存需要收集

    • 堆和方法区需要收集;程序计数器、虚拟机栈、本地方法栈都不需要做垃圾回收(按照其功能很容易理解)
  2. 什么时候收集

    • 对象已死。引申出另一个问题,怎么判断对象已死呢?
    • 当程序内存不足时
  3. 怎么收集

    • 分代收集理论

    • 垃圾回收算法

一、对象已死

1.1 引用计数法

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

**优点:**原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。

**缺点:**循环引用问题。两个对象再无任何引用,实际上这两个对象已 经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也 就无法回收它们。

Java虚拟机用的不是引用计数法

1.2 可达性分析

**定义:**通过一系列成为 “GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过所走的路径成为引用链,如果某个对象到 GC Roots 间没有任何引用链相连,则证明此对象是不可达的。

可作为 GC Roots 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,如方法参数、局部变量、临时变量等;
  • 方法区中类静态属性引用的对象,如 Java 类的应用类型的静态变量;
  • 方法区中常量引用的对象,如字符串常量池里的引用;
  • 本地方法栈中 JNI (Native 方法)引用的对象;
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器;
  • 所有被同步锁(synchronized关键字)持有的对象;
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

在这里插入图片描述

1.3 引用详细分类

**背景:**在 JDK1.2 版以前,Java 里的引用是很传统的定义,即 reference 类型的数据中存储的数值代表另一块内存的起始地址,就称该 reference 数据代表某块内存、某个内存的引用。在这种定义下,一个对象只有 “被引用” 或者 “未被引用” 两种状态,对于描述一些“ 食之无味,弃之可惜”的对象就显 得无能为力。譬如我们希望能庙述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应 用场景。

所以在 JDK 1.2 版之后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用和虚引用四种。

**强引用:**指最传统的引用定义,即 Object obj = new Object() 这种引用关系。只要强引用关系还在,对象永远不会被回收。

**软引用:**描述一些还有用,但非必须的对象。只被软引用关联着的对象,

**弱引用:**来描述那些非必须对象,但是它的强度比软引用更弱一些。
**虚引用:**是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

二、垃圾回收算法

2.1 分代收集理论

2.1.1 背景

当前商业虚拟机的垃圾收集器,大多遵循了 “分代收集” 的理论进行设计,它建立在两个分代假说之上:

  1. 弱分代假说:绝大多数对象都是朝生夕灭的。
  2. 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分 出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

好处:将大多数朝生夕灭的对象集中放在一起,每次回收时只需要关注如何保留少量存活而不是去标注那些大量要被回收的对象,就能以较低代价回收大量空间;将剩下的难以消亡的对象集中放在一块,虚拟机便可以较低频率回收这个区域,这就同时兼顾了垃圾回收时间和内存空间的有效利用。

2.1.2 应用

回收类型的划分:根据回收区域的位置不同,划分出 Minor GC、Major GC、Full GC

回收算法:根据不同区域中对象存活特征的不同,发展出标记-复制算法、标记-清除算法、标记-整理算法

2.1.3 存在的难点——跨代引用

**问题:**分代收集并非只是简单划分一下内存区域那么容易,它至少存在一个明显的困难:对象不 是孤立的,对象之间会存在跨代引用,假如要现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代中的对象是完全有可 能被老年代所引用的,为了找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性。

为了解决这个问题,添加第三条经验法则:

  1. 跨代引用假说: 跨代引用相对于同代引用来说仅占极少数。

解决方式:依据这条假说,我们不必扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在以及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称 为**“记忆集”**,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会 存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。

2.2 标记-清除法

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

**优点:**简单

缺点:

  1. 执行效率不稳定。果Java堆中包含大量对 象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过 程的执行效率都随对象数量增长而降低

  2. 内存空间碎片化的问题,标记、清除之后会产生大量不连续的内存碎片,导致大对象无法找到足够的内存分配,从而会触发另一次垃圾回收

在这里插入图片描述

2.3 标记-复制法

**算法思路:**为了解决标记-清除算法面对大量可回收对象时执行效率低 的问题,1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

**优点:**实现简单,运行高效。每次都针对整个半区进行清理,不用考虑内存碎片的问题;而且内存中对象大多数都是可回收的,所以需要复制的对象并不多。

**缺点:**这种复制回收法将可用内存缩小为原来的一半,空间浪费太多。

在这里插入图片描述

**改进:**IBM对新生代 “朝生夕灭” 的特点做了更量化的诠释——新生代中的对象有98%熬不过第一轮收集。因此并不需要按照1∶1的比例来划分新生代的内存空间。Andrew Appel 提出了一种更优化的半区复制分代策略,将新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空。间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。

**内存分配担保:**上述改进是基于98%的对象可被回收的理论,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。

**缺点:**老年代没有可用的内存为其作担保

2.4 标记-整理法

**定义:**老年代没有可用的内存为其作担保,所以不可用标记-复制算法。标记-整理法与标记-清除算法一样,但后续步骤不是直接对可 回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

在这里插入图片描述

**优点:**可以避免空间碎片化问题

**缺点:**移动存活对象并更新所有引用这些对象的地方是一种极为负重的操作,这种对象移动操作必须全程暂停用户应用程序才能进行。(ZGC和Shenandoah收集器使用读屏障(Read Barrier)技术实现了整理过程与用户线程的并发 执行)

三、Hotspot虚拟机的实现

第一章和第二章从原理上讲了常见的对象存活判定算法和垃圾收集算法,Java虚拟机实现这些算法时,必须对算法的执行效率有严格的考量才能保证虚拟机的高效运行。本章介绍 Hotspot 虚拟机是如何高效实现上述算法的。

下面这些技术都是针对可达性分析遇到的问题的解决方案

3.1 根节点枚举

**问题:**在做可达性分析时,首先需要查找 GC Roots,查找过程实现高效不是一件容易的事情,光是光是方法区的大小就常有数百上千兆,里面的类、常量等更是恒河沙数,若要逐个检 查以这里为起源的引用肯定得消耗不少时间。

解决方式: 虚拟机记录哪些地方时存放着对象引用的。在HotSpot虚拟机里,使用一组称为 OopMap 的数据结构来达到这个目的。一旦类加载完成的时候,HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也 会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信 息了,并不需要真正一个不漏地从方法区等GC Roots开始查找。

**注意事项:**迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。现在可达性分析算法耗时 最长的查找引用链的过程已经可以做到与用户线程一起并发(具体见3.6),但根节点枚举始终还 是必须在一个能保障一致性的快照中才得以进行。

3.2 安全点

**问题:**在OopMap的协作下,HotSpot可以快速准确地完成 GC Roots 枚举,但是导致 OopMap 内容变化的指令非常多,如果为每一条指令都生成对应的 OopMap,那将需要大量的额外存储空间。

解决方式:在 “特定的位置” 生成 OopMap,这些位置成为 安全点。有了安全点的设定,也就决定了用户程序执行时并非在指令流的任意位置都能停顿下来开始垃圾收集,而是必须达到安全点后才能暂停

**安全点的选取:**以 “是否具有让程序长时间执行的特征” 为标准。“长时间执行” 的明显特征是指令序列的复用,如方法调用、循环跳转、异常跳转等。

**注意:**当用户的所有线程到达安全点后才可以进行垃圾收集。

3.3 安全区域

**问题:**如果存在用户线程处于不执行(如线程处于 Sleep 或 Blocked)状态,那就到达不了安全点,就没办法进入垃圾回收了。对于这种情况,,必须引入安全区域来解决。

解决方式:安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。当用户线程进入到安全区域里面的代码时,首先会标识自己已经进入了安全区域。当用户离开安全区域时,只有在完成根节点枚举的情况下才能离开。

3.4 记忆集和卡表

问题: 在 GC Roots 扫描时,有些对象存在跨代引用的情况,为了避免将整个老年代加入扫描范围,垃圾收集器在新生代中建立了名为记忆集的数据结构。

**记忆集:**记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。实现记忆集可以有不同的精度:

  • **字长精度:**每个记录精确到一个机器字长,该字包含跨代指针。
  • **对象精度:**每个记录精确到一个对象,该对象里有字段含有跨代指针。
  • **卡精度:**每个记录精确到一块内存区域,该区域内有对象含有跨代指针。可以用 “卡表” 的方式实现记忆集,这也是目前最常用的一种记忆集实现形式。

**卡表:**卡表最简单的形式可以只是一个字节数组[2],而HotSpot虚拟机确实也是这样做的。以下这行代 码是HotSpot默认的卡表标记逻辑[3]:

CARD_TABLE [this address >> 9] = 0;

字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个 内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以2的N次幂的字节数,通过上面代码可 以看出HotSpot中使用的卡页是2的9次幂,即512字节(地址右移9位,相当于用地址除以512)。

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代 指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。

在这里插入图片描述

3.5 写屏障

**问题:**有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。但问题是如何变脏,即如何在对象赋值的那一刻去更新维护卡表呢? 在编译执行的场景中,经过即时编译后的代码已经是纯粹的机器指令流了,这就必须找到一个在机器码层面的手段,把维护卡表的动作放到每一个赋值操作之中。

**写屏障:**HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的。写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。

应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外 的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还是低得多的。

3.6 并发的可达性分析

看书3.4.6

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

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

相关文章

外部链接<a>

创建外部链接 HTML 通过使用 标签在 HTML 中创建链接。 注意:移除 target“_blank” 属性避免点击链接会打开新的元素页。 使用a元素实现页面内跳转 a元素也可以用在网页内不同区域的跳转。 设置a元素的 href 属性值为井号#加上想跳转区域对应的id属性值&…

ASP.NET Core 3.1系列(28)——ASP.NET Core中使用Autofac替换内置IoC容器

1、前言 前面的博客主要介绍了一些Autofac的使用方法,示例代码都是基于控制台程序。本文就来介绍一下如何在ASP.NET Core中使用Autofac代替内置的IoC容器。 2、创建接口和类 这里搭建了一个简易的项目,如下图所示: Service层代码如下&…

长安汽车推动新伙伴变革重塑供应链模式发布长安智电iDD技术

1月12日,以“携手勇进一路有你”为主题的2023长安汽车全球伙伴大会在重庆大剧院举行。此次大会,是长安汽车总结过往生产经营良好态势,研判行业未来发展趋势,发布最新企业发展战略,与全球合作伙伴共谋新未来&#xff0c…

【JavaEE】网络编程基础之Socket套接字

✨哈喽,进来的小伙伴们,你们好耶!✨ 🛰️🛰️系列专栏:【JavaEE】 ✈️✈️本篇内容:网络编程基础之Socket套接字。 🚀🚀代码存放仓库gitee:JavaEE初阶代码存放! ⛵⛵作者…

机器学习:公式推导与代码实现-概率模型

最大信息熵模型 根据最大信息熵原理,信息熵最大时得到的模型是最优模型,即最大信息熵模型。 最大信息熵原理 信息论的开创者香农将信息的不确定程度称为熵,为了与热力学中熵的概念区分,这种信息的不确定程度又称信息熵。 最大信息熵原理认为在所有可能的概率模型中,熵…

【Python从入门到进阶】4、pycharm的安装及使用

接上篇《3、运行python代码》 上一篇我们学习了如何使用终端和执行文件运行python代码,本篇我们来学习python编程工具pycharm的安装及基本使用。 一、IDE的概念 上一篇我们介绍了使用命令行指令执行和文件编译的方法进行python代码的解释执行,但是仍然…

总之2022,我的研发、直播、软文触达13W+人的成果打包拿走,展望2023一起加油

导读 | 2022年勇哥算是正是进入写作圈,在小伙伴们的支持下,勇哥也是每日每夜的肝,真心和小伙伴们分享技术前沿路上的系列故事,大家相互鼓励与支持,勇哥也是收获满满!现在勇哥通过这边文章整理一下本年度&am…

重装系统win11服务器未响应怎么修复操作

最近网友问小编win11服务器未响应怎么修复?最近有用户询问这个问题,在使用电脑的时候遇到了服务器无响应的情况,今天小编来教大家win11服务器未响应怎么修复操作,希望能帮到各位。 工具/原料: 系统版本:Windows11 品…

Koa 真解

1. 前言 昨天花费了比较多的时间将Koa的源码阅读了一遍,主要是项目中用到了Koa,为了做的更加得心应手所以先将源码看一下,总体上源码还是非常简单的,没啥难度。一方面为了总结另一方面也是为了不太看懂源码的同学们,今…

代码审计-7 ThinkPHP框架代码审计

ThinkPHP框架目录 applocation:此目录为应用目录,网站主要的文件控制器都放在applocation目录下 view:此目录在applocation下,为视图层 extend:为扩展类库目录 public:为网站对外访问目录,也就…

汽车路径尽头放一个点图像验证

文章目录前言一.图片二.大致思路2.1 小车位置识别2.2 采用轮廓算法得到路径的坐标2.3 采用断点续连的方法,将轮廓算法得到点组成直线,并寻找到最后的坐标三 缺陷四.如果大佬有其他的好的方法欢迎大佬们留言交流前言 提示:文章写完后&#xf…

坑多路难走,学数据分析转行前要知道培训机构不会说的事情

想要转行做数据分析师?那就要做好迎接坑多路难走的准备。虽然培训机构可以教你如何使用工具和算法,但它们很少会告诉你真正的行业现状。在这个竞争激烈的领域中,需要知道的不仅仅是如何处理数据,还有如何在企业中应用它。 跟着我…

CMMI之配置管理

配置管理(Configuration Management, CM)的目的是通过执行版本控制、变更控制等规程,以及使用配置管理软件,来保证所有配置项的完整性和可跟踪性。配置管理是对工作成果的一种有效保护。配置管理过程域是SPP模型的重要组成部分。本…

42. 【农产品溯源项目前后端Demo】后端-区块链连接服务

本节介绍后端代码是如何与区块链网络连接的。 1.在后端代码里fabric包 负责与区块链网络连接,并发送交易。 2.fabric.Const文件 定义 区块链网络拓扑结构,请查看注释。 public final class Const {//区块链网络中organizations的配置目录,从配置文件读取证书目录public stat…

【JavaEE】单例模式如何保证在多线程环境下线程安全高可用?

文章目录1 单例模式回顾2 饿汉式单例模式的实现3 懒汉式单例模式的实现4 单例模式的线程安全问题分析5 线程安全的懒汉式实现6 总结1 单例模式回顾 单例模式是设计模式的一种。而设计模式就是针对我们实际开发中写代码所遇到的不同场景所设立的解决方案。在笔者JavaSE阶段的文章…

Vue组件化编程需要注意的命名规则

在Vue组件化编程过程中,开始接触的不太注意命名规则,比如对于组件的内部参数命名以及在父组件中使用命名感到模糊,犯一些错误,就感觉在踩坑。 其实,这是对Vue组件化编程中的命名规则没有留意,稍加学习就可以…

java伪随机数生成器

关于随机数的基本概念 1、对随机数性质分类: 随机性:符合该性质的叫弱伪随机数。这种随机数仅可以用于一般应用,无法用在密码学,例如java中的java.util.Random类不可预测性:符合该性质的叫强伪随机数。在密码学中&am…

学习记录660@项目管理一般知识

看了项目管理一般知识这一章的知识,最开始觉得这些内容,觉得太过于书面化,比如关于什么是项目管理,都要用一段正式定义,充满了国内教育的繁琐感,但是细细品味觉得这些定义是很有道理的,并不是多…

保障接口数据安全的十种方案

视频介绍 数据加密 --主要针对网络抓包 AES 对称加密 RES 非对称加密 实践中直接使用 HTTPS 对于用户个人信息及密码等敏感信息 可额外进行加密 (如密码会进行md5加密防止撞库) 加签验签 --甄别数据在传输过程中被篡改 通常通过哈希算法 进行验证 需…

【学习笔记】【Pytorch】十、搭建CIFAR-10 model结构和Sequential的使用

【学习笔记】【Pytorch】十、搭建CIFAR-10 model结构和Sequential的使用学习地址主要内容一、CIFAR-10 model结构介绍二、代码实现学习地址 PyTorch深度学习快速入门教程【小土堆】. 主要内容 一、CIFAR-10 model结构介绍 input : 332x32,3通道32x32的图片 -->…