【JVM】(三) 深入理解JVM垃圾回收机制(GC)

news2024/12/22 23:48:49

文章目录

  • 前言
  • 一、死亡对象的判断方法
    • 1.1 引用计数算法
    • 1.2 可达性分析算法
  • 二、垃圾回收算法
    • 2.1 标记-清除算法
    • 2.2 复制算法
    • 2.3 标记-整理算法
    • 2.5 分代算法
    • 2.6 Minor GC 和 Major GC


前言

JVM 的垃圾回收机制(Garbage Collection)是 Java 中的重要特性之一,它负责在程序运行时自动回收不再使用的内存,以避免内存泄漏和提高程序的性能。垃圾回收机制的设计与实现对于 Java 程序的运行效率和稳定性至关重要。因此,作为一名合格的程序员,对于 JVM 的垃圾回收机制势必要有深入的了解,才能编写设计出合理高效的代码。

一、死亡对象的判断方法

在 JVM 的垃圾回收机制中,判断一个对象是否死亡是非常重要的,因为只有死亡的对象才能够被垃圾回收器回收,释放其占用的内存。其中常用的判断方法包括引用计数算法可达性分析算法

1.1 引用计数算法

引用计数算法是一种简单的垃圾回收算法,它的基本思想就是为每一个对象都维护一个引用计数器,用于记录当前有多少个引用指向该对象。当引用计数器变为零的时候,表示该对象没有任何引用指向它,因此就可以判断这个对象为死亡状态,就可以进行回收了

引用计数的实现非常简单,判断效率也很高,在大部分情况下都是一个不错的算法。比如 Python 就是采用的这种方法实现对内存的管理。但是引用计数的最大问题就是循环引用。

例如下面的代码就展示了循环引用:

class Test{
    public Test test;
}

public class Demo {
    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();
        test1.test = test2;
        test2.test = test1;
        test1 = null;
        test2 = null;
    }
}

以上代码就是一个简单循环引用的案例,类Test中的test字段是一个引用类型,它指向另一个Test对象。在 main函数中,分别常见了两个Test对象,即 test1test2,然后将其相互赋值给对方的 test字段。

如果是采用引用计数的方式,由于test1test2相互引用,它们的引用计数器始终不为零,因为它们之间的引用数始终为1。根据引用计数算法的原理,即使这两个对象不再被程序使用,它们的引用计数器也不会变为零,因此不会被垃圾回收器回收。

1.2 可达性分析算法

为了解决循环引用的问题,JVM 采用了更为复杂的可达性分析算法。可达性分析算法的基本思想是以一组称为 GC Roots 的对象作为起始点,通过向下搜索和遍历对象应用链,判断对象是否可达(reachable)

对象可达的含义:

  • 一个对象是可达的,意味着它可以通过一系列引用链从GC Roots 对象到达。
  • 如果一个对象不可达,即它没有与任何 GC Roots 对象相连接,那么该对象将被判断为死亡状态,即可以被垃圾回收器回收。

JVM 中的GC Roots对象包括:

  • 已加载类的类对象(Class Object);
  • 类静态变量引用的对象;
  • 活动线程(Active Thread)的栈帧(Stack Frame);
  • JNI(Java Native Interface) 中的本地方法栈(Native Method Stack)中引用的对象。

通过可达性分析算法,JVM 能够准确地判断对象的生命周期,避免了引用计数算法的循环引用问题,并有效地进行垃圾回收。

二、垃圾回收算法

垃圾回收算法是 JVM 垃圾回收机制的重要组成部分,它负责回收不再使用的对象,释放内存资源,确保程序的运行效率和稳定性。在 JVM 中,常见的垃圾回收算法包括:标记-清除算法、复制算法、标记-整理算法已经分代算法。

2.1 标记-清除算法

标记-清除算法是最基本的垃圾回收算法之一。其过程分为两个阶段:标记阶段和清除阶段。

  1. 标记阶段:从一组称为 GC Roots 的对象出发,遍历所有可达对象,并将它们进行标记,表示这些对象是活动对象,不会被回收。
  2. 清除阶段:遍历整个堆空间,将没有标记的对象视为垃圾对象,直接回收这些垃圾对象的内存。回收后的内存形成一些不连续的碎片,可能会造成内存碎片化问题

2.2 复制算法

