【Java虚拟机】JVM垃圾回收机制和常见回收算法原理-程序员宅基地

技术标签: jvm  算法  java  # JVM专栏  

1.垃圾回收机制

(1)什么是垃圾回收机制(Garbage Collection, 简称GC)

  • 指自动管理动态分配的内存空间的机制,自动回收不再使用的内存,以避免内存泄漏和内存溢出的问题
  • 最早是在1960年代提出的,程序员需要手动管理内存的分配和释放
  • 这往往会导致内存泄漏和内存溢出等问题,同时也增加了程序员的工作量,特别是C++/C语言开发的时候
  • Java语言是最早实现垃圾回收机制的语言之一,其他编程语言,如C#、Python和Ruby等,也都提供了垃圾回收机制

(2)JVM自动垃圾回收机制

  • 指Java虚拟机在运行Java程序时,自动回收不再使用的对象所占用的内存空间的过程

  • Java程序中的对象,一旦不再被引用会被标记为垃圾对象,JVM会在适当的时候自动回收这些垃圾对象所占用的内存空间

  • 优点

    • 减少了程序员的工作量,不需要手动管理内存
    • 动态地管理内存,根据应用程序的需要进行分配和回收,提高了内存利用率
    • 避免内存泄漏和野指针等问题,增加程序的稳定性和可靠
  • 缺点

    • 垃圾回收会占用一定的系统资源,可能会影响程序的性能
    • 垃圾回收过程中会停止程序的执行,可能会导致程序出现卡顿等问题
    • 不一定能够完全解决内存泄漏等问题,需要在编写代码时注意内存管理和编码规范

(3)引用计数法

  • 基本思想,跟踪每个对象被引用的次数,当引用次数为0时,就可以将该对象回收
  • 在JVM中,每个对象都有一个引用计数器,当对象被引用时,引用计数器+1
  • 当对象被取消引用时,引用计数器-1
  • 当引用计数器为0时,该对象就可以被回收
  • 优点
    • 实现简单,回收垃圾的效率高
  • 缺点
    • 循环引用无法回收。如果两个对象互相引用,它们的引用计数器永远不会为0,因此无法被回收
    • 引用计数器开销大,每个对象都需要一个引用计数器,如果对象很多,开销就会很大

在这里插入图片描述

public class Main {
    
    public static void main(String[] args) {
    
        A a = new A();
        B b = new B();
        a.setB(b);
        b.setA(a);
        a = null;
        b = null;
        System.gc();
    }
}

class A {
    
    private B b;

    public void setB(B b) {
    
        this.b = b;
    }
}

class B {
    
    private A a;

    public void setA(A a) {
    
        this.a = a;
    }
}
  • 类A和类B相互引用,每个对象都持有对方的引用,形成了一个循环引用的环,当Main方法执行完毕后,a和b对象都置为null
  • 由于它们相互引用,它们的引用计数器都不为0,无法被垃圾回收器回收,导致内存泄漏
  • 但是上面代码却不会发生内存泄漏,因为多数jvm没有采用这个引用计数器方案,而是采用可达性分析算法

(4)可达性分析算法

  • 可达性分析算法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索。
  • 如果“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。
  • 被判定为不可达的对象要成为回收对象,要至少经历两次标记过程。
  • 如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。

在这里插入图片描述

(5)什么是GC Root

  • 指一些被JVM认为是存活的对象,它们是垃圾回收算法的起点
  • 可以理解为由堆外指向堆内的引用, 本身是没有存储位置,都是字节码加载运行过程中加入 JVM 中的一些普通引用
  • 通俗的例子可以是一个树形结构,树的根节点就是GC Roots
  • 是垃圾回收器的起点,如果一个节点没有任何子节点与根节点相连,那这个节点就被认为是不可达的,可以被回收器回收

(6)JVM中的GCRoots对象有哪几种

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

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

    • JDK 1.7 开始静态变量的存储从方法区移动到堆中
    • 比如你定义了一个static 的集合对象,那里面添加的对象就是可以被GC Root可达的
  • 方法区中常量引用的对象

    • 字符串常量池从 JDK 1.7 开始由方法区移动到堆中
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

