【JVM神秘大门】Java虚拟机原理保姆式教学,零基础速成GC机制(下篇)

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

引言

当我们谈论 Java 时,JVM 原理就像是一扇神秘的大门,通向程序运行的深层世界。在这扇门后,隐藏着无数的奥秘和惊喜,等待着我们去探索。让我们一起推开这扇门,揭开 JVM 原理的神秘面纱吧!

目录

  1. Java的垃圾回收机制

  2. 怎么找出 “垃圾”

  3. 如何释放资源空间

  4. JVM 的垃圾回收器

一. Java 的垃圾回收机制

1. 垃圾回收的引入

试想一下, 如果像之前学过的C语言当我们开辟一定的内存, 而忘记了 free 释放内存, 就很容易 让这一段内存空间无法利用, 而浪费掉, 从而出现 内存泄漏问题

那么在Java 中, JVM 就实现这样的机制: 可以让程序猿大胆的 new 对象 , 不用考虑内存泄漏问题, JVM 会自动的去释放内存中 == 已经不再使用的内存空间==。 而我们把这样的机制就称之为: 垃圾回收机制 简称为: GC

2. GC的回收区域

我们知道在一块内存区域中, 最占内存空间的当然是对象, 而GC 的维度也是以 对象 为维度进行回收释放的。

这时我们就需要考虑一点, 竟然GC 是 以对象为维度的 , 那么我们可以思考下GC 的主战场是:

程序计数器和栈都是 随着线程的创建而创建的 , 只要 线程结束就自动销毁 的。 这里就不需要考虑。

而方法区一般是 存放静态成员变量的创建的内存空间很小。 不需要去 GC 去释放。

而真正要 GC 释放的就是 主要存放着对象的 , 才是 GC 的主战场。

是的,入上图, 不在使用的对象就会被GC 一点一点的回收 , 其中有一部分骑墙派(处在边界的) 也认为 不回收

虽然 GC 很方便 , 有利于开发人员的开发效率, 但是 GC 的创建和使用也是需要消耗一定的时间的 , 很有可能在程序 正在执行的过程JVM 进行GC 操作 , 就会产生 STW 问题。 STW 就相当于 《黑神话. 悟空》 中的定身术,也就是说会使 程序出现卡顿 , 会 额外产生 因为 GC额外时间消耗

所以在一些对时间要求比较高的场景, 例如C++ 的开发就没有用到 GC 机制

鱼式疯言

总而言之,言而总之

GC 能 提高开发效率, 但是 影响运行效率


垃圾 是什么? 不是我们日常生活中的垃圾, 而是在 内存区域中不再使用的对象 , 我们称之为 垃圾

在JVM 实现 GC 机制的原理中, 我们需要探讨的两个过程:

  1. 怎么找出垃圾

  2. 怎么找到垃圾后释放

下面 让我们来学习学习吧~

二. 怎么找出 “垃圾”

