jvm常见的垃圾回收算法_垃圾回收机制算法

jvm常见的垃圾回收算法_垃圾回收机制算法在早期的计算机语言,比如C和C++,需要开发者手动的来跟踪内存,这种机制的优点是。但是它也有它的缺点,新的编程语言,比如JAVA,Go,Python,PHP…现在市面上的大部分主流编程语言,都采取了一个方案,那就是“垃圾回收机制”,运行时自身会运行相应的垃圾回收机制。。垃圾回收器(GC)会在适当的时候将的内存给释放掉。GC的优点:GC的缺点:JVM的内存结构包括四大区域:1.程序计数器2.栈(虚拟机栈,本地方法栈)3.堆4.方法区举个例子,任何组织里,人都有三个派别,1.积极派2.消极派

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用


1. 什么是垃圾回收机制(GC)

在早期的计算机语言,比如 C 和 C++,需要开发者手动的来跟踪内存,这种机制的优点内存分配和释放的效率很高。但是它也有它的缺点如果程序员不小心忘记释放内存,从而造成内存的泄露

内存泄露:申请内存之后,忘记释放了 导致 可用的内容越来越少,最终无内存可用

新的编程语言,比如 JAVA,Go,Python,PHP… 现在市面上的大部分主流编程语言,都采取了一个方案,那就是 “垃圾回收机制”,运行时自身会运行相应的垃圾回收机制。程序员只需要申请内存,而不需要关注内存的释放垃圾回收器(GC)会在适当的时候将已经终止生命周期的变量的内存给释放掉。

1.1 垃圾回收机制的优缺点

GC的优点:

  • 它大大简化了应用层开发的复杂度(不需要开发者再去手动跟踪内存)
  • 降低了内存泄露的风险

GC的缺点:

  • 消耗额外的开销(消耗的资源更多了)
  • 会影响程序的流畅运行

2. 哪些内存需要回收

JVM的内存结构包括四大区域:1.程序计数器 2.栈 (虚拟机栈,本地方法栈)3.堆 4.方法区

在这里插入图片描述

举个例子,任何组织里,人都有三个派别,1.积极派 2.消极派 3.中间摇摆派,如图,对于上述三个派别,哪些是要进行回收释放内存的?

正在使用的内存中的对象 代表 积极派

不再使用,但是尚未回收的内存中的对象 代表消极派

中单部分为中间摇摆派
在这里插入图片描述
需要进行回收释放资源的:消极派

为什么中间摇摆派不回收释放内存呢?对于这种部分仍在使用,一部分不在使用的对象,整体来说是不释放的!等到这个对象彻底完全不使用,才真正的释放!!

注意

垃圾回收的基本单位是“对象”,而不是“字节”

3. 垃圾回收具体是如何回收的

分为两个阶段:

  1. 找垃圾/判定垃圾
  2. 回收垃圾(释放内存)

3.1 找垃圾/判定垃圾

如何找 垃圾/判定垃圾呢?当下主流的思路,有两种方案:

  1. 基于引用计数(不是Java中采取的方案,这是别的语言,像Python采取的方案)
  2. 基于可达性分析(这个是Java采取的方案)

3.11 基于引用计数

什么是基于引用计数:简单来说,针对每个对象,都会额外引入一小块内存,保存这个对象有多少个引用指向他
举个例子
1.Test t = new Test();,此时 new 了一个对象,那么我们就会额外引入一小块内存,此时 t 指向这个对象的引用,因此 引用计数 加 1

在这里插入图片描述
2.Test t2 = t; 此时 t 和 t2 都是指向这个对象的引用,此时引用计数 从1 变为 2
在这里插入图片描述
3.

void func() {
   Test t = new Test();
   Test t2 = t;
}

func()//调用方法过程中,创建了对象(分配内存),在方法执行过程中,引入计数量是2,当方法执行结束,由于 t 和 t2 都是局部变量,跟着栈帧一起释放了,这一释放就导致引用计数为0了(没有引用指向这个对象了,也就没有代码能够访问到这个对象了),此时就认为这个对象是个垃圾!

注意:引用计数为0的时候,就不再使用了,这个内存不再使用,就释放了(为后面理解做铺垫)

3.12 引用计数的优缺

引用技术,简单可靠高效,但是有个两个致命缺陷!!

  1. 空间利用率比较低,每个 new 的对象都得搭配个 计数器,计数器假设 4个字节,如果对象本身很大(几百个字节),多出来4个字节,就不算什么,但是如果本身对象很小(自己才4个字节),多出4个字节,相当于空间被浪费了一半
  2. 会有循环引用的问题

循环引用问题:写个代码举例子便于理解

// 先创建一个类
class Test {
// 成员变量
    Test t = null;
}
// 创建实例
Test t1 = new Test();
Test t2 = new Test();

画出内存布局:

在这里插入图片描述