(7)对象可回收,就一定会被回收吗?

  • 不一定会回收,对象的finalize方法给了对象一次最后一次的机会。
  • 当对象不可达(可回收)并发生 GC 时,会先判断对象是否执行了 finalize 方法,如果未执行则会先执行 finalize 方法
  • 将当前对象与 GC Roots 关联,执行 finalize 方法之后,GC 会再次判断对象是否可达
  • 如果不可达,则会被回收,如果可达,则不回收!
  • 需要注意的是 finalize 方法只会被执行一次,如果第一次执行 finalize 方法,对象变成了可达,则不会回收
  • 但如果对象再次被 GC,则会忽略 finalize 方法,对象会被直接回收掉!
2.垃圾回收算法

(1)垃圾回收算法之标记-清除算法

  • 是一种常见的垃圾回收算法,它的基本思路是分为两个阶段:标记阶段和清除阶段
  • 标记阶段
    • 垃圾回收器会从一些GC Roots对象开始,遍历整个对象图,标记所有被引用的对象
    • 被标记的对象会被打上标记,表示这些对象是“活”的对象,需要保留下来,未被标记的对象就是垃圾对象,可以被回收
  • 清除阶段
    • 垃圾回收器会对所有未被标记的对象进行回收

在这里插入图片描述

  • 优点
    • 能够回收不连续的内存空间
  • 缺点
    • 标记和清除两个步骤,都需要垃圾回收器遍历整个对象图,耗费时间较长
    • 会产生内存碎片,当频繁进行垃圾回收时,内存碎片会越来越多导致可用内存空间不足,从而影响程序的性能和稳定性
  • 应用场景
    • 在实际应用中,标记清除法一般用于不需要频繁进行垃圾回收的场景,比如在Java堆中大对象的分配和回收
    • 后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进

(2)垃圾回收算法之标记-复制算法

  • 标记算法是一种常见的垃圾回收算法,它的基本思路是将Java堆分为两个区域:一个活动区域和一个空闲区域
  • 在垃圾回收过程中,首先标记所有被引用的对象
  • 然后将所有被标记的对象复制到空闲区域中,最后交换两个区域的角色,完成垃圾回收
  • 标记复制算法的详细实现步骤
    • 将Java堆分为两个区域:一个活动区域和一个空闲区域,初始时,所有对象都分配在活动区域中
    • 从GC Roots对象开始,遍历整个对象图,标记所有被引用的对象
    • 对所有被标记存活的对象进行遍历,将它们复制到空闲区域中,并更新所有指向它们的引用,使它们指向新的地址
    • 对所有未被标记的对象进行回收,将它们所占用的内存空间释放
    • 交换活动区域和空闲区域的角色,空闲区域变为新的活动区域,原来的活动区域变为空闲区域
    • 当空闲区域的内存空间不足时,进行一次垃圾回收,重复以上步骤。

在这里插入图片描述

  • 优点

    • 如果内存中的垃圾对象较多,需要复制的对象就较少,则效率高
    • 清理后,内存碎片少
  • 缺点

    • 标记复制算法的效率较高,但是预留一半的内存区域用来存放存活的对象,占用额外的内存空间
    • 如果出现存活对象数量比较多的时候,需要复制较多的对象 效率低
    • 假如是在老年代区域,99%的对象都是存活的,则性能底,所以老年代不适合这个算法
  • 应用场景

    • 标记复制算法一般用于新生代的垃圾回收,因此需要对新生代的对象进行分代管理

    • 虚拟机多数采用这个算法,对新生代进行内存管理,因为多数这个新生代区域的存活对象数量少

    • 国外有公司统计过多数业务,98%撑不过一次GC; 所以不用1:1比例分配新生代的空间

    • 原因

      • GC时, 将Eden和Survivor中存活对象一次性复制到另外一块Survivor空间上, 然后清理掉Eden和已用过的那块Survivor空间
      • 每次新生代中可用内存空间为整个新生代容量的90% (Eden的80% + Survivor的 10%) ,
      • 只有一个Survivor空间, 即10%的新生代是会被浪费而已

在这里插入图片描述

(3)垃圾回收算法之标记-整理算法

  • 从根节点开始对所有可达对象做一次标记,但之后并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端
  • 然后清理边界外的垃圾,避免了碎片的产生,也不需要两块相同的内存空间,因此性价比比较高

在这里插入图片描述

  • 优点
    • 解决了标记清除算法的碎片化的问题
    • 对比标记-复制算法,不用浪费额外的空间
    • 对比 标记-清除算法与标记-整理算法,前者是一种非移动式的回收算法, 而后者是移动式的回收,且没有了碎片化内存
  • 缺点
    • 效率相比于标记复制算法低一些
    • 在整理存活对象时,因对象位置的变动,需要该调整虚拟机栈中的引用地址
  • 应用场景
    • 标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。

