当前位置: 首页 > news >正文

【JVM】垃圾回收机制|死亡对象的判断算法|垃圾回收算法

思维导图

目录

1.找到谁是垃圾

1)引用计数(不是JVM采取的方式,而是Python/PHP的方案)

2)可达性分析(是JVM采用的方案)

2.释放对应的内存的策略

1)标记-清除(并不实用)

2)复制算法

3)标记-整理

4)分代算法


垃圾回收机制GC,是Java提供的对于内存自动回收的机制,相对于C/C++的手动回收malloc free来命名的,GC需要消耗额外的系统资源,而且存在非常影响执行效率的“STW”问题(stop the world),触发GC的时候,就可能一瞬间把系统负载拉满,导致服务器无法响应其他的请求

GC回收的是“内存”,更准确的说是“对象”,回收的是“堆上的内存”

1)程序计数器:不需要额外回收,线程销毁,自然回收了

2)栈::不需要额外回收,线程销毁,自然回收了

3)元数据区:一般也不需要,都是加载类,很少“卸载类”

4):GC的主力

回收一定是一次回收一个完整的对象,不能回收半个对象(一个对象有10个成员,肯定是把10个成员的内存都回收了,而不是只回收一部分)

GC的流程,主要是两个步骤:

1.找到谁是垃圾(即不被继续使用的对象)

2.释放对应的内存


1.找到谁是垃圾

谁是垃圾,这个事情,不太好找;一个对象,什么时候创建,时机往往是明确的,但是什么时候不在使用,时机往往是模糊的...在编程中,一定要确保,代码中使用的每个对象,都得是有效的,千万不要出现“提前释放”的情况(宁可放过,也不能错杀)

因此判定一个对象是否是垃圾,判定方式是比较保守的;此处引入了一个非常"保守“的做法,一定不会误判的做法(可能会释放不及时),判定某个对象,是否存在引用指向它

Test t = new Test();

使用对象,都是通过引用的方式来使用的,如果没有引用指向这个对象,意味着这个对象注定无法在代码中使用,如果没有引用指向的对象就可以视为是垃圾了

Test t = new Test();
t = null;

修改t的指向,此时new Test()的对象就没有引用指向了,此时这个对象就可以认为是”垃圾“

具体怎么判断,某个对象是否有引用指向呢?有很多方式来实现,下面介绍两种方式

如果面试官问:GC中,如何判定对象是垃圾,那么两种都要回答

如果问:Java的GC中,如何判定对象是垃圾,那么只需要回答第二种


1)引用计数(不是JVM采取的方式,而是Python/PHP的方案)

给对象增加⼀个引⽤计数器,每当有⼀个地⽅引⽤它时,计数器就+1;当引⽤失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使⽤的,即对象已"死"

Test a = new Test();
Test b = a;
//把对象的地址进行赋值,都知道对象的地址了,自然就能找到对象,更能找到对象旁边的计数器了
b = null;
a = null;
//引用计数器为0,此时对象就是垃圾了

 but,这种方案有两个缺陷:

(1)消耗额外的存储空间

如果对象比较大,浪费的空间还好,对象比较小,空间占用就多了,并且对象数目多,空间的浪费就多了

(2)存在”循环引用“的问题(面试考到引用计数的重点)

class Test {Test t;
}Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;a = null;
b = null;

此时,这两对象相互指向对方,导致两个对象的引用计数都为1(不为0,不是垃圾),但是外部代码,也无法访问到这两个对象

这个问题也是能解决的,需要付出额外的代价(搭配一些其他的机制来解决),Python和PHP通过额外的机制,避免形成循环引用(自动检测是否形成了循环引用,并且给出了一些报错提示)


2)可达性分析(是JVM采用的方案)

解决了空间的问题,也解决了循环引用的问题,但是同时也要付出代价,时间上的代价

JVM把对象之间的引用关系,理解形成了一个”树形结构“;JVM就会不停的遍历这样的结构,把所有能够遍历访问到的对象标记成”可达“,剩下的就是”不可达“

class Node{Node left;Node right;
}Node build() {Node a = new Node();Node b = new Node();Node c = new Node();Node d = new Node();Node e = new Node();Node f = new Node();Node g = new Node();a.left = b;a.right = c;b.left = d;b.right = e;e.left = g;c.right = f;return a;
}Node root = build();
//此处只有一个引用,通过这个引用,就能访问到所有树上的结点对象

 如果a.right = null;

此时c就不可达了,同时f也就不可达了

GC roots(这些数的根节点是怎么确定的?)

Java代码中,你所有的

1)栈上的局部对象,引用类型的,就是GC roots

2)常量池中,引用的对象

3)方法区中的静态成员

很多个上述的树,JVM就会周期性的对这所有的树进行遍历,不停的标记可达,也不停的把不可达的对象干掉;具体树是否复杂,都取决于实际代码的实现(有时会很复杂)

