JVM原理:JVM垃圾回收算法(通俗易懂)

news2024/11/17 21:48:02

目录

  • 前言
  • 正文
    • 垃圾标记算法
      • 引用类型
        • 强引用
        • 软引用
        • 弱引用
        • 虚引用
      • 引用计数法
        • 循环引用问题
      • 根可达性分析法
        • 虚拟机栈(栈帧的局部变量表)中的引用
        • 方法区中类静态属性引用
        • 方法区中常量引用
        • 本地方法栈(Native方法)引用
    • 垃圾回收算法
      • 标记清除算法
      • 复制算法
        • 复制算法和标记清除算法如何选择?
      • 标记整理算法
      • 分代收集算法
  • 总结

前言

为什么程序跑久了有时会变卡,如果你看过笔者的 《JVM运行时内存模型》那么应该知道用户线程在跑时会将new出来的对象放到虚拟机堆中,当堆满了之后会发生垃圾回收,而垃圾回收过程中会STW,也就是停止用户线程运行,这种在程序层面看来就是没反应,所以如果出现这种问题,我们就要想办法让STW的时间尽量的变短;

这分为几种情况,垃圾回收时间长或者垃圾回收的频率高;

垃圾回收时间长的情况?

可能是因为GC要扫描的区域比较大有,清理时间较长,或者说我们选择的垃圾回收器不适合当下情况

GC频率过高

可能有部分对象发生内存泄漏,导致无法回收,占用堆空间,此时的堆空间变小,满了就触发GC,频率自然高。也可能是年轻代和老年代空间分配不合理,空间过小导致频繁GC;

正文

认清原理,并接受原理,才能有效地做事情,所以我们要知道有哪些垃圾回收算法、哪些垃圾回收器,了解其特性,才可以帮忙我们进行程序优化。

垃圾标记算法

程序运行时,堆中存储了一堆对象,那垃圾回收器怎么知道哪些需要回收,不会发生误回收的情况呢?当一个实例对象有被引用时可以认为它是个活跃的状态,不进行回收。所以根据这个特性,可以添加一个标记专门记录被引用的次数,当次数不为0的就不进行垃圾回收,这种方式就是引用计数法。还可以在根节点挨个遍历,遍历到的对象进行标记,那些没遍历到的则被当作垃圾处理,这里的根节点可以简单理解为main方法里面开始的都是根,这种方式就是根可达性分析法;目前使用最多的是根可达性分析法;

引用类型

强引用

强引用我们平常用得最多,强引用在内存不足时,垃圾回收器不会进行回收,即使抛出OOM,因为该实例对象被引用,证明不是垃圾数据。

如:

Person person=new Person();

软引用

软引用在内存不足时(证明老年代满了,触发FULL GC),垃圾回收器会进行回收,所以垃圾标记算法在扫描标记时,判断如果引用为软引用,则把它当垃圾处理;

如:

Person person=new Person();

SoftReference<Person> sPerson=new SoftReference<>(new Persion());

弱引用

弱引用不管内存够不够时,这里内存够触发GC则证明年轻代满了触发YGC,内存不够则证明是老年代满了触发FULL GC,只要进行垃圾回收就会被当垃圾清除;

如:

Person person=new Person();

WeakReference<Person> sPerson=new WeakReference<>(new Persion());

虚引用

虚引用在垃圾回收时被清理,一般配合队列使用,在垃圾回收时会将其加入队列,此时队列可以拿到虚引用,但是虚引用指向的强引用Person已经被回收了,调用phantomReference.get()方法时获取不到实例对象;

 ReferenceQueue<Person> QUEUE = new RegferenceQueue<>();
 Person person=new Person();
 PhantomReference phantomReference = new PhantomReference(person, QUEUE);

引用计数法

我们在引用指向对象时就给该对象引用次数加1,在指引指向别的对象或变量销毁时将其-1,在垃圾回收器进行回收时,判断对象的引用标记数是否为0,如果为0则认为它是垃圾数据,对其进行回收;

优点:算法简单,判断搞笑

缺点:无法解决循环引用问题

循环引用问题

public class MyObject {
    public Object ref = null;
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person();
        person1.friend = person2;
        person2.friend = person1;
        person1 = null;
        person2 = null;
    }
}

