JVM的垃圾回收机制--GC

ops/2024/9/23 11:13:30/

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


http://www.ppmy.cn/ops/44654.html

相关文章

自从有了可观测性,传统运维如何进行提升?

在 201x 年,随着容器技术的出现,容器的部署方式逐渐被各大互联网公司采用,相比物理机/虚拟机,容器的好处是环境隔离、轻量、快速。 但是管理容器是一件复杂的事情,后来出现了 Kubernetes,成为了事实上的容…

QML基本语法介绍

为什么使用QML 开发者效率 将前后端分离,QML和JavaScript语言主要用于前度UI的方法,后端有C++来完成绘制。将JavaScript和C++分开能够快速迭代开发; 跨平台移植性 基于Qt平台的统一抽象概念,现在可以更加容易和快速和将Qt移植到更多的平台上。 开发的开放 Qt是由Qt-Proje…

酷黑简洁大气体育直播自适应模板赛事直播门户网站源码

源码名称:酷黑简洁大气体育直播自适应模板赛事直播门户网站源码 开发环境:帝国cms 7.5 安装环境:phpmysql 支持PC与手机端同步生成html(多端同步生成插件) 带软件采集,可以挂着自动采集发布,无…

mysql数据库管理-mysqlbinlog备份与恢复,主备复制分析

由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文件的文本格式, 就会用到mysqlbinlog日志管理工具。 mysqlbinlog的具体用法如下: shell> mysqlbinlog [options] log-files1 log-files2. . option有很多选项&…

PaddleOCR2.7+Qt5

章节一:Windows 下的 PIP 安装 官网安装教程地址 按照里面的教程去安装 如果使用cuda版本的还要安装tensorrt,不然后面运行demo程序的程序会报如下错。 下载TensorRT 8版本,tensorrt下载地址 章节二:编译源码 进入官网源码地址 下…

Oracle的安装以及一些相关问题

系列文章目录 Oracle的安装以及一些相关问题 文章目录 系列文章目录前言一、Oracle的安装二、常用命令三、误删dbf四、PLSQL乱码五、oracle更换数据库字符集总结 前言 一段时间没更新,主要最近一直在找工作,最终还是顺着春招找到工作了,现在…

SWM320系列应用

一、Swm320系列 SPI 应用 现象:应用SWM320的SPI1的模式0作为从机,整体产品硬件平台同步上电,从机的SPI无法正常工作,进不了中断,手工复位一次或连接SWD调试就正常了,这样的情况应该怎么解决?其…

代码随想录——找树左下角的值(Leetcode513)

题目链接 层序遍历 思路:使用层序遍历,记录每一行 i 0 的元素,就可以找到树左下角的值 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}*…