JVM的垃圾回收机制--GC

垃圾回收机制,是java提供的对于内存自动回收 的机制。java不需要像C/C++那样手动free()释放内存空间,而是在JVM中封装好了。垃圾回收机制,不是java独创的,现在应该是主流编程语言的标配。GC需要消耗额外的系统资源,而且存在非常影响执行效率的"STW"问题(触发GC的时候,可能一瞬间把系统负载拉满,导致服务器无法响应其他的请求)。

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

内存区域有四块:

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

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

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

4)堆GC的主力部分

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

GC的流程

GC的流程,主要是两个步骤。1)找到谁是垃圾 2)释放对应的内存

找到谁是垃圾

一个对象,什么时候创建,时机往往是明确的。但是什么时候不再使用,时机往往是模糊的。在编程中,一定要确保,代码中使用的每个对象,都得是有效的,不能出现**"提前释放**"的情况。

因此判定一个对象是否是垃圾,判定方式是比较保守的。

此处引入了非常"保守"的,一定不会误判的做法(可能回释放的不及时)。判定某个对象,是否存在引用指向它。

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

如何判定,某个对象是否有引用指向呢?

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

会在为new 对象开辟内存空间时,额外开辟一个计数器,每当对象多一个引用,计数器+1。当计数器为0时,即可回收对象。

这种方法存在两个缺陷:

1、消耗额外的存储空间

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

2、存在"循环引用"的问题

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

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

可达性分析是java采用的做法,解决了空间和循环引用的问题,但是付出了时间上的代价。核心思想是"遍历",JVM把对象之间的引用关系,理解成了一个"树形结构"。JVM就会不停的遍历这样的结构,把所有能够遍历访问到的对象标记成"可达",剩下就是"不可达"。

这些树的根结点是怎么确定的?

Java代码中,你所有的

1)栈上的局部变量,引用类型的,都是GC roots

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

3)方法区中的静态成员

都是一棵树的根结点。JVM就会周期性的对这所有的树进行遍历,不停的标记可达,也不停的把不可达的对象干掉。

具体树是否复杂,都取决于实际代码的实现。

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

释放垃圾的策略

1、标记-清除

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

这样的做法会存在"内存碎片"问题。指空闲内存被分成一个个的碎片了,后续很难申请到连续的大的内存。并不实用。

2、复制算法

将内存空间分成两块,要释放某一块内存空间时,将无需删除的数据提前复制到另一块内存中。

这种做法空间浪费太多了。如下图删除1、3、5:

3、标记-整理

将无需删除的部分向前搬运,覆盖掉要删除的数据。如删除2、4、6

这种方法能解决空间利用率问题,但是时间开销更大。

JVM中实际采取的方案是综合上述方案,更复杂的策略。分代回收。也就是分情况讨论,根据不同的场景和特点选择合适的方案。根据对象的**年龄(经历GC周期性扫描的轮次)**来讨论,GC有一组线程,回对内存进行周期性扫描。某个对象经历了一轮GC之后,还是存在,没有成为垃圾,年龄就+1。

JVM堆区的结构如下:

分代回收的流程:1)把新创建的对象,放到伊甸区中。

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

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

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

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

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

以上,关于JVM的垃圾回收机制,希望对你有所帮助。

相关推荐
玉红77722 分钟前
R语言的数据类型
开发语言·后端·golang
夜斗(dou)25 分钟前
node.js文件压缩包解析,反馈解析进度,解析后的文件字节正常
开发语言·javascript·node.js
觅远27 分钟前
python+PyMuPDF库:(一)创建pdf文件及内容读取和写入
开发语言·python·pdf
呜呼~225141 小时前
前后端数据交互
java·vue.js·spring boot·前端框架·intellij-idea·交互·css3
神雕杨1 小时前
node js 过滤空白行
开发语言·前端·javascript
飞的肖1 小时前
从测试服务器手动热部署到生产环境的实现
java·服务器·系统架构
周伯通*1 小时前
策略模式以及优化
java·前端·策略模式
两点王爷1 小时前
Java读取csv文件内容,保存到sqlite数据库中
java·数据库·sqlite·csv
lvbu_2024war012 小时前
MATLAB语言的网络编程
开发语言·后端·golang
问道飞鱼2 小时前
【Springboot知识】Springboot进阶-实现CAS完整流程
java·spring boot·后端·cas