垃圾回收机制和常用的算法

一.什么是垃圾回收?

垃圾回收主要针对堆和方法区(非堆),程序计数器,虚拟机栈,本地方法栈这三个区域属于线程私有,随着线程的销毁,自然就会雄安会了,因此不需要堆着三个区域进行垃圾回收。

二.如何判断一个对象是否可以被回收

堆中几乎放着所有的对象实例(为什么这么说呢?因为现在有了JIT和标量替换等优化方法),对对垃圾回收前的第一步就是判断哪些对象已经死亡,即不能再被任何途径使用的对象。

引用计数算法

为对象添加一个引用计数器,当对象增加一个引用计数器就加一,引用失效就减一,引用计数为0的对象可被回收,在两个对象出现循环引用的情况下,此时引用计数器永远不会为0,导致无法对他们及逆行回收,就是因为有循环引用的存在,因此java虚拟机不使用引用计数算法。

public class Test {

    public Object instance = null;

    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        doSomething();
    }
}

上面的例子中,a,b引用对象示例互相持有了对象的引用,因此当我们把对a对象和b对象的引用去除之后,由于两个对象还存在互相引用,导致两个Test对象无法被回收

优点 执行效率高,缺点,无法解决循环引用,引起内存泄漏

可达性分析算法

通过判断对象的引用链是否可达来决定对象是否可以被回收

以GC roots 为起始点进行搜索,可达的对象都是存货的,不可达的对象可被回收?

Java 虚拟机使用可达性分析算法来判断对象是否可被回收,GC Roots 一般包含以下几种:

  • 虚拟机栈中局部变量表中引用的对象(栈帧中的本地方法变量表)

  • 本地方法栈中 JNI(Native方法) 中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中的常量引用的对象

  • 活跃线程的引用对象

方法区的回收

因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高

主要对常量池的回收和对类的卸载

为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。

  • 类卸载条件很多,需要满足以下三个条件,此时堆中不存在该类的任何示例。
  • 加载该类的ClassLoader已经被回收
  • 该类对应的Class对象没有任何地方引用了,也就是无法在任何地方通过反射访问该类的方法

finalize() 

用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。

当垃圾回收器宣告一个对象死亡时,至少要经理两次标记过程

如果对象在进行可达性分析以后,没有与GC root 直接相连接的引用,就会被第一次标记。并且判断是否执行finalize方法;

如果这个对象覆盖了finalize() 并且未被引用,就会放置F-Queue对象,稍后由虚拟机创建一个低优先级的finalize()线程去执行触发finalize()方法,在该方法中让对象重新被引用,从而实现自救,但是该线程的优先级比较低,执行过程随时可能被终止,此外,自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。

强引用

被强引用关联的对象不会被回收。

使用 new 一个新对象的方式来创建强引用。

当内存空间不足,JVM 抛出 OOM Error 终止程序也不会回收具有强引用的对象,只有通过将对象设置为 null 来弱化引用,才能使其被回收。

软引用

表示对象处在有用但非必须的状态。

被软引用关联的对象只有在内存不够的情况下才会被回收。可以用来实现内存敏感的高速缓存。

软引用可以和一个引用队列 ReferenceQueue 联合使用,如果软引用所引用的对象被垃圾回收,JVM 就会把这个软引用加入到与之关联的引用队列中。如果一个弱引用对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。

使用 SoftReference 类来创建软引用。

弱引用

表示非必须的对象,比软引用更弱一些。适用于偶尔被使用且不影响垃圾收集的对象。

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。

弱引用可以和一个引用队列 ReferenceQueue 联合使用,如果弱引用所引用的对象被垃圾回收,JVM 就会把这个弱引用加入到与之关联的引用队列中。如果一个弱引用对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。

使用 WeakReference 类来创建弱引用。

虚引用

又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。

不会决定对象的生命周期,任何时候都可能被垃圾回收器回收。必须和引用队列 ReferenceQueue 联合使用

为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知,起哨兵作用。具体来说,就是通过判断引用队列 ReferenceQueue 是否加入虚引用来判断被引用对象是否被 GC(垃圾回收线程) 回收:当 GC 准备回收一个对象时,如果发现它还仅有虚引用指向它,就会在回收该对象之前,把这个虚引用加入到与之关联的引用队列 ReferenceQueue 中。如果一个虚引用对象本身就在引用队列中,就说明该引用对象所指向的对象被回收了

使用 PhantomReference 来创建虚引用。

引用类型被垃圾回收的时间用途生存时间
强引用从来不会对象的一般状态JVM停止运行时终止
软引用在内存不足的时候对象缓存内存不足时终止
弱引用在垃圾回收的时候对象缓存GC运行后终止
虚引用Unknown标记、哨兵Unknown

垃圾回收算法

1.标记清除算法

 标记阶段,从根集合进行扫描,会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头打上标记

清除阶段,会进行对象回收并取消标志位,另外还会帕努但回收后的分块与前一个空闲分块是否连续,如果连续会合并这两个分块,回收对象就是把对象作为分块,连接到被称为空闲链表的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。