在这里插入图片描述

虽然person1对象和person2对象已经没有任何引用指向它们了,但是此时它们的引用标记数依旧不为0,垃圾回收器认为它们不是垃圾,导致空间无法得到释放;

在这里插入图片描述

根可达性分析法

根可达性法以GC ROOTS节点为起点,开始往下扫描,遍历属性引用关系来判断对象是否为可回收的垃圾对象;如果把ROOTS比喻成江河的源头,那么那些未跟源头相通的河或者内湖就可以理解为垃圾对象,三色标记法是比较常用的根可达性分析法;

在这里插入图片描述

那么,GC Roots 是什么呢?

虚拟机栈(栈帧的局部变量表)中的引用

public class Demo {
    public static void main(String[] args) {
        // 创建实例A
        // a1:就是虚拟机栈(栈帧的局部变量表)中引用
        A a1 = new A();
        
        // 当 a1 不再指向实例A的时候,实例A 将不可达
        a1 = null;
    }
}

当开启一个新线程时,会创建线程私有的虚拟机栈,栈帧中维护着局部变量表,线程执行方法中的变量会记录到局部变量表里面,所以可以把局部变量表的变量理解为GC Roots,往下遍历其引用链,在引用链上的都是可用对象;

方法区中类静态属性引用

public class Demo {
    // 创建实例A
    // a2:方法区中类静态属性引用
    public static A a2 = new A();

    public static void main(String[] args) {
        // 当 a2 不再指向实例A的时候,实例A 将不可达
        a2 = null;
    }
}

当属性被static修饰时,其变量会放到方法区中的静态变量区中,静态变量区的变量指向的引用可以看作GC ROOTS;

方法区中常量引用

public class Demo {
    // 创建实例A
    // a3:方法区中常量引用
    public static final A a3 = new A();

    public static void main(String[] args) {
        // 当 a3 不再指向实例A的时候,实例A 将不可达
        a3 = null;
    }
}

当使用final修饰后,其变量会存储到方法区中的常量池中,所以方法区中常量引用可以看作GC ROOTS;

本地方法栈(Native方法)引用

本地方法栈和虚拟机栈类型,也会维护局部变量表,当本地方法区引用了堆中的对象时,可以把其当作GC ROOTS;

垃圾回收算法

标记清除算法

标记清除法分为了标记和清除两个步骤,首先采用可达性分析法进行标记后,对被标记为垃圾的对象进行回收;

在这里插入图片描述

黑色为已经扫描过的,有被引用的对象,灰色代表待扫描对象,白色代表待垃圾回收对象;当垃圾回收完成后,此时的空间就变得很碎片化,如果此时需要存入一个对象,该对象占用3个格子的空间,那么第一行的两格空间就没法被利用到;所以碎片化太严重的情况下,空间利用率低,GC的频率就会变高;

复制算法

在这里插入图片描述

复制算法会开辟两块内存空间,当标记完成之后,将存活对象拷贝到另外一个空间中,并将当前空间数据清除。这种方式可以解决内存碎片化问题,而且效率高。但意味着我需要更多的内存空间,比如1G的空间,由于需要复制就得拆分成两块500M的空间,所以也是很耗费内容的;

复制算法和标记清除算法如何选择?

一般如果程序对象一般占用空间较小的话,可以采用标记清除算法,这样空间利用率会比复制算法高;如果对象是比较大的话,选择复制算法的空间利用率会比较高,算法没有好坏,只有适不适合;

标记整理算法

在这里插入图片描述

标记整理算法在进行标记之后,将存活对象移动到一块,剩下的全部清理掉。这样就有碎片化带来的问题,也不像复制算法那样浪费空间;

分代收集算法

由于在垃圾回收时,会STW暂停用户线程,导致程序没响应,如果停顿时间过长,会导致服务宕机;所以堆空间过大会导致垃圾回收时间过长,所以将堆进行分代管理,分为年轻代和老年代;

在这里插入图片描述

当new对象时,会尝试将数据存储到Eden区,如果Eden区满了之后会触发YGC,年轻代采用复制算法,第一次将存活对象复制到survivor0区,第二次会将survivor0和Eden区存活的对象复制到survivor1区,就这样反复在survivor0和survivor1间复制。

