垃圾回收的概念与算法(第四章)

news2024/11/17 3:59:59

《实战Java虚拟机:JVM故障诊断与性能优化 (第2版)》

第4章 垃圾回收的概念与算法

目标:

  1. 了解什么是垃圾回收
  2. 学习几种常用的垃圾回收算法
  3. 掌握可触及性的概念
  4. 理解 Stop-The-World(STW)

4.1. 认识垃圾回收 - 内存管理清洁工

垃圾回收(Garbage Collection,简称 GC),GC 中的垃圾,特指存在于内存中的、不会再被使用的对象,如果大量不会被使用的对象一直占着空间,在需要内存空间时有可能导致内存溢出。
垃圾回收并不是 JVM 独创的,早在 20 世纪 60 年代,垃圾回收就已经被 Lisp 语言所使用。现在,除了 Java,C#、Python 等语言都运用了垃圾回收的思想。

4.2. 常用的垃圾回收算法 - 清洁工具大 PK

常用的垃圾回收算法包括:引用计数法、标记清除法、复制算法、标记压缩法、分代算法和分区算法。

4.2.1. 引用计数法(Reference Counting)

1.最经典、最古老的一种垃圾收集算法。
2.实现:对于一个对象 A,只要任何一个对象引用了 A,则 A 的引用计数器就加 1,当引用失效时,引用计数器就减 1。只要对象 A 的引用计数器的值为 0,则对象 A 就不可能再被使用。
3.引用计数器的实现:为每个对象配备一个整型的计数器即可。
4.问题:非常严重的两个问题:
(1) 无法处理循环引用。因此,在 Java 的垃圾回收器中没有使用这种算法。
(2) 引用计算器要求在每次引用产生和消除的时候,伴随一个加法操作和一个减法操作,对系统性能会有一定的影响。

(1)一个简单的循环引用问题描述如下:有对象 A 和对象 B,对象 A 中含有对对象 B 的引用, 对象 B 中含有对象 A 的引用。此时,对象 A 和对象 B 的引用计数器都不为 0。但是,在系统中却不存在任何第 3 个对象引用了对象 A 或对象 B。也就是说,对象 A 和对象 B 是应该被回收的垃圾对象,但由于垃圾对象间相互引用,使垃圾回收器无法识别,引起了内存泄漏。

在这里插入图片描述

4.2.2. 标记清除法(Mark-Sweep)

1.标记清除法是现代垃圾回收算法的思想基础;
2.标记清除法将垃圾回收分为两个阶段:标记阶段和清除阶段;
3.实现:在标记阶段,首先通过根节点标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
4.问题:可能产生空间碎片;
5.注意:标记清除法先通过根节点标记所有的可达对象,然后清除所有的不可达对象,完成垃圾回收。

如图 4.2 所示,使用标记清除法对一块连续的内存空间进行回收。从根节点开始(这里显示了 2 个根节点),所有的有引用关系的对象均被标记为存活对象(箭头表示引用)。从根节点起,不可达对象均为垃圾对象。在标记操作完成后,系统回收所有不可达对象的空间。
如图 4.2 所示,回收后的空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续内存空间的工作效率要低于连续空间。因此,这也是该算法的最大缺点。
在这里插入图片描述

4.2.3. 复制算法(Copying)

1.核心思想:将原有的内存空间分为两块,每次只使用其中一块,在进行垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收;
2.优点:
(1)效率高:如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量就会相对很少;
(2)没有碎片:对象是在垃圾回收过程中统一被复制到新的内存空间中的,可确保回收后的内存没有碎片;
3.缺点:复制算法的代价是将系统内存折半;
4.新生代:存放年轻对象的堆空间。年轻对象指刚刚创建的或者经历垃圾回收次数不多的对象;
5.老年代:存放老年对象的堆空间。老年对象指经历多次垃圾回收后依然存活的对象;
6.注意:复制算法比较适合新生代,因为在新生代垃圾对象通常会多于存活对象,复制算法的效果会比较好;

如图 4.3 所示,A、B 两块相同的内存空间,A 在进行垃圾回收时,将存活对象复制到 B 中,B 在复制后保持连续。复制完成后,情况 A,并将空间 B 设置为当前使用空间。
在这里插入图片描述
在 Java 的新生代串行垃圾回收器中,使用了复制算法的思想。新生代分为 eden 区、from 区和 to 区 3 个部分。其中 from 区和 to 区可以视为用于复制的两块大小相同、地位相等且可进行角色互换的空间。from 区和 to 区也称为 survivor 区,即幸存者空间,用于存放未被回收的对象。