分配阶段,程序会搜索空闲链表寻找空间大于等于新对象大小size的块block,如果它找到的块等于size 会直接返回这个分块,如果找到大于size,就会对块分割成大小为size 与block-size的两部分,返回大小为size的分块,并把大小为(block - size) 的块返回给空闲链表。

不足:

  • 标记和清除过程效率都不高;

  • 会产生大量不连续的内存碎片,导致无法给大对象分配内存。

2.标记整理算法

‘ 

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:不会产生内存碎片

不足:需要移动大量对象,处理效率比较低。

复制算法

 

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

主要不足是只使用了内存的一半。

现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象

分代收集

现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法:

  • 新生代:新生代对象存活时间很短,所以可以选择“复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。

  • 老年代:老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

Stop-the-World & SafePoint

Stop-the-World

所谓 Stop-the-World(简称 STW),指的是 JVM 由于要执行 GC 而停止了应用程序的执行 :

  • 可达性分析算法中 GC Roots 会导致所有 Java 执行线程停顿,原因如下:

    • 分析工作必须在一一个能确保一致性的快照中进行

    • 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上

    • 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证

  • 被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样, 影响用户体验,所以需要减少 STW 的发生。

STW 事件和采用哪款垃圾收集器无关,所有的 GC 都有这个事件。哪怕是 G1 也不能完全避免 STW 情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。

STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。开发中不要用 System.gc()  这样会导致 STW 的发生。

目前,降低系统的停顿时间两种算法:增量收集算法和分区算法。

增量收集算法

基本思想:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。垃圾收集线程一次只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。

不足:由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

分区算法

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

注意分区算法与分代收集算法是不同的:分代收集算法将按照对象的生命周期长短划分成两个部分,而分区算法将整个堆空间划分成连续的不同小区间,其中每个小区间都独立使用,独立回收,这样可以控制一次回收多少个小区间。

总结:

  • 增量收集算法是将总的收集量一部分一部分的去执行

  • 分区算法是将总的内存空间分为小分区,一次可控的去收集多少个小区间。

SafePoint

程序执行时并非可以在任何地方都能停顿下来开始 GC,只有在特定的位置才能停顿下来开始 GC,这些位置称为Safepoint 。

SafePoint 的选择很重要,如果太少可能导致 GC 等待的时间太长,如果太频繁可能导致运行时的性能问题。

大部分指令的执行时间都非常短暂,通常会根据是否具有让程序长时间执行的特征为标准。比如选择一些执行时间较长的指令作为 SafePoint,如方法调用、循环跳转和异常跳转等。

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

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

相关文章

macOS 虚拟桌面黑屏(转)

转自:macOS重置虚拟桌面、macOS 虚拟桌面黑屏 有几次出现如图的情况,以为是iTerm的问题,但是在关闭软件,重启之后,依旧无效。 后面经过网友告知,才知道是虚拟桌面的问题。 为了清理这个问题,有以…

Apache Doris 巨大飞跃:存算分离新架构

作者:马如悦 Apache Doris 创始人 历史上,数据分析需求的不断提升(更大的数据规模、更快的处理速度、更低的使用成本)和计算基础设施的不断进化(从专用的高端硬件、到低成本的商用硬件、到云计算服务)&…

牛客网Verilog刷题——VL56

牛客网Verilog刷题——VL56 题目答案 题目 实现4bit无符号数流水线乘法器设计。电路的接口如下图所示: 输入输出描述: 信号类型输入/输出位宽描述clkwireInput1系统时钟信号rst_nwireInput1异步复位信号,低电平有效mul_awireInputsize乘数mu…

MFC第二十六天 CRgn类简介与开发、封装CMemoryDC类并应用开发

文章目录 CRgn类简介与开发CRgn类简介CRgn类区域管理开发CRgn类区域管理与不规则形状的选取 封装CMemoryDC类并应用开发CMemoryDC.h封装CMemoryDC开发游戏透明动画CFlashDlg.hCFlashDlg.cpp 封装CMemoryDC开发游戏动画 附录四大窗口CDC派生类 CRgn类简介与开发 CRgn类简介 CR…

常微分方程建模R包ecode(二)——绘制相速矢量场