当年轻代的存活对象存活时间较长时,导致年轻代剩余空间变小,YGC就会变得很频繁;针对这种情况,给每个存活对象设置存活年龄,每次YGC后未被回收将年龄+1,当年龄到达阈值时(默认15),将其迁移到年轻代中;

当老年代空间满了之后会触发FULL GC,此时会对年轻代和老年代进行垃圾回收,老年代的回收算法不同的垃圾回收器采用的机制不同;

总结

垃圾回收算法有多种,每种针对的场景不一样,没法说其好坏,只有综合业务场景,设备信息等因素挑选出适合程序的算法,现市面上垃圾回收器有多种就是针对不同场景而定制的。

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

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

相关文章

Java语法进阶及常用技术(八)--线程池

初识线程池 什么是“池” ---- 软件中的“池”&#xff0c;可以理解为计划经济。 我们的资源是有限的&#xff0c;比如只有十个线程&#xff0c;我们创造十个线程的线程池&#xff0c;可能我们的任务非常多&#xff0c;如1000个任务&#xff0c;我们就把1000个任务放到我们十个…

shell脚本学习记录(流程控制)

前言&#xff1a; 在shell脚本中&#xff0c;()、{}、[]都是用来表示命令或者变量的范围或者属性。它们的具体区别如下&#xff1a; ()&#xff1a;表示命令在子shell中运行。括号中的命令会在一个子shell中运行&#xff0c;并且该子shell拥符有自己的环境变量和文件描述&#…

【youcans动手学模型】DenseNet 模型-CIFAR10图像分类

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【youcans动手学模型】DenseNet 模型-CIFAR10图像分类 1. DenseNet 神经网络模型1.1 模型简介1.2 论文介绍1.3 改进方法与后续工作1.4 分析与讨论 2. 在 PyTorch 中定义 DenseNet 模型类2.1 DenseBlo…