在进行垃圾回收时,eden 区的存活对象会被复制到未使用的 survivor 区(假设是 to 区),正在使用的 survivor 区(假设是 from )的年轻对象也会被复制到 to 区(大对象或者老年对象会直接进入老年代,如果 to 区已满,则对象也会直接进入老年代)。此时,eden 区和 from 区的剩余对象就是垃圾对象,可以直接清空,to 区则存放此次回收后的存活对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费,如图 4.4 所示,显示了复制算法的实际回收过程。当所有存活对象都复制到 survivor 区(图中为 to)后,简单地清空 eden 区和备用的 survivor 区(图中为 from)即可。
图 4.4 改进的复制算法

4.2.4. 标记压缩法(Mark-Compact)

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本将很高。因此,基于老年代垃圾回收的特性,需要使用其他算法。

1.老年代的回收算法,是在标记清除法的基础上做了一些优化;
2.实现:首先需要从根节点开始,对所有可达对象做一次标记。之后,将所有的存活对象压缩到内存的一端。然后,清理边界外所有的空间。
3.优点:
(1)避免了碎片的产生;
(2)不需要两块相同的内存空间,性价比较高;
4.最终效果等同于标记清除法执行完后再进行一次内存碎片整理,因此,也可以称为标记清除压缩法;

如图 4.5 所示,在通过根节点标记出所有的可达对象后,沿虚线进行对象移动,将所有的可达对象都移动到一端,并保持它们之间的引用关系,最后清理边界外的空间,即可完成垃圾回收工作。
在这里插入图片描述

4.2.5. 分代算法(Generational Collecting)

在前面介绍的算法中,没有一种算法可以完全替代其他算法,它们都有自己的优势和特点。根据垃圾回收对象的特性,使用合适的算法,才是明智的选择。

1.分代算法将内存区间根据对象的特点分成几块,根据每块内存区间的特点使用不同的回收算法,以提高垃圾回收的效率;
2.新生代比较适合使用复制算法:新生代的特点是朝生夕灭;

一般来说,JVM 会将所有的新建对象都放入称为新生代的内存区域,新生代的特点是对象朝生夕灭,大约 90% 的新建对象会被很快回收,因此新生代比较适合使用复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年代的内存空间。在老年代中,几乎所有的对象都是经过几次垃圾回收依然得以存活的。因此,可以认为这些对象在一段时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。

3.老年代使用标记压缩法或标记清除法:

在极端情况下,老年代对象的存活率可以达到 100%。如果依然使用复制算法回收老年代,将需要复制大量对象。再加上老年代的回收性价比也低于新生代,因此这种做法是不可取的。根据分代的思想,可以对老年代的回收使用与新生代不同的标记压缩法或标记清除法,以提高垃圾回收效率。如图4.6所示,显示了这种分代回收的思想。
在这里插入图片描述

4.新生代回收的频率很高,但是每次回收的耗时很短;老年代回收的频率比较低,但是会消耗更多的时间;

为了支持高频率的新生代回收,虚拟机可能使用一种叫作卡表(Card Table)的数据结构。卡表为一个比特位集合,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用。这样在新生代GC时,可以不用花大量时间扫描所有的老年代对象来确定每一个对象的引用关 系,可以先扫描卡表,只有当卡表的标记位为 1 时,才需要扫描给定区域的老年代对象, 而卡表位为0的老年代对象,一定不含有新生代对象的引用。如图 4.7 所示,卡表中每一位 表示老年代 4KB 的空间,卡表记录为 0 的老年代区域没有任何对象指向新生代,只有卡表 位为1的区域才有对象包含新生代引用,因此,在新生代GC时只需要扫描卡表位为1的老 年代空间。使用这种方式,可以大大加快新生代的回收速度。
在这里插入图片描述

4.2.6. 分区算法(Region)

1.分区算法将整个堆空间划分成连续的不同小区间,每一个小区间都独立使用,独立回收;
2.优点:可以控制一次回收小区间的数量;

一般来说,在相同条件下,堆空间越大,一次 GC 所需要的时间就越长,从而产生的停顿也越长。为了更好地控制 GC 产生的停顿时间,将一 块大的内存区域分割成多个小块,根据目标停顿时间,每次合理地回收若干个小区间,而不是回收整个堆空间,从而减少一次GC所产生的停顿。
在这里插入图片描述

4.3. 判断可触及性 - 谁才是真正的垃圾

4.3.1. 对象的复活 - finalize() 函数

1.可触及性包含以下 3 种状态:

  • 可触及的:从根节点开始,可以到达这个对象;
  • 可复活的:对象的所有引用都被释放,但是对象有可能在 finalize() 函数中复活;
  • 不可触及的(可被回收):对象的 finalize() 函数被调用,并且没有复活,就会进入不可触及状态,不可触及的对象不可能被复活,因为 finalize() 函数只会被调用一次;

