JVM的垃圾回收机制(GC)

news2025/1/30 16:21:59

系列文章目录

JVM的内存区域划分_crazy_xieyi的博客-CSDN博客

JVM类加载(类加载过程、双亲委派模型)_crazy_xieyi的博客-CSDN博客


文章目录

  • 一、什么是垃圾回收?
  • 二、java的垃圾回收,要回收的内存是哪些?
  • 三、回收堆上的内存,具体是回收什么?
  • 四、垃圾回收到底是怎么回收的?
  • 如何判定垃圾?
    • 1.引用计数
    • 2.可达性分析(java)
  • 六、已经知道哪些对象是垃圾了,具体怎么去回收呢?
    • 1.标记-清除
    • 2.复制算法
    • 3.标记整理
    • 4.分代回收

一、什么是垃圾回收?

垃圾回收,回收的是内存。JVM其实是一个进程,一个进程会持有很多硬件资源,比如:CPU,内存,硬盘,带宽资源等。系统的内存总量是一定的,程序在使用内存的时候,必须先得申请,才能使用,使用完毕后还要释放。

从代码编写的角度看,内存的申请时机是非常明确的,但是内存的释放时机在很多时候是不太明确的。这个时候就给内存的释放带来了一些困难,典型的问题就是,这个内存是否还要继续使用?

像C/C++这样的编程语言,内存释放,是纯手工的,全靠程序员手动释放。比如,在C语言中,如果malloc出来的对象,不手动调用free,那么这个内存就会一直持有。此时,内存的释放就全靠程序员自己来控制,如果忘了释放(在该释放的时候没有释放),那么很有可能会带来“内存泄漏”这样的问题。一直申请不释放,导致系统可用的内存越来越少,直到耗尽,如果再想要申请内存,就申请不到了。所以,内存泄漏,一向成为了程序员幸福感的头号杀手!

既然内存泄漏有这么大的弊端,肯定有相应的办法来反制。比如,在C++中,就采取了智能指针的方式,在java中采取的方案就是“垃圾回收”机制。对于java来说,代码中的任何地方都可以申请内存,然后由JVM统一进行释放,具体来说,就是由JVM内部的一组专门负责垃圾回收的线程来进行这样的工作的。

JVM垃圾回收的优点:

能够非常好的保证不出现内存泄漏的情况,但是也不是100%保证,就是再厉害的机制也顶不过程序猿自己瞎搞。

JVM垃圾回收缺点:

1.需要消耗额外的系统资源

2.内存的释放可能存在延时

3.可能会导致出现STW问题(stop the world)(在java的世界中,其实前辈们已经做出了很多的努力来改进这个问题,目前能够把STW控制在1ms内)

二、java的垃圾回收,要回收的内存是哪些?

JVM中的内存分为了好几个区域,有堆、方法区、栈和程序计数器。在这几个区域中,堆占据的内存空间是最大的,所以在java的垃圾回收机制中,咱们日常讨论的垃圾回收,主要是指堆上内存的回收。

三、回收堆上的内存,具体是回收什么?

 在堆上,是new出了很多的对象,此时针对堆上的对象,也分成三种:完全使用、完全不使用、一半要使用一半不使用。对于完全不使用,这就是我们要回收的东西。java中的垃圾回收,是以“对象”为基本单位的,一个对象要么被回收,要么不被回收,不会出现一个对象被回收一半的情况。

四、垃圾回收到底是怎么回收的?

垃圾回收的基本思想是先找出垃圾,再回收垃圾。一般情况下时把再也不会被使用到的对象进行垃圾回收,如果要是把正在使用的对象进行垃圾回收,这是一个非常可怕的结果。对于回收少了这样的问题来说,回收多了或回收错了显然是更严重的问题。对于GC来说,判定垃圾的原则,宁可放过,也不能错杀.........

如何判定垃圾?

单说GC的话,判定垃圾有两种典型的方案:引用计数、可达性分析

1.引用计数

在对象里面包含一个单独的计数器,随着引用增加,计数器就自增,随着引用减少,计数器就自减。

Test a = new Test();

此时认为new Test()这个对象就有一个引用指向它。