性能测试实战——登录接口的性能测试(超详细总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在实际业务场景中…

python:六个模块,概括全书(上万字最详细版)

拍摄于——无锡南长街 文章目录 模块一&#xff1a;基础知识1、python语言2、常见数字类型3、字符串4、数字类型转换5、标识符命名6、常见关键字7、运算符与表达式&#xff08;1&#xff09;算术运算符&#xff08;2&#xff09;关系运算符&#xff08;3&#xff09;逻辑运算符…

循序渐进,搞懂什么是动态规划

循序渐进&#xff0c;搞懂什么是动态规划 写在前面 温馨提示&#xff0c;本文的篇幅很长&#xff0c;需要花很长的时间阅读。如果要完全理解所有内容&#xff0c;还需要花更多的时间学习。如果打算认真学习动态规划&#xff0c;又不能一次看完&#xff0c;建议您收藏本文以便后…

《深入理解计算机系统》(6)存储器层次结构

1、存储技术 随机访问存储器&#xff0c;分为两类&#xff1a; RAM&#xff0c;同时也是易失性存储器&#xff0c;也分为两类&#xff1a; - SRAM&#xff1a;静态随机访问存储器&#xff0c;速度快&#xff0c;价格高。多用来作为高速缓存存储器。 - DRAM&#xff1a;动态随机…

WinDbg安装入坑1(C#)

由于作者水平有限&#xff0c;如有写得不对的地方&#xff0c;请指正。 使用WinDbg的过程中&#xff0c;坑特别的多&#xff0c;对版本要求比较严格&#xff0c;如&#xff1a; 1 32位应用程序导出的Dump文件要用32位的WinDbg打开&#xff0c;想要没有那么多的问题&#xff…

chatgpt赋能python:Python删除内容:掌握三种删除方式

Python删除内容&#xff1a;掌握三种删除方式 删除变量中的值 删除变量中的值是Python编程中常见的操作。Python提供了del语句用于删除变量中的值&#xff1a; x "Hello World" del x上述代码中&#xff0c;del x语句将删除变量x中的值。如果我们在执行print(x)时…

从C语言到C++_18(stack和queue的常用函数+相关练习)力扣

目录 1. stack 1.1 栈的概念 1.2 stack 的介绍和使用 2. queue 2.1 队列的概念 2.2 queue 的介绍和使用 3. 栈和队列的相关选择题 答案&#xff1a; 4. 栈和队列的相关OJ题 155. 最小栈 - 力扣&#xff08;LeetCode&#xff09; 解析代码&#xff1a; 剑指 Offer 3…

python学习-代码调试器

目录 为什么学习调试器Pycharm Debugger示例所用代码布局调试工具栏 Debug Bar程序控制工具栏 pdb查看源代码 l list查看当前函数源代码 ll longlist打印变量 p查看调用栈w where向上移动当前帧 u up向上移动当前帧 d down运行当前行代码,在第一个可以停止的位置停下 s step继续…

Selenium基础篇之八大元素定位方式

文章目录 前言一、如何进行元素定位&#xff1f;1.右击元素-检查2.F12-选择工具点击元素3.借助selenium IDE 二、八大元素定位方式1.ID1.1 方法1.2 举例1.3 代码1.4 截图 2.NAME2.1 方法2.2 举例2.3 代码2.4 截图 3.CLASS_NAME3.1 方法3.2 举例3.3 代码3.4 截图 4.TAG_NAME4.1 …

再也不用担心组件跨层级的数据共享和方法驱动了

文章目录 兄弟组件的传值和方法调用多个独立组件的数据共享和方法调用多个组件内的方法和数据互相驱动&#xff1a;eventBus多个组件的数据共享以及数据修改&#xff1a;vuex 项目中关于组件的使用经常会碰到这种情况&#xff1a;父子组件传和方法调用、兄弟组件的传值和方法调…

Selenium该如何实现微博自动化运营:关注、点赞、评论

目录 前言&#xff1a; Selenium 是什么&#xff1f; 一、核心代码 二、步骤分解 三、自动化运营常用工具 前言&#xff1a; 使用 Selenium 实现微博自动化运营&#xff0c;可以提高效率、减少工作量&#xff0c;下面讲解如何使用 Selenium 实现微博的关注、点赞和评论功…

webSocket 学习

引子 WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它是 HTML5 中的一种新特性&#xff0c;能够实现 Web 应用程序和服务器之间的实时通信&#xff0c;比如在线聊天、游戏、数据可视化等。 相较于 HTTP 协议的请求-响应模式&#xff0c;使用 WebSocket 可以建…

JVM零基础到高级实战之Java内存区域虚拟机栈

JVM零基础到高级实战之Java内存区域虚拟机栈 JVM零基础到高级实战之Java内存区域虚拟机栈 文章目录 JVM零基础到高级实战之Java内存区域虚拟机栈前言JVM内存模型之虚拟机栈总结 前言 JVM零基础到高级实战之Java内存区域虚拟机栈 JVM内存模型之虚拟机栈 虚拟机栈是什么&#x…

Ansys Lumerical | 光纤布拉格光栅温度传感器的仿真模拟

说明 该示例演示了一种基于光纤布拉格光栅&#xff08;FBG&#xff09;的温度传感器&#xff0c;因为光纤折射率会随温度而变化&#xff0c;导致其布拉格波长发生偏移&#xff0c;所以可以被用作温度的测量。&#xff08;联系我们获取文章附件&#xff09; 综述 在本示例中要考…

java八股文-并发篇

并发篇 1. 线程状态 要求 掌握 Java 线程六种状态掌握 Java 线程状态转换能理解五种状态与六种状态两种说法的区别 六种状态及转换 分别是 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态此时未与操作系统底层线程关联 可运行 调用了 start …

在Linux上安装Zookeeper集群(zookeeper-3.5.9)

记录&#xff1a;455 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用zookeeper-3.5.9版本&#xff0c;在三台机器上&#xff0c;安装Zookeeper集群。 版本&#xff1a;zookeeper-3.5.9&#xff0c;CentOS 7.9,Linux kernel-5.4.218。 1.主机规划 目标&#xff1a…

接口自动化测试新玩法!Python构建mock服务让你的测试更加高效!

目录 引言 Flask mock接口开发示例 引言 Mock 即模拟&#xff0c;就是在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个虚拟的对象来创建以便测试的测试方法&#xff0c;其最大的优势就是降级前后端耦合度&#xff0c; 使前端工程师可以不…