t1.t = t2;//把 t2 赋值给了 t1里面的t属性,此时对象2有两个引用
引用计数加1,变为2

在这里插入图片描述

t2.t = t1// 把 t1 赋值给 t2 里面的 t 属性,此时对象1 有两个引用
引用计数加1,变为2

在这里插入图片描述
接下来,烧脑的环节:

t1 = null
t2 = null

在这里插入图片描述

此时此刻,两个对象的引用计数,不为0,所以无法释放,但是由于引用长在彼此的身上,外界的代码也无法访问到这两个对象,此时此刻,这俩对象,就不能使用,又不能释放,就出现了“内存泄露”的问题。

所以,像 Python,PHP里进行GC也不只靠引用计数,还依赖其他的机制配合,但是Java可以直接采用可达性分析,来判断垃圾

3.13 基于可达性分析

基于可达性分析:简单的来说,通过额外的线程,定期的针对整个内存空间的对象进行扫描,有一些起始位置(称为 GCRoots),会类似于 深度优先遍历一样,把可以访问到的对象都标记一遍(带有标记的对象就是可达对象),没有被标记的对象,就是不可达,也就是垃圾!

什么才算 GCRoots

  1. 栈上的局部变量
  2. 常量池中的引用指向的对象
  3. 方法区中的静态成员指向的对象

举个栗子吧,比如:写个代码,构造一个二叉树
在这里插入图片描述
如果我们在外面的代码中

Node root = a

代码中只要拿到 树 根节点,就可以掌握所有的节点,树上的任意节点,都可以通过 a 直接/间接的获取到

换句话说,GC在进行可达性分析的时候,当 GC 扫描到 a 的时候,就会把 a 能访问到的所有元素都去访问一遍,并且进行标记,所标记的节点 表示 都不是 垃圾

如果代码中,写了如下代码

c.right = null

在这里插入图片描述
则此时意味着,从 a 出发,访问不到 f,f 就是 不可达,f 就是垃圾,f 就应该被回收

如果代码中

a.right = null

在这里插入图片描述
此时从 a 出发,c 和 f 都是不可达了,也就都被标记成垃圾了!

从上面的这几点我们可以看出,可达性分析是去遍历每一个对象,如果内存中的对象特别多,这个遍历就会很慢,因此 GC 还是比较消耗时间和系统资源的!

3.14 可达性分析的优缺点

优点:

  • 克服了引用计数的两个缺点:
    1. 空间利用率低
    2. 循环引用

缺点:

  • 系统开销大,遍历一次可能比较慢

tips:

找垃圾,核心就是确认这个对象未来是否还会使用,什么算不使用了?没有引用,就不使用了

明确了谁是垃圾之后,接下来就要回收垃圾了!

3.2 回收垃圾(释放内存)

3.21 回收垃圾(释放内存)三种基本策略

标记 – 清除

如图:这是一块内存,上面被分成了很多小块,其中有些部分是垃圾(打钩的)

在这里插入图片描述

这里的 标记 ,就是可达性分析的过程

清除,就是直接释放内存 ,灰色区域代表释放内存

在这里插入图片描述

此时如果直接释放,虽然内存还是还给了系统,但是被释放的内存是离散的(不是连续的)

分散开带来的问题就是:“内存碎片”,这个问题其实非常影响程序的执行!

内存碎片:比如,空闲的内存,有很多,假设一共是 1G,如果要申请 500M 内存,也是可能申请失败的,因为要申请 500M 的内存 必须是连续的,每次申请,都是申请的连续的内存空间,而这里的 1G 可能是多个 碎片加在一起 才 1G,可用的并不多

为了解决内存碎片因此我们引入了复制算法!

复制算法

如图:一块内存,分成两半,左边一半有很多对象,打钩的标记为垃圾,右边为 左边不是垃圾的,拷贝过来

在这里插入图片描述

然后再将左边全部标记为垃圾(灰色),全部释放掉,我们就能保证,左右两侧空间都是整体连续的

在这里插入图片描述

此时内存碎片问题就迎刃而解了!

注意:复制算法的问题有如下几点

  • 内存空间利用率低(只能用一般的空间)
  • 如果要保留的的对象多,要释放的对象少,此时复制开销就很大

针对复制算法我们进行改进!–》 标记 – 整理

标记 – 整理

如图:还是一块内存,上面有一些对象,其中一些被标记为垃圾(打钩的)

在这里插入图片描述

如何进行标记 – 整理呢?

类似于顺序表删除中间元素,有一个搬运操作,我们将 3 搬运到 2 ,再将 5 搬运到 3 ,再把 7 搬运到 4 然后再把后面的部分整体的释放掉

在这里插入图片描述

这个方案空间利用率是高了,但是仍然没有解决复制/搬运元素开销大的问题~

3.22 分代回收