but:由于可达性分析,需要消耗一定的时间,因此Java垃圾回收,没法做到”实时性“,周期性进行扫描(JVM提供了一组专门的负责GC的线程,不停的进行扫描工作)


2.释放对应的内存的策略

1)标记-清除(并不实用)

直接把标记为垃圾对象对应的内存,释放掉(简单粗暴)

白色区域:正在使用的

黑色区域:已经释放的,其他代码可以重复利用的

但是这样的做法会存在”内存碎片“问题,空闲内存被分为一个个的碎片了,后续很难申请到大的内存。申请内存,都是要申请”连续“的内存空间的


2)复制算法

要释放1,3,5,保留2,4,不会直接释放1,3,5的内存,而是把2,4拷贝到另一块空间中

内存碎片的问题就解决了,但是这样空间浪费太多,如果你要保留的对象比较多,复制的时间开销也不小


3)标记-整理

释放2,4,6,保留1,3,5,7

类似于“顺序表删除中间元素”,搬运

这样能解决内存碎片,也能解决空间利用率的问题

但是这样的搬运,时间开销更大


4)分代算法

上述三个方案,只是铺垫,JVM中的实际方案,是综合上述的方案,更复杂的策略,分代回收(即分情况讨论,根据不同的场景/特点选择合适的方案)

把新创建的对象,放到伊甸区,这个区中,大部分的对象,生命周期都是比较短的,第一轮GC到达的时候,就会成为垃圾,只有少数对象能活过第一轮GC

伊甸区->生存区 通过复制算法(由于存活对象较少,复制开销也很低,生存区空间也不必很大)

生存区->生存区  通过复制算法,没经过一轮GC,生存区中都会淘汰掉一批对象,剩下的通过复制算法,进入另一个生存区(进入另一个生存区的还要从伊甸区里进来的对象),成存活下来的对象,年龄+1

生存区->老年代 某些对象,经历了很多轮GC,都没有成为垃圾,就会复制到老年代

老年代的对象,也是需要进行GC的,但是老年代的对象生命周期都比较长,就可以降低GC扫描的频率

综上就是”分代回收“的基本逻辑

对象 伊甸区-> 生存区-> 生存区 ->老年区  复制算法

对象在 老年代中,通过标记-整理(搬运)来进行回收


垃圾回收还有一个话题:垃圾回收器,上述思想的具体实现,实现过程有很多优化和策略的体现

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 学习笔记——RegNet:Designing Network Design Spaces
  • (k8s)kubernetes集群基于Containerd部署
  • 2024年开放式蓝牙耳机十大排名震撼揭晓!哪款开放式耳机是音质王者?
  • 从零开始学习Linux(13)---多线程
  • Flutter iOS混淆打包
  • python中ocr图片文字识别样例(一)
  • 低级编程语言和高级编程语言
  • 【ArcGIS微课1000例】0121:面状数据共享边的修改方法
  • 如何优化前端页面的 AJAX 请求性能并避免冲突
  • 【算法题】53. 最大子数组和-力扣(LeetCode)
  • 从HarmonyOS升级到HarmonyOS NEXT-环信SDK数据迁移
  • 如何基于Flink CDC与OceanBase构建实时数仓,实现简化链路,高效排查
  • C#-日志系统
  • 多边形抠图 python
  • 怎么解除BitLocker对磁盘的加密?
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 2017-08-04 前端日报
  • Android Volley源码解析
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • django开发-定时任务的使用
  • Fundebug计费标准解释:事件数是如何定义的?
  • IDEA 插件开发入门教程
  • Idea+maven+scala构建包并在spark on yarn 运行
  • JS 面试题总结
  • JSONP原理
  • Js基础——数据类型之Null和Undefined
  • KMP算法及优化
  • LintCode 31. partitionArray 数组划分
  • maven工程打包jar以及java jar命令的classpath使用
  • Node + FFmpeg 实现Canvas动画导出视频
  • PHP CLI应用的调试原理
  • vue.js框架原理浅析
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 从零开始的无人驾驶 1
  • 工作中总结前端开发流程--vue项目
  • 码农张的Bug人生 - 见面之礼
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 使用agvtool更改app version/build
  • 思维导图—你不知道的JavaScript中卷
  • 原生Ajax
  • FaaS 的简单实践
  • 阿里云ACE认证之理解CDN技术
  • 大数据全解:定义、价值及挑战
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #php的pecl工具#
  • #VERDI# 关于如何查看FSM状态机的方法
  • (2.2w字)前端单元测试之Jest详解篇
  • (C++17) std算法之执行策略 execution
  • (javascript)再说document.body.scrollTop的使用问题
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (回溯) LeetCode 77. 组合
  • (一)插入排序