(4)垃圾回收算法之分代收集算法

  • 针对不同生命周期的对象采用不同的垃圾回收策略,以达到更好的垃圾回收效果
  • 年轻代多数对象存活时间短,高频进行回收;老年代多数对象存活时间久,进行低频回收
  • 分代算法是根据回收对象的特点进行选择,年轻代适合标记-复制算法,老年代适合标记清除或标记压缩算法
  • 通过将内存划分为不同的代,可以使得Minor GC的频率更高,更早地回收垃圾对象,减少Full GC的发生频率,提高整体性能

在这里插入图片描述

  • JVM将内存分为年轻代和老年代,其中年轻代又分为Eden区和两个Survivor区
  • 年轻代
    • 用于存放新生的对象,其中Eden区是新对象的分配区域
    • 当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,同时清空Eden区
    • Survivor区则用于存放经过一次Minor GC后存活的对象
  • 老年代
    • 用于存放长时间存活的对象,当年轻代中的对象经过多次Minor GC后仍然存活,就会被移动到老年代
    • 当老年代满时,会触发Full GC
  • GC的分类和专业术语
    • 部分收集(Partial GC)
      • 新生代收集
        • 对象从Young 区域消失的过程称为”minor GC / Young GC
        • Eden 的清理,S0\S1的清理都由于MinorGC
        • YoungGen区内存不足,会触发minorGC
      • 老年代收集
        • 对象从老年代中消失的过程,清理整合OldGen的内存空间,称为**”Major GC/Old GC“**
        • 有些垃圾收集器 针对老年代单独回收,所以比较少用
    • 整堆收集(Full GC)
      • 是清理整个堆空间,包括年轻代和老年代
      • 理解为Major GC+Minor GC组合后进行的一整个过程,是清理JVM整个堆空间
    • FullGC触发场景
      • 手工调用System.gc( ), 建议执行Full GC,不一定会执行,可通过-XX:+ DisableExplicitGC 参数来禁止调用System.gc()
      • 老年代空间不足 , 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • STW(Stop The World)
      • 垃圾回收发生过程中,用户线程在运行至安全点(safe point)后,就自行挂起进入暂停状态,对外的表现就是卡顿
      • 所以应尽量减少Full GC的次数,是Minor GC还是Major GC都会STW,区别只在于STW的时间长短
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47533244/article/details/130467721

智能推荐

PHP使用gearman扩展完成异步任务总结_php5 gearman-程序员宅基地

文章浏览阅读2.2k次。PHP的gearman扩展,可以在Linux服务器上,实现PHP脚本的异步任务,甚至是分布式异步任务。在项目中一些响应慢,或者是占用时间的PHP脚本,可以用异步任务去完成,用户访问时不用等待漫长的队列任务,因为在服务器上有专门跑这些异步任务的脚本。1、安装能执行任务的job(用于执行“work”)#wget http://launchpad.net/gearmand/tru_php5 gearman

【无标题】针对MNIST数据集,构造卷积神经网络实现手写数字识别。_从网上下载或自己编程实现一个卷积神经网络并在手写字符识别数据 mnist上进行实验-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏31次。以pytorch构建的两层卷积核,一层池化层,两层全连接层的神经网络,在Mninst数据 集上训练,准确率达到98%,而且在增加旋转的Mninst数据集上达到97%的准确率。_从网上下载或自己编程实现一个卷积神经网络并在手写字符识别数据 mnist上进行实验

IDEA专栏—IDEA导入maven工程发现Java文件颜色不对_maven颜色-程序员宅基地

文章浏览阅读1.2k次。如下图设置,File Setting中:或者在我们导入项目时,idea右下角界面会弹出提示框,询问我们是否要自动导入,选择Enable Auto-imort即可,如下图。但是这里选择仅仅是针对当前项目自动导入,所以还是乖乖在file setting中设置自动导入最为保险。_maven颜色

详解@JsonProperty、@JsonFormat 和 @DateTimeFormat 注解用法-程序员宅基地

文章浏览阅读1.4w次,点赞6次,收藏37次。详细解释 @JsonProperty、@JsonFormat 和 @DateTimeFormat 注解的用法_@jsonproperty

Linux使用docker搭建Mysql主从复制_linux 实现docker的mysql主从复制-程序员宅基地

文章浏览阅读326次。Linux使用docker搭建Mysql主从复制_linux 实现docker的mysql主从复制