Test b = a;

此时就有a和b两个引用都指向这个对象。引用计数,就是通过一个变量来保存当前这个对象被几个引用来指向。 

Test a = new Test();
Test b = a;
a = null;
b = null

 当这两个引用都指向 null 的时候,然后这个对象就没有被指向了,此时这个对象就认为是垃圾了(引用计数为0)。

引用计数的优点:

规则简单,实现方便,比较高效(程序运行效率比较高)。

引用计数的缺点:

1.空间利用率比较低,针对大量的小对象,比较浪费空间。(比如,一个对象4个字节,也需要4个字节的计数器)

2.存在循环引用的问题(致命问题)

有些特殊的代码下,循环引用会导致代码的引用计数判断出现问题,从而无法回收。

class Test{
  Test t = null;
}

Test t1 = new Test();
Test t2 = new Test();

t1.t = t2;
t2.t = t1;

当执行下面这个操作:t1 = null; t2 = null;这一操作其实是销毁了两个引用,但是引用计数只减了1,没有了t1,就无法使用t1.t。

 很多编程语言,虽然使用了引用计数这种机制,但是实际上都是改进过的引用计数,比如(Python,PHP)。在java中,并没有使用引用计数的这种方式,而是使用第二种机制:可达性分析。

2.可达性分析(java)

从一组初始的位置出发(比如二叉树),向下进行深度遍历,把所有能够访问到的对象都标记成“可达”(可以被访问到),对应的,不可达的对象就是垃圾。

JVM中采取的方案是:在JVM中存在一组线程,来周期性的进行上述的遍历过程。不断的找出这些不可达的对象,由JVM进行回收。

把可达性分析的初始位置称为“GCRoot”,主要对栈上的局部变量表中的引用、常量池里面的引用指向的对象,方法区中引用类型的静态成员变量进行标记。和引用计数相比,可达性分析确实要稍微麻烦一点,而且同时可达性分析的遍历过程的开销是比较大的。虽然是开销比较大,但是后面会有一些优化的手段。但是可达性分析带来了好处就是解决了引用计数的两个缺点,内存上不需要消耗额外的空间,也没有循环引用的问题。

六、已经知道哪些对象是垃圾了,具体怎么去回收呢?

在java的垃圾回收机制中,有一些经典的策略/算法:标记-清除、复制算法、标记整理以及分代回收。

1.标记-清除

白色是正在使用的对象,灰色是已经被释放的空间。虽然这个过程可以释放掉不用的空间,但是引入了额外的问题:内存碎片。

如果内存中的内存碎片很多很多,那么此时你去申请一块小的内存还好,但是想要很大的一块连续内存空间,可能会申请失败。内存碎片问题,如果一直累积下去,就会导致出现系统上看起来空闲内存挺多的,但是实际上申请连续的内存空间是申请不到的。内存碎片问题,在“频繁申请释放”的场景中,尤为严重。 

2.复制算法

 为了解决内存碎片问题,就引入了复制算法。

复制算法把整个内存分为了两个部分,一次只用一个部分,1和3要被回收了, 于是就把剩下的2和4拷贝到另外一侧,然后再整体回收这一整块空间。

使用复制算法,可以非常有效的避免出现内存碎片问题。但是复制算法也有一些缺点:

可用的内存空间只有一半;如果要回收的对象比较少,剩下的对象比较多,那么复制的开销就比较大了。复制算法只适用于对象会被快速回收,并且整体内存不大的场景下。

3.标记整理

为了能够解决复制算法的内存空间利用率低的问题,就引入了标记整理的策略。标记整理,就有点类似于“顺序表删除元素的搬运过程”。

这样的操作,既可以有效的避免内存碎片,也可以提高空间的利用率。但是在这个搬运的过程中,也是一个很大的开销,这个开销比复制算法里面的复制对象的开销还要大。 

4.分代回收

在实际中的垃圾回收算法,是结合了以上的三种方式,取长补短,引入了分代回收的策略。

在分代回收中,把内存中的对象分成了几种情况,每一种情况采用不同的回收算法。

那么是如何进行分代的呢?