复制算法是为了解决标记-清除算法的内存碎片化问题而设计的一种垃圾回收算法。它将堆空间划分为两个大小相等的区域,每次只使用其中的一半空间。其过程分为三个阶段:标记阶段、复制阶段和角色互换阶段。

  1. 标记阶段:与标记-清除算法相同,从GC Roots对象出发,遍历所有可达对象,标记活动对象。
  2. 复制阶段将所有活动对象从一个区域复制到另一个区域,使得复制后的内存是连续的,不会出现内存碎片化问题
  3. 角色互换:完成复制后,两个区域的角色互换,原来的存活对象成为新的空闲区域,原来的空闲区域成为新的工作区域。

现在的商用虚拟机,包括HotSpot都是采用这种收集算法来回收新生代区域的对象。

  • 新生代中 98% 的对象都是朝生夕死的,所以并不需要按照1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的 Eden(伊甸园)空间和两块较小的 Survivor(幸存者)空间。
  • 每次使用 Eden 和其中一块 Survivor(两个 Survivor 区域一个称为 From(S0) 区,另一个称为 To (S1)区域)。
  • 当回收时,将 EdenSurvivor 中还存活的对象一次性复制到另一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间。
  • Survivor 空间不够用时,需要依赖其他内存(如老年代)进行分配担保。

关于HotSpot

HotSpot默认EdenSurvivor的大小比例是 8 : 1,也就是说Eden : Survivor From : Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的 90%,而剩下的 10% 用来存放回收后存活的对象。

HotSpot是Java平台上使用最广泛的Java虚拟机(JVM)的一种实现。它由Oracle(前身是Sun Microsystems)开发,是Java >Development Kit(JDK)的一部分,也是OpenJDK的默认JVM实现之一。