关于怎么找出 “垃圾” , 这里小编讲解两个平常开发中常用的两种寻找 “垃圾” 的方案:

  • 引用计数法

  • 可达性分析

  • 1. 引用计数法

    <1>. 原理流程

    引用计数的过程就是:

    每一个对象 分配一个 计算器count

    如果一个对象 每添加一个引用了就 count++ , 每释放一个引用 count--

    当这个对象的 count0 时, 就说明 这个对象没有被引用 , 就把这个对象标记为 “垃圾”

    <2>. 原理分析

    如上图, 给每一个对象都分配内存资源, 只要 count为0 就释放该对象的内存资源。

    但是小伙伴们有没有 考虑一点就是: 要给每一个对象都分配一个 count 这样的计数器。

    但是如果对象很多呢? 是不是会消耗额外的很多内存资源来给计数器空间, 比如我只是一个 Integer 类型的数据, 但是却是用 short 来计数, 就会额外消耗 2 byte 的内存空间。

    并且有可能会出现两个对象互相引用的情况, 当出现这样的情况 计数器就可能 count 就不可能为 0

    所以上面的情况引用计数的说明问题:

  • 消耗 额外的内存资源

  • 出现 对象成环 的现象

  • 出现像上面互相引用的情况, JVM 就没有采用这样的 查找 “垃圾” 的机制

    所以下面小编就要介绍 =第二个排查垃圾 的方法: 可达性分析

    2. 可达性分析

    <1>. 原理流程

    可达性分析是什么?

    简单理解就是 点名, 就是 班级上课的点名

    比如现在班级的纪委有一个 班上所有人的名单 , 现在要开始上课了

    纪委开始点名, 如果 这个对象答到了 , 就说明 这个对象被引用上了不是 “垃圾”

    如果这个 对象没有答道 , 就说明 这个对象没有被引用上 , 就标记为 “垃圾” , 后面就需要被 释放内存空间

    有了这些理解, 下面我们就可以正式讲解 JVM 的GC 的可达性分析 了。

       TreeNode root  = new TreeNode(a);
            root.left = new TreeNode(b); 
            root.right = new TreeNode(c);
            root.left.left = new TreeNode(d);
            root.left.right = new TreeNode(e);
            root.right.left = new TreeNode(f);
            root.right.right = new TreeNode(g);
    

    如上图, 这棵树的右子树断开连接了。

    首先, 在JVM 中存有一份所有对象的名单 , JVM 会创建一个线程 , 让这个线程从 root 根节点开始遍历对象, 只要遍历过的对象, 就相当于喊到了没有遍历到的对象就相当于没有喊到

    那么上图也就是 root.right = null , 那么 c, g 和 f 也就和 root 断开了, 没有被引用到 , 相当于 c , f , g 都没有被引用到 , 都视为 “垃圾” , 这样的方式就称之为: 可达性分析

    鱼式疯言

    栗子理解

    想以前的抗日战争, 在地下党中, 一般都是从 上级去联系下级的一旦上级联系不到下级了, 为了保守组织安全 , 就会把没有联系到的视为已经被敌人给抓捕了, 也就视为 “垃圾” , 那么换做我们的 可达性分析 也是如此。

    <2>. 原理分析

    虽然可达性分析不是很占内存空间 , 但是从 GC root 从上往下搜索, 如果对象很多的话, 就会 消耗大量的时间

    所以一般而言 , GC root 会有很多个, 一般来自 栈的局部变量方法区的静态变量常量池的引用 都可以作为 GC root , 并且准确来说并不是以上面树的方法来搜索, 而是以 图的方式来遍历

    鱼式疯言

    对于上述的两种查找 “垃圾”的方案。

    Java 采用的 GC 是 可达性分析

    PythonPHP 等其他 编程语言的GC 采用的就是 引用计数法

    所以当我们谈及 GC 的时候, 一定要指明是 哪个编程语言 的 !


    谈及完如何找 “垃圾” ,下面我们就谈谈找到垃圾之后, 如何去 清理垃圾, 释放内存空间的方案 吧 。

    三. 如何释放资源空间

    释放内存空间的方式很多种, 其中主流的有三种:

  • 标记清除

  • 复制算法

  • 标记整理

  • 分代回收

  • 1.标记清除

    所以如上图: 假设 JVM 已经通过可达性分析来确定了哪些对象是 “垃圾” , 并做好的标志 , 所以接下来只需要 把标记好的对象进行释放 即可。

    标记清除 虽然是一个不是很复杂的事情, 但是 释放完这段内存空间会不会造成新开辟的内存空间造成影响 , 这是我们需要考虑的问题 ? ? ?

    答案: 会的

    当我们把一段 需要释放的内存空间释放掉 之后, 就会使 整个内存空间不连续, 就会使内存被 分割成一段一段的碎片 , 而我们通常把这种问题称之为: 内存碎片问题

    内存碎片问题就很有可能会使 需要新开辟的内存申请不下来 , 就好比现有 3MB 的内存, 但是由于 内存碎片问题 , 当我需要申请2 MB 的内存时, 这2 MB 的内存中 有部分的内存是正在使用的 , 并 没有被释放掉 , 就会 申请失败


    知道了内存碎片问题, 接下来的几个方案就是针对内存碎片问题所采取的。

    2. 复制算法

    如上图: 针对标记清除, 我们采取了复杂算法:

    1. 首先将整个内存空一分为二 , 左边为 正在使用的内存区域 , 右边为 剩余区域
    1. 然后将左边 没有标记为 “垃圾” 的对象顺序的转移到右边
    1. 然后 左边一整块的区域全部释放掉 即可。

    这里就有效的解决 内存碎片问题 ~

    但是小伙伴们有没有思考过, 如果 剩余的对象很多 , 那么需要搬运的这些 对象所消耗的时间岂不是很大 , 那么造成的 STW 是很明显的。

    3. 标记整理

    标记整理 就好似顺序表的 删除操作 , 也就是每 GC 一次 “对象” , 就整理一次(像 顺序表 一样进行 挪动数据 ,是 内存空间连续 )。

    上述两种方案在 对象量级很大 的情况, 就会产生 大量时间的消耗, 那么这最后一种方案就成为了 救世主

    4. 分代回收

    关于分代回收的过程就在上面这张图中展示着:

    1. 首先把整个堆区域分为三个部分: 伊甸区(新生区)幸存区老年区

    2. 年龄小的为新生代 放在 伊甸区年龄中等的中年代 放在 幸存区年龄比较大的老年代 放在 老年区。

    3. 然后JVM 就进行对 伊甸区和幸存区 每轮GC 的调整:当新生代的对象经历过一轮GC 就会把不是 “垃圾” 的对象拷贝到幸存区, 此时 age + 1

    4. 幸存区分为两个相同的小区域 , 每一轮GC 就会进行上述的 复制算法 , 进行 逐步清除 。此时 age + 1 , 当 age 达到一定的程度之后,就会 调整到老年区

    5. 老年区的老年代对象 ,就可以认为是 经常被引用的对象 , 就不会轻易的释放,并且对象也比较 , 只需要偶尔的标记整理, 并不会产生 很大的时间消耗

    上述过程可能小伙伴们还不是很能理解吧 , 下面小编举个咱找工作的过程举举栗吧 ~

    分代回收就好比我们找工作流程:

    伊甸区——》 幸存区 : 投简历(筛掉大部分对象

    幸存区——》 老年区 : 面试(经历 一面 二面, 三面,HR面) 多层筛选, 也会筛掉一部分对象

    老年区 : 成为正式员工(只是公司偶尔会考察, 筛掉很少的一部分对象

    鱼式疯言

    补充说明

    其实分代回收的思想就是 前三种方案的结合伊甸区和幸存区的复杂算法 , 以及 利用到老年区的标记整理

    四. JVM 的垃圾回收器

    如上图,分代回收只是GC 的思想, 但是在真正用于实现GC 的工作流程的还得是 垃圾回收器

    但现在常用的 垃圾回收器G1CMS

    1. CMS

    其中上图是 CMS垃圾回收器 : 把 整个GC 都拆成多个阶段 , 能和 业务线程 进行 并发执行就并发执行 , 经可能的 减少STW 的时间

    2. G1

    如上图这是G1 的结构图, G1 就是把整个内存分为很多块, 不同的颜色就代表 伊甸区幸存区老年区

    进行GC 的时候, 不要求把所有的内存都回收掉 , 而是只回收一部分就好了, 让STW 控制在一定的时间内

    降低 STW 的影响,目前能将 Java STW 降到 1ms 以内

    总结

  • Java的垃圾回收机制: 初识 垃圾回收 的机制: 在 软件层面上实现自动回收不需要使用内存空间, 但是会容易造成STW 问题

  • 怎么找出 “垃圾”: 两个找出垃圾的方案: Java 的 GC 利用的可达性分析 和 其他编程语言GC 利用的 引用计数

  • 如何释放资源空间 : 介绍了五种释放资源空间的方案: 标记清除, 复制算法, 标记整理, 分代回收。 其中标记清除会产生内存碎片问题复制算法和标记整理会产生大量的 STW , 最终采用 前三种结合的分代回收

  • JVM 的垃圾回收器: 主要接收 CMSG1两种垃圾回收器

  • 如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

    希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

    作者:邂逅岁月

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【JVM神秘大门】Java虚拟机原理保姆式教学,零基础速成GC机制(下篇)

    发表回复