垃圾回收的基本思想是考查每一个对象的可触及性,即从根节点开始是否可以访问这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点开始都无法访问到某个对象,说明该对象已经不再使用了,一般来说,该对象需要被回收。但事实上,一个无法触及的对象有可能在某个条件下使自己“复活”,如果是这样的情况,那么对它的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下才可以安全地回收对象。

2.finalize() 函数是一个非常糟糕的模式,不推荐使用 finalize() 函数释放资源;
(1)因为 finalize() 函数有可能发生引用外泄,在无意中复活对象;
(2)由于 finalize() 函数是被系统调用的,调用时间是不明确的,因此不是一个好的资源释放方案,推荐在 try-catch-finally 语句中进行资源的释放。

4.3.2. 引用和可触及性的强度

1.Java 中提供了 4 个级别的引用:强引用、软引用、弱引用和虚引用。

除强引用外,其他 3 种引用均可以在 java.lang.ref 包中找到。如图 4.9 所示,显示了这 3 种引用类型对应的类,开发人员可以在应用程序中直接使用它们。其中 FinalReference 为“最终”引用,它用以实现对象的 finalize() 函数。
在这里插入图片描述

2.强引用就是程序中一般使用的引用类型,强引用的对象是可触及的,不会被回收。软引用、弱引用和虚引用的对象是软可触及、弱可触及和虚可触及的,在一定条件下都是可以被回收的。
3.强引用的特点:
(1)强引用可以直接访问目标对象;
(2)强引用所指向的对象在任何时候都不会被系统回收,JVM 宁愿抛出 OOM 异常,也不会回收强引用所指向的对象;
(3)强引用可能导致内存泄漏;

4.3.3. 软引用 - 可被回收的引用

1.软引用是比强引用弱一点的引用类型。如果一个对象只持有软引用,那么当堆空间不足时,就会被回收。软引用使用 java.lang.ref.SoftReference 类实现。GC 未必会回收软引用的对象,但是当内存资源紧张时,软引用就会被回收,软引用对象不会引起内存溢出。

4.3.4. 弱引用 - 发现即回收

1.弱引用是一种比软引用弱的引用类型。在系统 GC 时,只要发现弱引用,不管系统堆空间情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,并不一定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长时间。一旦一个弱引用对象被垃圾回收器回收,便会加入一个注册的引用队列(这一点和软引用很像)。弱引用使用 java.lang.ref.WeakReference 类实现。
2.注意:软引用、弱引用都非常适合保持那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到让系统加速的作用。

4.3.5. 虚引用 - 对象回收跟踪

1.虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的 get() 方法取得强引用时,总会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程;
2.当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
3.由于虚引用可以跟踪对象的回收时间,所以也可以将一些资源释放操作放在虚引用中执行和记录。

4.4. 垃圾回收时的停顿 - Stop-The-World

垃圾回收器的任务是识别和回收垃圾对象,以进行内存清理。为了让垃圾回收器可以正常且高效地执行,在大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程的执行,只有这样系统中才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于垃圾回收器更好地标记垃圾对象。因此,在垃圾回收时,都会产生应用程序的停顿。停顿产生时,整个应用程序会被卡死,没有任何响应,因 此这个停顿也叫作“Stop-The-World”(STW)。

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

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

相关文章

vue keep-alive多层级路由支持

keep-alive使用 属性值 1.include - 字符串或正则表达式。只有名称匹配的组件会被缓存。 2.exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。 3.max - 数字。最多可以缓存多少组件实例。 注:匹配首先检查组件自身的 name 选项,如果 nam…

用ab压测工具搞垮目标网站

一、介绍ab 命令会创建很多的并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问。它的测试目标是基于 URL 的,因此,既可以用来测试 Apache 的负载压力,也可以测试 nginx、lighthttp、tomcat、IIS 等其它 Web 服务器的压力。…

数据结构与算法——6.Comparable接口

这篇文章我们一起来看一下java中的Comparable接口 目录 1.学数据结构与算法的小套路 2.Comparable接口介绍 3.小结 1.学数据结构与算法的小套路 我们知道java是面向对象的,并且底层为我们封装了许多的方法。在java的开发工具包jdk中,已经给我们提供…

XGBoost简单介绍

1. 概述 XGBoost本身的核心是基于梯度提升树实现的集成算法,整体来说可以有三个核心部分:集成算法本身,用于集成的弱评估器,以及应用中的其他过程。 1.1 提升集成算法: XGBoost的基础是梯度提升算法,因此…

kali linux安装换源切换系统语言

安装 去官网 https://www.kali.org/ 找到自己合适的虚拟机版本,我们不要下载那个torrent,那个还要重新下载一遍 换源 sudo vim /etc/apt/sources.list 按 i 进入vim的编辑模式 用 # 把用来的注释掉,一定要去掉 在后面补上国内的源&#x…