C++ :error C3872: '0x3000': this character is not allowed in an identifier 包含中文全角空格错误【已解决】_character <u+ff08> not allowed in an identifier-程序员宅基地

文章浏览阅读870次。error C3872: ‘0x3000’: this character is not allowed in an identifier写的serialdll.cpp文件中报错如下,一大堆错误,但是反复查看了那几行代码,甚至重新写了一遍,还是报错。在网上帖子说可能是代码中有中文的全角空格,导致报错。但使用Ctrl + H查找当前的cpp文件,完全没有找到中文全角空格;继续排查原因,发现..._character not allowed in an identifier

随便推点

关于Ionic cordova 的一些基本问题_关于cordova:ionic-(仅适用于ios)仍在应用程序第一个可见模板的背景中显示闪屏中-程序员宅基地

文章浏览阅读252次。新建项目 -vx 表示指定第x个ionic版本ionic start myAwesomeApp --vx(不指定会提示使用哪个版本)ionic创建项目报错Error: read ECONNRESET at _errnoException (util.js:992:11) at TLSWrap.onread (net.js:618:25)–> 版本4.1.1 降级为3.9.1 !!!..._关于cordova:ionic-(仅适用于ios)仍在应用程序第一个可见模板的背景中显示闪屏中

光线追踪渲染实战(五):低差异序列与重要性采样,加速收敛!_软光追,bvh可视化,gpu实现,brdf,重要性采样以及差异序列-程序员宅基地

文章浏览阅读5.2k次,点赞25次,收藏26次。1. 低差异序列具有很好的空间填充性质,使用 sobol sequence 作为生成半球采样样本的随机数 2. 使用重要性采样策略对 Disney BRDF 进行采样,大大加速收敛的过程 3. 预计算 hdr 贴图的重要性采样样本,直接采样光源,对 hdr 贴图高亮处分配更多的光线 4. 使用多重重要性采样策略混合 brdf 采样和 hdr 采样,适应粗糙度和不同大小的光源,加速光线追踪的收敛_软光追,bvh可视化,gpu实现,brdf,重要性采样以及差异序列

My97DatePicker日期控件使用方法_my97datepicker设置选择今天以后-程序员宅基地

文章浏览阅读660次。-------------------------------引入JS------------------------ ------------------使用控件------------------------------- class="Wdate" size="10" readonly="readonly" style="height:15px;wi_my97datepicker设置选择今天以后

医学图像分割模型:U-Net详解及实战-程序员宅基地

文章浏览阅读6k次,点赞11次,收藏122次。2015年U-Net的出现使得原先需要数千个带注释的数据才能进行训练的深度学习神经网络大大减少了训练所需要的数据量,并且其针对神经网络在图像分割上的应用开创了先河。当时神经网络在图像分类任务上已经有了较好的成果,但在很多视觉的任务中由于输出需要进行定位,也就是每个像素需要分配一个类标签,这导致成千上万的训练图像在生物医学任务中通常难以获得,从而急需要一个神经网络,它不需要那么多的数据来进行训练却依旧有较好的效果,这就导致了U-Net的诞生。U-Net几乎是当前segmentation项目中应用最广的模型。_医学图像分割

qt 实现RTSP&RTMP拉流,实时显示视频流_qt rtsp拉流-程序员宅基地

文章浏览阅读2.5w次,点赞22次,收藏202次。最近项目需求,要实现一个rtsp视频流,经过一番了解之后,最后选择两种方式进行测试对比,一个是基于ffmpeg编码实现rtsp拉流,另外一个则是基于VLC开源的qt第三方库,实在github上搜索到的 key: qt vlc。首先粗略讲下ffmpeg编码怎么实现rtsp拉流呢?没有接触之前,感觉很高深的样子,其实并不然,ffmpeg内部基本都帮你实现了,类似打开摄像头一样的流程,使用avfor..._qt rtsp拉流

Java操作本地windows系统的cmd命令-程序员宅基地

文章浏览阅读10w+次,点赞2次,收藏11次。人工智能,零基础入门!http://www.captainbed.net/inner一、windows系统下运行cmd命令,直接打开cmd窗口输入对应命令执行就可以了,步骤:【1】输入快捷键:windows + R,如何输入cmd,按回车【2】然后会进入cmd命令的窗口界面:【3】执行一些cmd命令,看效果如下显示:二、但是在Java程序中如何执行本地windo...

推荐文章

热门文章

相关标签