是根据对象的“年龄”的年龄来进行划分的。在JVM中,在进行垃圾回收扫描(可达性分析)也是周期性的,这个对象每次经历了一个扫描周期,就认为是长了一岁。就根据这个对象的年龄,就把整个内存进行划分。年龄短的放在一起,年龄长的放在一起。根据不同的年龄对象,就可以采用不同的回收算法了。

分代回收的过程:

1.一个新的对象,诞生于伊甸区。

2.如果活到一岁的对象(该对象经历了一轮GC还没回收)就拷贝到幸存区。根据经验规律,绝大部分对象都是熬不过一轮GC的,所以进入幸存区的对象不是很大,这里的拷贝开销就不是很大。

3.在幸存区中,对象也要经历若干轮GC。每一轮GC逃过的对象,都通过复制算法拷贝到另外的幸存区里。在这两个幸存区的对象经过来回拷贝,每一轮都会淘汰一批对象。

4.在幸存区中,熬过一定轮次的GC,JVM就认为这个对象未来还会更持久的存在下去,于是就把这样的对象拷贝到老年代中。

5.进入老年代的对象,JVM认为都是属于能够持久存在的对象。但是这些对象也需要使用GC来扫描,但是扫描的频次就大大降低了,老年代这里通常使用的是标记整理算法。 

其实在某些特殊情况下,如果一个对象特别大,为了避免大的开销,那么也会直接进入老年代的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA Base Xilinx跨时钟域宏XPM_CDC

FPGA Base Xilinx跨时钟域宏XPM_CDC最近看手底下的小伙子们写代码,对于跨时钟域的处理极度的不规范,还是放下这句话基础不牢,地动山摇 其实Xilinx公司已经为用户提供了宏定义,实现跨时钟域处理,见截图 XPM_CDC在命名上…

关于旅游景点主题的HTML网页设计——青岛民俗 7页 带登录注册

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材,DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 游景点介绍 | 旅游风景区 | 家乡介绍 | 等网站的设计与制作| HTML期末大学生网页设计作业 HTML:结构 CSS:样式 在操作方面上运…

【C++】简化源码——vector的模拟实现

文章目录一、前言二、无参构造&析构三、基础接口1.empty和clear2.size和capacity3.[]和iterator四、resize和reserve五、尾插尾删六、其他构造七、迭代器失效1.insert2.erase八、memcpy问题九、vector.h一、前言 本篇的目的很简单,只有一个:模拟实现…

C语言刷题(一)

🐒博客名:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 目录 用递归法求一个整数一维数组a的最大元素 猴子吃桃问题 奇偶数换位问题 水仙花数(0-100000) 换啤酒…

web前端电影项目作业源码 大学生影视主题网页制作电影网页设计模板 学生静态网页作业成品 dreamweaver电影HTML网站制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置,有div的样式格局,这个实例比较全面,有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

redis命令行操作库、键、和五大数据类型详解

一、数据库操作命令 redis默认有16个数据库,类似数组下标从0开始,初始默认使用0号库。 1.1 测试是否连通 ping测试服务器是否连通 返回pone就是连通了 1.2 切换数据库 select index1.3 数据移动 move key db1.4 显示数据总量 dbsize1.5 数据清除 …

Maven 跳过测试的几种方式

在 Maven 对项目进行编译的时候,我们通常可能会希望跳过复杂的测试。 尤其是在开始项目还不是非常稳定的阶段。 命令行中使用 -Dmaven.test.skiptrue 在命令行,只要简单的给任何目标添加 maven.test.skip 属性就能跳过测试: mvn install …

leetcode:6251. 统计回文子序列数目【dp + 统计xy子序列出现的个数】

目录题目截图题目分析ac code总结题目截图 题目分析 固定了中间的数i后从两边选xy 和 yx对于x y的情况,比较简单预处理每个数字出现的index为ids然后看看两边x各自的个数n1 n2n1和n2必须大于等于2左边可以选n1 * (n1 - 1) // 2右边可以选n2 * (n2 - 1) // 2两边乘…

【C++】通过哈希表实现map和set