上述的三个方案,虽然能解决回收垃圾的问题,但是都有缺陷,实际 JVM 中的实现,会把多种方案结合起来一起使用,这个思路我们称为 “分代回收”

在这里插入图片描述

我们这个对象,他是怎样在这个区域里来回 轮转 的呢?

  1. 刚创建出来的对象,就放在伊甸区
  2. 如果伊甸区的对象熬过一轮 GC 扫描,就会被拷贝到 幸存区(伊甸区 到 幸存区 应用了复制算法)
  3. 在后续的几轮 GC 中,幸存区的对象就在两个幸存区之间来回拷贝(复制算法),每一轮都会淘汰一波幸存者
  4. 在持续若干轮之后,对象终于,进入老年代,老年代有个特点,里面的对象都是比较老的(年级大的),因此老年代的 GC 扫描频率大大低于新生代。老年代中使用标记整理的方式进行回收!

上述过程是面试中的经典问题!!!一定要重点掌握啊!

注意:
在这里插入图片描述

注意!!!
分代回收中,还有一个特殊情况,有一类对象可以直接进入老年代(大对象,占有内存多的对象),大对象拷贝开销比较大,不适合使用复制算法!

4. 垃圾回收器

上面说的找垃圾,和释放垃圾,说的都是算法思想,不是具体落地实现,在JVM里,真正实现上述算法的模块称为“垃圾回收器”

在这里插入图片描述

在这里插入图片描述


?✨总结

“种一颗树最好的是十年前,其次就是现在”

所以,

“让我们一起努力吧,去奔赴更高更远的山海”

如果有错误❌,欢迎指正哟?

?如果觉得收获满满,可以动动小手,点点赞?,支持一下哟?

以梦为马,不负韶华

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/182489.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 2021vue经典面试题_vue面试题大全

    2021vue经典面试题_vue面试题大全面试题(2020)Vue面试题汇总博客说明文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!1、对于MVVM的理解MVVM是Model-View-ViewModel的缩写。Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。View代表UI组件,它负责将数据模型转化成UI展现出来。ViewModel监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View和Model的对

    2022年10月9日
    7
  • 如何查看Vue项目vue的版本号

    如何查看Vue项目vue的版本号如果是用vue-cli创建的项目,则找到项目根目录下的”package.json”文件如果是要查看vue-cli的版本号的话,则键盘Win+R,输入cmd,再在cmd里面输入vue-V

    2022年5月20日
    66
  • win732位系统怎么安装_windows7可以安装python 什么版本

    win732位系统怎么安装_windows7可以安装python 什么版本win732位系统如何安装pycharm?1.查找安装说明百度找到了PyCharm安装教程(Windows),地址是:https://www.runoob.com/w3cnote/pycharm-windows-install.html按照步骤选择了community社区版的pycharm进行下载安装安装过程中出现如下提示信息:提示信息显示安装pycharm2019.3.1版本…

    2022年8月29日
    4
  • java实现线程通信的几种方式[通俗易懂]

    java实现线程通信的几种方式[通俗易懂]前言在多线程的世界里,线程与线程之间的交互无处不在,只不过在平时的开发过程中,大多数情况下,我们都在单线程的模式下进行编码,即使有,也直接借助框架自身的机制实现了,其实线程之间的通信在JDK中是一个比较深的问题,比如大家熟知的消息中间件的实现,从某种角度上讲,就借助了多线程通信的思想,下面总结了JDK中常用的几种实现线程通信的方式,提供参考1、synchronized实现方式可能很多小伙伴们会有疑问,synchronized是对共享资源加锁使用的,怎么和线程通信扯在一起呢?这里纠正一个小小的偏见,也是

    2022年6月19日
    43
  • hashmap扩容后数据的迁移_HashMap扩容

    hashmap扩容后数据的迁移_HashMap扩容上文回顾在上文深入源码分析HashMap到底是怎样将元素put进去的我们着重分析了无参构造函数是如何创建map对象和HashMap是如何将第一个元素put进table的。此篇重点这篇我们将逐行代码分析1、有参构造函数是如何创建map对象的2、当元素增多导致扩容之后,元素是如何重新分布的同样,为了方便读者复盘,我截取源码是尽量将行号带上。jdk版本还是1.8结构图再重复一遍,HashMap的底层数据结构为数组+链表+红黑树的结构,放一个HashMap的结构示意图,有个大致印象。解剖思路

    2022年9月21日
    3
  • IAR for AVR delay函数「建议收藏」

    IAR for AVR delay函数「建议收藏」众所周知,在GCCAVR里有个delay.h的头文件,可以直接使用.IARforAVR里面只有__delay_cycles所以,我自己写了个delay.h,包含三个可调用的函数#ifndef__DELAY_H__#define__DELAY_H__#include#ifndefF_CPU#defineF_CPU        1

    2022年5月28日
    59

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号