【论文/写作】计算机论文写作全攻略总结

如果觉得我的分享有一定帮助,欢迎关注我的微信公众号 “码农的科研笔记”,了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【论文/写作】计算机论文写作全攻略总结 机器翻译学术论⽂写作⽅法和技巧 https://nlp.csai.tsinghua.edu.cn/~ly/tal…

一篇搞定ShardingSphere-jdbc 实战

谈到分库分表中间件时,我们自然而然的会想到 ShardingSphere-JDBC 。这篇文章,我们聊聊 ShardingSphere-JDBC 相关知识点,并实战演示一番。1 ShardingSphere 生态Apache ShardingSphere 是一款分布式的数据库生态系统,它包含两大产…

操作系统权限提升(二十三)之Linux提权-通配符(ws)提权

系列文章 操作系统权限提升(十八)之Linux提权-内核提权 操作系统权限提升(十九)之Linux提权-SUID提权 操作系统权限提升(二十)之Linux提权-计划任务提权 操作系统权限提升(二十一)之Linux提权-环境变量劫持提权 操作系统权限提升(二十二)之Linux提权-SUDO滥用提权 利用通配符…

redis的集群方式

1.主从复制 主从复制原理: 从服务器连接主服务器,发送SYNC命令; 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; 主服务器BGSAVE执行完后,向所有从服务…

阿里测试7年,薪资从7K到25K,我的成功值得每一个人借鉴

7年从业经验,这篇文章将汇集自动化测试所需知识,拒绝标题党,水文。让所有想学习提升技术的能从文中获取有价值的知识。 在这个吃技术的IT行业来说,我之前每天做的是最基础功能测试的工作,但是随着时间的消磨&#xff…

C++回顾(五)—— 构造函数和析构函数

5.1 构造和析构 5.1.1 构造函数 (1)定义 1)C中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;2)构造函数在定义时可以有参数;3)没有任何返回类型的…

第十届蓝桥杯省赛——5最大降水量(纯填空,分析)

题目:试题 E: 最大降雨量本题总分:15 分【问题描述】由于沙之国长年干旱,法师小明准备施展自己的一个神秘法术来求雨。这个法术需要用到他手中的 49 张法术符,上面分别写着 1 至 49 这 49 个数字。法术一共持续 7 周,每…

二叉树——二叉树的最近公共祖先

二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一…

如何使用DDexec在Linux上隐蔽运行二进制文件

关于DDexec DDexec是一种能够在Linux上使用无文件技术和隐秘技术运行二进制文件的方法,它可以使用dd工具来将Shell替换为其他进程。 众所周知,在Linux上运行一个程序,则这个程序必须以一个文件的形式存在,而且必须能够通过文件系…

电脑没有回收站找回删除文件的2种方法

最近后台收到了这样的咨询:”在网吧上网,删除东西的时候不小心把我的文件给删除了,但是桌面上没有回收站,怎么才能找回我的文件?“——针对“电脑没有回收站删除的东西怎么恢复”这种疑问?不妨看看下面数据…

环境搭建02-Ubuntu16.04 安装CUDA和CUDNN、CUDA多版本替换

1、CUDA安装 (1)下载需要的CUDA版本 https://developer.nvidia.com/cuda-toolkit-archive (2)安装 sudo sh cuda_8.0.61_375.26_linux.run(3)添加环境 gedit ~/.bashrc在文件末尾添加: ex…

【小程序】已有公众号认证,一步一步申请小程序(图文)

一、登陆公众号后台,找到左侧广告与服务,小程序管理,开通 二、选择快速注册认证小程序 三、快速创建 四、选择微信认证资质(复用),这样不用再付认证费了 五、需要一个新的邮箱,这点挺让人无语&a…

【Hello Linux】进程优先级和环境变量

作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:简单介绍下进程的优先级 环境变量 进程优先级环境变量进程的优先级基本概念如何查看优先级PRI与NINI值的设置范围NI值如何修改修改方式…

PT100铂电阻温度传感器

PT100温度传感器又叫做铂热电阻。     热电阻是中低温区﹡常用的一种温度检测器。它的主要特点是测量精度高,性能稳定。其中铂热电阻的测量精确度是﹡高的,它不仅广泛应用于工业测温,而且被制成标准的基准仪。金属热…

此网站可能不支持TLS1.2协议

问题描述 火狐浏览器版本:“97.0.1 (64 位)”,打开360网神设备Web管理地址时出现:“此网站可能不支持TLS1.2协议,而这是Firefox支持的最低版本。”,如下图所示。 原本是默认使用https协议打开的,看起来出问…