前言 在前面,我们通过红黑树这一底层结构实现了map和set。它们是关联式容器。而现在,我们将通过哈希表这一数据结构重新实现map和set,即unordered系列的关联式容器。因为它们的遍历是无序的,和平衡二叉树不同,不能做到…

APOLLO UDACITY自动驾驶课程笔记——规划、控制

1、路径规划使用三个输入,第一个输入为地图,Apollo提供的地图数据包括公路网和实时交通信息。第二个输入为我们当前在地图上的位置。第三个输入为我们的目的地,目的地取决于车辆中的乘客。 2、将地图转为图形 该图形由“节点”(node)和“边缘…

直流潮流计算matlab程序

一、直流潮流计算原理 直流潮流发的特点是用电力系统的交流潮流(有功功率和无功功率)等值的直流电流来代替。甚至只用直流电路的解析法来分析电力系统的有功潮流,而不考虑无功分布对有功的影响。这样一来计算速度加快,但计算的准确…

Rocket MQ : 拒绝神化零拷贝

注: 本文绝非对零拷贝机制的否定笔者能力有限,理解偏差请大家多多指正不可否认零拷贝对于Rocket MQ的高性能表现有着积极正面的作用,但是笔者认为只是锦上添花,并非决定性因素。Rocket MQ性能卓越的原因绝非零拷贝就可以一言以蔽之。 笔者企图…

第146篇 笔记-智能合约介绍

定义:当满足某些预定义条件时,智能合约是一种在区块链网络上运行的防篡改程序。 1.什么是智能合约 智能合约是在区块链网络上托管和执行的计算机程序。每个智能合约都包含指定预定条件的代码,这些条件在满足时会触发并产生结果。通过在去中…

IDEA热部署插件JRebel and XRebel

IDEA热部署插件JRebel and XRebel嘚吧嘚下载安装激活配置使用嘚吧嘚 刚开始用过一段时间的eclipse,其他方面没感觉,但是eclipse的热部署真的是深得我心啊😊。 后来换了IDEA,瞬间就心动了,各个方面真的很好用&#xf…

U3D VideoPlayer播放视频和坑点

最近做的游戏里,需要先播放一段几秒钟的工作室LOGO片头,拿到的视频是AVI格式,以前没在U3D里用到过视频,本以为很简单,没想到都2022年了,U3D播放视频还这么烂。。。 插件最好用的是AVPro,除非你有大量的视频要播放,否则没必要用插件,一个是贵,另一个插件很大。 首先…

Python爬虫从入门到进阶

前言 董伟明,国内某知名Python应用网站高级产品开发工程师,《 Python Web 开发实战》作者,本书目前已经售出 17k 余本,另外也已经在台湾地区上市。在 2012 和 2014 年分别通过 2 个爬虫免试获得 2 个业界知名公司 offer&#xff…

MyBatis缓存机制之一级缓存

MyBatis缓存机制之一级缓存 前言 MyBatis内部封装了JDBC,简化了加载驱动、创建连接、创建statement等繁杂的过程,是我们常见的持久性框架。缓存是在计算机内存中保存的临时数据,读取时无需再从磁盘中读取,从而减少数据库的查询次…

Node.js 入门教程 1 Node.js 简介

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录,不存在任何商业用途,如侵删 文章目录Node.js 入门教程1 Node.js 简介1.1 大量的库1.2 Node.js 应用程序的示例1.3 Node.js框架和工具1 Node.js 简介 Node.js 是一个开源和跨平台…

子矩形计数(冬季每日一题 17)

给定一个长度为 nnn 的数组 aaa 和一个长度为 mmm 的数组 bbb。 两个数组均只包含 000 和 111。 利用两个给定数组生成一个 nmnmnm 的矩阵 ccc,其中 cijaibjc_{ij}a_ib_jcij​ai​bj​。 显然,矩阵 ccc 中也只包含 000 和 111。 请问,矩阵…

期末复习 c

作者:小萌新 专栏:C语言复习 作者简介: 大二学生 希望能和大家一起进步! 本篇博客简介:回顾之前的分支循环以及一些题目博客 [TOC](这里写目录标题分支循环选择switch casegetchar putchar 以及EOF三个C语言练习题总结…