本节中我们考虑一个更为复杂的常微分方程模型, d X C d t ν ( X A Y A ) − β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ) ⋅ X C , ( 1 ) d Y C d t β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ρ ) ⋅ Y C , ( 2 ) d X A d t g ⋅ X C − β ⋅ X A ⋅ ( Y C Y A …

Tcp的粘包和半包问题及解决方案

目录 粘包: 半包: 应用进程如何解读字节流?如何解决粘包和半包问题? ①:固定长度 ②:分隔符 ③:固定长度字段存储内容的长度信息 粘包: 一次接收到多个消息,粘包 应…

利用XSS在线平台获取用户cookie

//XSS弹窗&#xff1a; <script>alert("xss")</script> XSS漏洞&#xff1a; //XSS弹窗&#xff1a; <script>alert("xss")</script> //XSS在线平台&#xff1a; <ScRipT sRc//7ix7kigpovxdbtd32fuspgffmtmufo3wwzgnzaltddewtb…

推荐前端开发者提升效率的工具

是否掌握新的技术很大程度决定着你是否被淘汰。 虽然应用程序试图将网站替代&#xff0c;但前端 Web 开发业务仍在快速变化和增长&#xff0c;前端开发人员的功能并没有消失。以下介绍一款前端开发者提升效率的工具。 目录 一、低代码工具前景 二、如何理解低代码工具 三、前端…

【word技巧】如何做到,批量保存word文档图片

Word文件中有很多图片都需要保存&#xff0c;除了一张张的进行图片另存为以外&#xff0c;我们还有其他方法可以批量一次性保存word文档中的图片嘛&#xff1f;今天分享两个方法&#xff0c;批量保存word文档图片。 方法一&#xff1a; 将文件进行另存为&#xff0c;在选择路…

C++ 用指针处理数组元素

指针加减运算的特点使得指针特别合适于处理存储在一段连续内存空间中的同类数据。而数组恰好是具有一定顺序关系的若干同类型变量的集合体&#xff0c;数组元素的存储在物理上也是连续的&#xff0c;数组名就是数组存储的首地址。这样&#xff0c;便可以使用指针来对数组及其元…

Spring Boot 日志

Spring Boot 日志 ​ 在 Spring Boot 里面是有一个内置了的日志框架的&#xff0c;所以才能运行的时候在控制台打印出来。默认情况下的日志是系统定义和打印的&#xff0c;但我们也可以自行自定义打印日志。 日志的好处&#xff1a; 1、发现问题和定位问题&#xff1b;☆&am…

物联网工程开发实施,应该怎么做?

我这里刚好有嵌入式、单片机、plc的资料需要可以私我或在评论区扣个6 物联网工程的概念 物联网工程是研究物联网系统的规划、设计、实施、管理与维护的工程科学&#xff0c;要求物联网工程技术人员根 据既定的目标&#xff0c;依照国家、行业或企业规范&#xff0c;制定物联网…

NVIDIA 535.86.05 Linux 图形驱动程序改进 Wayland 支持

NVIDIA公司近日发布了适用于 Linux、FreeBSD 和 Solaris 系统的 NVIDIA 535.86.05 图形驱动程序&#xff0c;作为其生产分支的维护更新&#xff0c;解决了各种错误和问题。 NVIDIA 535.86.05 是在 NVIDIA 535.54.03 发布一个多月之后发布的&#xff0c;它通过解决在使用某些 W…

C高级--day3(shell中的输入、命令置换符、数组、算数运算、分支结构)

#!/bin/bash pls ~/ -l | grep "^-" | wc -l qls ~/ -l | grep "^d" | wc -l echo "普通文件个数&#xff1a;$p" echo "目录文件个数&#xff1a;$q"#!/bin/bash read file posexpr index $file \. strexpr substr $file $((pos1)) 2…

Meta AI研究团队新AI模型: Llama 2 大语言模型

Llama是Facebook Research团队开发的基础语言模型集&#xff0c;旨在提供广泛的语言理解能力。它基于转换器架构&#xff0c;参数范围从7B到65B。通过使用Llama模型&#xff0c;研究人员和开发人员可以构建更先进的自然语言处理系统。您可以在GitHub上找到相关的代码和资源&…

SpringBoot复习:(15)Spring容器的核心方法refresh是在哪里被调用的?

在SpringApplication的run方法&#xff1a; refreshContext代码如下&#xff1a; 其中调用的refresh方法代码如下&#xff1a; 其中调用的refresh方法代码如下&#xff1a; 其中调用的fresh方法代码如下&#xff1a; 其中调用了super.refresh();而这个super.refresh()就是…

数据结构 | 基本数据结构——队列

目录 一、何谓队列 二、队列抽象数据类型 三、用Python实现队列 四、模拟&#xff1a;传土豆 五、模拟&#xff1a;打印任务 5.1 主要模拟步骤 5.2 Python实现 一、何谓队列 队列是有序集合&#xff0c;添加操作发生在“尾部”&#xff0c;移除操作则发生在“头部”。新…

idea如何加快创建Maven项目的速度

一、下载archetype-catalog.xml 下载archetype-catalog.xml的地址 二、配置 以下所说的配置都指全局配置。 配置Maven -DarchetypeCataloglocal -Dfile.encodinggbk

靶形数独

题目描述 小城和小华都是热爱数学的好学生&#xff0c;最近&#xff0c;他们不约而同地迷上了数独游戏&#xff0c;好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了&#xff0c;于是他们向 Z 博士请教&#xff0c;Z 博士拿出了他最近发明的“靶形数独”&am…

Portraiture 4.0.3 for windows/Mac简体中文版(ps人像磨皮滤镜插件)

Imagenomic Portraiture系列插件作为PS磨皮美白必备插件&#xff0c;可以说是最强&#xff0c;今天它更新到了4.0.3版本。但是全网都没有汉化包&#xff0c;经过几个日夜汉化&#xff0c;终于汉化完成可能是全网首个Portraiture 4的汉化包&#xff0c;请大家体验&#xff0c;有…