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的垃圾回收机制,希望对你有所帮助。

相关推荐
Swift社区3 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht3 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht3 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20243 小时前
Swift 数组
开发语言
吾日三省吾码4 小时前
JVM 性能调优
java
stm 学习ing4 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc5 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi775 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器