HotSpot实现的复制算法流程如下:

  1. Eden区满的时候,会触发第一次Minor GC,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor GC的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将EdenFrom区域清空。
  2. 当后续Eden又发生Minor GC的时候,会对EdenTo区域进行垃圾回收,存活的对象复制到From区域,并将EdenTo区域清空。
  3. 部分对象会在FromTo区域中来回复制,如此交换15次(由 JVM 参数 MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活的,就存入到老年代。

2.3 标记-整理算法

复制收集算法对象存活率较高时会进行比较多的复制操作,效率会变低,因此在老年代一般不能使用复制算法

针对老年代的特点,提出了标记-整理算法。标记过程仍与标记-清除过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。流程图如下:

2.5 分代算法

以上三种算法都存在一些共同的问题:

  1. 效率问题标记-清除算法标记-整理算法需要对整个堆空间进行遍历,可能导致垃圾回收的效率较低

  2. 内存碎片问题标记-清除算法在回收阶段会产生内存碎片,造成内存空间的浪费和不连续的内存布局

分代算法的诞生为了解决这些问题的,分代算法是一种综合利用多种垃圾回收算法的策略,通过对堆内存进行区域划分,针对不同区域采用不同的垃圾回收策略,从而实现更好的垃圾回收效果

分代算法的设计思想:
在 Java 程序中,不同对象的生命周期往往是不同的。大部分新创建的对象很快就会变成垃圾,而一些对象可能会长时间存活。因此,根据这个特点就将堆划分为不同的代,分别处理不同生命周期的对象,这样就能更好地优化垃圾回收的效率和性能。

分代算法通常将堆内存划分为以下几个代:

  1. 新生代(Young Generation)新创建的对象通常被分配到新生代。新生代使用复制算法进行垃圾回收,因为这些对象的生命周期短,产生的垃圾较多

  2. 老年代(Old Generation)经过多次 GC 仍然存活的对象被移到老年代。老年代使用标记-清除标记-整理算法进行垃圾回收,因为这些对象的生命周期较长,产生的垃圾相对较少

  3. 永久代(Permanent Generation)永久代用于存放类的元数据和常量等信息,在Java 8之后,永久代被元空间(Metaspace)所取代

通过分代算法,不同代采用不同的垃圾回收策略,能够针对不同生命周期的对象进行更精细的垃圾回收,避免将整个堆空间都遍历,提高垃圾回收效率。这样的设计使得垃圾回收器能够根据应用程序的运行情况动态调整回收策略,从而更好地适应不同的应用场景。

2.6 Minor GC 和 Major GC

在 JVM 中,垃圾回收(Garbage Collection,GC)可以根据执行的任务不同而分为两种类型:即 Minor GCMajor GC(也称为 Full GC)

Minor GC

Minor GC是针对新生代的垃圾回收过程。新生代是 Java 堆内存中的一部分,用于存放刚被创建的对象。通常,新创建对象的生命周期较短,因此在新生代使用复制算法进行垃圾回收。

Minor GC的工作过程如下:

  1. 标记阶段:从GC Roots对象出发,标记所有在新生代中存活的对象。

  2. 复制阶段:将所有存活的对象从Eden区复制到Survivor区。

  3. 角色互换:完成复制后,Eden区和Survivor区的角色互换,使得原来的Eden区成为新的空闲区,原来的Survivor区成为新的工作区。

Minor GC的目的是清理新生代中的垃圾对象,使得新生代能够为新的对象分配空间,尽量保证新生代的空间是连续的,避免产生内存碎片

Major GC

Major GC是针对老年区的垃圾回收过程,老年区用于存放长时间存活的对象。用于老年区中的对象生命周期较长,如果使用复制算法进行垃圾回收可能就会导致较大的复制成本,因此通常使用的是标记-清除标记-整理算法

Major GC的工作过程如下:

  1. 标记阶段:从GC Roots对象出发,标记所有在老年代中存活的对象。

  2. 清除或整理阶段:根据采用的算法进行相应的垃圾回收操作。标记-清除算法将清除未标记的垃圾对象,标记-整理算法将移动存活对象并清理未标记的垃圾对象。

Major GC的目的是清理老年代中的垃圾对象,以避免老年代占用过多内存资源,同时也是为了确保老年代的空间是连续的,避免内存碎片化问题

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

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

相关文章

【EasyGBD】工程编译过程

目录 1 google Play requires that apps target API level 30 or higher ​编辑 2 Migrate to AndroidX 3 BuildConfig 编译与运行成功 遗留问题 1 google Play requires that apps target API level 30 or higher Google Play requires that apps target API level 30 …

数字孪生的「三张皮」问题:数据隐私、安全与伦理挑战

引言 随着数字化时代的来临,数据成为了当今社会的宝贵资源。然而,数据的广泛使用也带来了一系列隐私、安全与伦理挑战。数字孪生作为一种虚拟的数字化实体,通过收集和分析大量数据,模拟和预测现实世界中的各种情境,为…

【c++】rand()随机函数的应用(一)——rand()函数详解和实例

c语言中可以用rand()函数生成随机数,今天来探讨一下rand()函数的基本用法和实际应用。 本系列文章共分两讲,今天主要介绍一下伪随机数生成的原理,以及在伪随机数生成的基础上,生成随机数的技巧,下一讲主要介绍无重复随…

1230. K倍区间(超级详细,小白入!!!)

输入样例&#xff1a; 5 2 1 2 3 4 5输出样例&#xff1a; 6 这个题看到区间两个字&#xff0c;两眼一瞪可能就和前缀和差分有关 做题思路&#xff1a; 通过记录每个[1,r]区间值的和,看它前面是否出现过一个[1,l](l<r),使得[l,r]区间的值能除尽k 有同学就好奇&#xff0c;…

re学习(26)攻防世界-re-BABYRE(IDA无法分析出函数-代码混淆)

题目链接&#xff1a;https://adworld.xctf.org.cn/challenges/list elf是一种对可执行文件&#xff0c;目标文件和库使用的文件格式&#xff0c;跟window下的PE文件格式类似。载入IDA后如果需要对此文件进行远程调试&#xff0c;需要用linux系统&#xff0c;比如说Ubuntu&…

卡了大半天的联调问题

卡了大半天的联调问题 问题&#xff1a;别人dubbo总是调不通我的服务 问题&#xff1a;别人dubbo总是调不通我的服务 org.apache.dubbo.remoting.RemotingException: client(url: dubbo://192.168.56.1:28085/com.alibaba.cloud.dubbo.service.DubboMetadataService?actives1…

8.2day03 Redis入门+解决员工模块

概述 在我们日常的Java Web开发中&#xff0c;无不都是使用数据库来进行数据的存储&#xff0c;由于一般的系统任务中通常不会存在高并发的情况&#xff0c;所以这样看起来并没有什么问题&#xff0c;可是一旦涉及大数据量的需求&#xff0c;比如一些商品抢购的情景&#xff0…

Jest和Mocha对比:两者之间有哪些区别?

目录 什么是单元测试&#xff1f; Jest和Mocha介绍 Jest Jest的特点&#xff1a; Jest的使用限制 Mocha Mocha的特点 使用Mocha的限制 Jest和Mocha的全面比较 我们应该使用哪个测试框架&#xff1f; 结论 什么是单元测试&#xff1f; 所谓单元测试&#xff0c;是对软…

LabVIEW开发多材料摩擦电测量控制系统

LabVIEW开发多材料摩擦电测量控制系统 摩擦电效应是两个物体摩擦在一起&#xff0c;电荷从一个物体转移到另一个物体的现象&#xff0c;从而导致两个物体携带相等和相反的电荷。接触和充电是主导该过程的两个关键因素。当静电荷累积到一定水平时&#xff0c;可能会出现放电现象…

Profile多环境配置以及结合Maven如何使用

文章目录 一、前言二、如何使用profile多环境配置2.1、编写各环境的配置文件2.2、如何让配置文件生效 三、结合Maven使用 一、前言 我们在开发项目的过程中&#xff0c;会遇到需要使用多套环境配置的情况&#xff0c;因为不同环境可能存在不同的配置&#xff0c;比如数据库连接…

eNSP 路由器启动时一直显示 # 号的解决办法

文章目录 1 问题截图2 解决办法2.1 办法一&#xff1a;排除防火墙原因导致 3 验证是否成功 1 问题截图 路由器命令行一直显示 # 号&#xff0c;如下图 2 解决办法 2.1 办法一&#xff1a;排除防火墙原因导致 排查是否因为系统防火墙原因导致。放行与 eNSP 和 virtualbox 相…

Shiro框架基本使用

一、创建maven项目&#xff0c;引入依赖 <dependencies><dependency><groupId>org.apache.directory.studio</groupId><artifactId>org.apache.commons.codec</artifactId><version>1.8</version></dependency><!-- …

A-LOAM安装与配置

GitHub https://github.com/HKUST-Aerial-Robotics/A-LOAM 依赖项 需要安装Ceres Slover和PCL PLC之前已经安装过&#xff0c;因此只需要安装Ceres Solver 编译安装Ceres Slover报错 官方的安装指南 http://ceres-solver.org/installation.html 一开始是git下来的最新版本的…

r一个高性能、无侵入的 Java 性能监控和统计工具

背景 随着所在公司的发展&#xff0c;应用服务的规模不断扩大&#xff0c;原有的垂直应用架构已无法满足产品的发展&#xff0c;几十个工程师在一个项目里并行开发不同的功能&#xff0c;开发效率不断降低。 于是公司开始全面推进服务化进程&#xff0c;把团队内的大部分工程…

官网下载历史版本Android studio

有时我们个更新到了最新版本的AndroidStudio&#xff0c;但发现最新版的有一些bug影响使用&#xff0c;这时我们需要将新版卸载安装到旧版本&#xff0c;本文便是记录如何在官网下载旧版本的android studio。 下载地址 1、查看Release notes 2、查看过往版本 3、点击下载旧版…

Jenkins持续集成实现过程(简易版)

我就用一台服务器模拟&#xff0c;gitlab用公司现成的&#xff0c;jenkins安装发版都是在同一台服务器上 安装Jenkins&#xff08;Docker&#xff09; 宿主机上创建数据映射目录 mkdir /data/jenkins给映射目录权限&#xff0c;不然jenkins用不了 chmod -R 777 /data/jenkin…

【es6】Promise实现

友情链接 关于promise的介绍&#xff0c;请看此篇水文 本篇文章只是介绍实现promise以及promise常用方法。 正文 Promise使用 let promise new Promise((resolve,reject)>{resolve(success); //这里如果是reject(fail) }); promise.then((res)>{console.log(res); …

企业AD域管理:ADManager Plus助您轻松掌控全局

在现代企业中&#xff0c;Active Directory&#xff08;AD&#xff09;域是一个至关重要的组成部分。它作为一种身份验证和授权机制&#xff0c;管理着企业网络中的用户、计算机、组和其他资源。然而&#xff0c;随着企业规模和复杂性的不断增长&#xff0c;AD域的管理变得越来…

深入挖掘地核和地幔之间的相互作用

一本新书介绍了我们在理解地核-地幔相互作用和共同进化方面的重大进展&#xff0c;并展示了提高我们对地球深层过程的洞察力的技术发展。 与地核-地幔共同演化相关的地球深层结构和动力学的图示。图片来源&#xff1a;白石千寻 Editors Vox是 AGU 出版部的博客。 地球深层内部很…

螺杆支撑座使用深沟球轴承和角接触球轴承的区别?

螺杆支撑座是连接丝杆和电机的轴承固定座&#xff0c;与滚珠螺杆搭配使用&#xff0c;能够获得高刚性、高精度的稳定的回转性能&#xff0c;因此被广泛应用在各行各业中。 使用螺杆支撑座之所以能够获得稳定的回转性能&#xff0c;主要是因为最佳轴承的使用&#xff0c;通俗点说…