Java Jvm中的垃圾回收机制

news2024/11/17 14:46:23

jvm的垃圾回收机制是什么

jvm的垃圾回收机制是GC(Garbage Collection),也叫垃圾收集器。

GC基本原理:将内存中不再被使用的对象进行回收;GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、老年代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。

在这里插入图片描述

什么是垃圾回收

java相较于c、c++语言的优势之一是自带垃圾回收器,垃圾回收是指不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc 方法来建议执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。

为什么需要垃圾回收

如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。

jvm垃圾回收原理

在JVM运行时数据区存在一个堆区, 堆是一个巨大的对象池。在这个对象池中管理着数量巨大的对象实例,而池中对象的引用层次,有的是很深的。一个被频繁调用的接口,每秒生成对象的速度,是很大的,同时,对象之间的关系,形成了一张巨大的网。

Java 一直在营造一种无限内存的氛围,但对象不能只增不减,所以需要垃圾回收;那 JVM 是如何判断哪些对象应该被回收?哪些应该被保持呢?这就要用到JVM的垃圾回收机制了,也就是我们常说的GC(Garbage Collection),也叫垃圾收集器。

GC (Garbage Collection:即垃圾回收)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、老年代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

  • 对新生代的对象的收集称为minor GC
  • 对老年代的对象的收集称为Full GC
  • 程序中主动调用System.gc()强制执行的GC为Full GC

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

  • 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
  • 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
  • 弱引用:在GC时一定会被GC回收
  • 虚引用:由于虚引用只是用来得知对象是否被GC

对象被标记为垃圾方法

JVM的内存结构包括五大区域:程序计数器虚拟机栈本地方法栈堆区方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

1、引用计数器

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

优点: 引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点: 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。

在这里插入图片描述
对以上代码通过引用计数法分析:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、可达性分析

可达性算法是目前主流的虚拟机都采用的算法,程序把所有的引用关系看作一张图,从一个节点GC Roots开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象(栈帧中的本地变量表);
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象。
    在这里插入图片描述

可以得出对象实例1、2、4、6都具有对象可达性,也就是存活对象,不能被GC回收的对象。而随想实例3、5虽然直接相连,但并没有任何一个GC Roots与之相连,即GC Roots不可达对象,就会被GC回收的对象。

垃圾回收算法

1、(标记-清除)算法

标记/清除算法的基本思想就跟它的名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

标记阶段:
标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的 GC Roots 对象,对从 GCRoots 对象可达的对象都打上一个标识,一般是在对象的 header 中,将其记录为可达对象。

清除阶段
清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header 信息),则将其回收。

在这里插入图片描述
上图是标记/清除算法的示意图,在标记阶段,从对象 GC Root 1 可以访问到 B 对象,从 B 对象又可以访问到 E 对象,因此从 GC Root 1 到 B、E 都是可达的,同理,对象 F、G、J、K 都是可达对象;到了清除阶段,所有不可达对象都会被回收。
在垃圾收集器进行 GC 时,必须停止所有 Java 执行线程(也称"Stop The World"),原因是在标记阶段进行可达性分析时,不可以出现分析过程中对象引用关系还在不断变化的情况,否则的话可达性分析结果的准确性就无法得到保证。在等待标记清除结束后,应用线程才会恢复运行。

标记/清除算法缺点:
效率问题
标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,这无疑很耗费时间,而且 GC 时需要停止应用程序,这会导致非常差的用户体验。
空间问题
标记清除之后会产生大量不连续的内存碎片(从上图可以看出),内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

2、复制算法

复制算法是将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉
在这里插入图片描述
复制算法每次都是对整个半区进行内存回收,这样就减少了标记对象遍历的时间,在清除使用区域对象时,不用进行遍历,直接清空整个区域内存,而且在将存活对象复制到保留区域时也是按地址顺序存储的,这样就解决了内存碎片的问题,在分配对象内存时不用考虑内存碎片等复杂问题,只需要按顺序分配内存即可。

复制算法缺点:
复制算法简单高效,优化了标记清除算法的效率低、内存碎片多问题,存在缺点:

  • 将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;
  • 如果对象的存活率很高,极端一点的情况假设对象存活率为100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的。

3、(标记-整理)算法

标记-整理算法算法与标记/清除算法很像,事实上,标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。

在这里插入图片描述
可以看到,回收后可回收对象被清理掉了,存活的对象按规则排列存放在内存中。这样一来,当我们给新对象分配内存时,jvm 只需要持有内存的起始地址即可。标记/整理算法弥补了标记/清除算法存在内存碎片的问题消除了复制算法内存减半的高额代价,可谓一举两得。

标记/整理缺点:
效率不高:不仅要标记存活对象,还要整理所有存活对象的引用地址,在效率上不如复制算法。

4、分代回收算法

分代收集算法的思想是按对象的存活周期不同将内存划分为几块一般是把 Java 堆分为新生代和老年代(还有一个永久代,是 HotSpot 特有的实现,其他的虚拟机实现没有这一概念,永久代的收集效果很差,一般很少对永久代进行垃圾回收),这样就可以根据各个年代的特点采用最合适的收集算法。

特点:

新生代:朝生夕灭,存活时间很短。采用复制算法来收集
老年代:经过多次 Minor GC 而存活下来,存活周期长。采用标记/清除算法或者标记/整理算法收集老年代

新生代中每次垃圾回收都发现有大量的对象死去,只有少量存活,因此采用复制算法回收新生代,只需要付出少量对象的复制成本就可以完成收集;
老年代中对象的存活率高,不适合采用复制算法,而且如果老年代采用复制算法,它是没有额外的空间进行分配担保的,因此必须使用标记/清理算法或者标记/整理算法来进行回收。

在这里插入图片描述
新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。

老年代中的对象因为对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。

新产生的对象优先进去Eden区,当Eden区满了之后再使用Survivor from,当Survivor from 也满了之后就进行Minor GC(新生代GC),将Eden和Survivor from中存活的对象copy进入Survivor to,然后清空Eden和Survivor from,这个时候原来的Survivor from成了新的Survivor to,原来的Survivor to成了新的Survivor from。复制的时候,如果Survivor to 无法容纳全部存活的对象,则根据老年代的分配担保(类似于银行的贷款担保)将对象copy进去老年代,如果老年代也无法容纳,则进行Full GC(老年代GC)。

大对象直接进入老年代:JVM中有个参数配置
-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。

长期存活的对象进入老年代:JVM给每个对象定义一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被移入Survivor并且年龄设定为1。没熬过一次Minor GC,年龄就加1,当他的年龄到一定程度(默认为15岁,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。但是JVM并不是永远要求年龄必须达到最大年龄才会晋升老年代,如果Survivor 空间中相同年龄(如年龄为x)所有对象大小的总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。

分代回收:

我们从一个object1来说明其在分代垃圾回收算法中的回收轨迹。

1、object1新建,出生于新生代的Eden区域。

在这里插入图片描述
2、minor GC,object1 还存活,移动到From suvivor空间,此时还在新生代。

在这里插入图片描述
3、minor GC,object1 仍然存活,此时会通过复制算法,将object1移动到ToSuv区域,此时object1的年龄age+1。

在这里插入图片描述
4、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象并没有达到survivor的一半,所以此时通过复制算法,将fromSuv和Tosuv 区域进行互换,存活的对象被移动到了Tosuv。

在这里插入图片描述
5、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象已经达到survivor的一半以上(toSuv的区域已经满了),object1被移动到了老年代区域。

在这里插入图片描述

6、object1存活一段时间后,发现此时object1不可达GcRoots,而且此时老年代空间比率已经超过了阈值,触发了majorGC(也可以认为是fullGC,但具体需要垃圾收集器来联系),此时object1被回收了。fullGC会触发 stop the world。

在这里插入图片描述

在以上的新生代中,我们有提到对象的age,对象存活于survivor状态下,不会立即晋升为老年代对象,以避免给老年代造成过大的影响,它们必须要满足以下条件才可以晋升:

1、minor gc 之后,存活于survivor 区域的对象的age会+1,当超过(默认)15的时候,转移到老年代。
2、动态对象,如果survivor空间中相同年龄所有的对象大小的综合和大于survivor空间的一半,年级大于或等于该年纪的对象就可以直接进入老年代。

原文备份:https://www.php.cn/java/guide/500566.html

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

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

相关文章

认识BASH这个Shell

文章目录 认识BASH这个Shell硬件、内核与shell为什么要学命令行模式的Shell?Bash Shell的功能命令与文件补全(TAB)命令别名设置(alias)历史命令(history)任务管理、前台、后台控制(jobs,fg,bg)通配符程序化脚本 查询命令是否为Bash shell 的内…

前端--移动端布局--2移动开发之flex布局

目标: 能够说出flex盒子的布局原理 能够使用flex布局的常用属性 能够独立完成携程移动端首页案例 目录: flex布局体验 flex布局原理 flex布局父项常见属性 flex布局子项常见属性 写出网首页案例制作 1.flex布局体验 1.1传统布局与flex布局 传…

nacos 部署 配置

文章目录 一、Nacos简介 1、为什么叫Nacos2、Nacos是什么3、能干嘛4、去哪下二、安装并运行Nacos 2.1 基础环境及配置:三、Nacos作为服务注册中心演示 3.1 基于Nacos的服务提供者 2、POM文件3、YML文件4、主启动5、业务类6、测试7、nacos控制台3.2 基于Nacos的服务消…

[POJ - 1080 ]Palindrome(区间DP)

[POJ - 1080 ]Palindrome( 区间DP) 1、问题2、分析状态表示状态转移空间优化 3、代码 1、问题 给定一个字符串,通过添加最少的字符,使得这个字符串成为一个回文字符串。 2、分析 状态表示 f [ i ] [ j ] f[i][j] f[i][j]表示将…

2023年产品经理需要考的证书,NPDP含金量真高

产品经理国际资格认证NPDP是新产品开发方面的认证,集理论、方法与实践为一体的全方位的知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会(PDMA)成立于1979年,是…

盖子的c++小课堂——第十七讲:递归

前言 通知一下,以后每周不定期更新,有可能是周六更新,也可能是周日吧,反正会更新的~~还有我新出的专栏《跟着盖子读论语》,记得订阅一下啊跟着盖子学《论语》_我叫盖子的盖鸭的博客-CSDN博客 三元表达式 三元表达式…

单链表中二级指针的使用以及单链表的增删查改操作

前言:在链表的构建中,链表的初始化和销毁为何需要使用一个二级指针,而不是只需要传递一个指针就可以了,其问题的关键就在于c语言的参数传递的方式是值传递 那么,这篇文章就来聊一聊在链表的初始化中一级指针的传递和二级指针的区别,并总结给出单链表的C语…

AutoCAD使用技巧

AutoCAD使用技巧 环境说明AutoCAD 导入EXCELAutoCAD移动对象到原点 环境说明 本文基于AutoCAD 2021版本。 AutoCAD 导入EXCEL 如果菜单栏隐藏点击显示菜单栏: 在excel中选中copy内容赋值,AutoCAD中选择:编辑-选择性粘贴-作为AutoCAD图元…

必用WhatsApp营销的4个理由

WhatsApp是世界上最受欢迎的消息传递应用程序。每天有1万新用户加入WhatsApp。各种规模的公司都利用该平台与世界各地的客户进行有效的沟通,这要归功于其广泛的覆盖范围、用户友好的设计和安全的端到端加密。因此,WhatsApp聊天机器人迅速普及。 1.为您的…

Kotlin的出现无疑是为了超越Java而存在

Kotlin的出现无疑是为了超越Java而存在。在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言,背景就是Oracle告Google侵权使用java。众所周知,Java的跨平台的开发语言,得益于虚拟机。我比较关注Kotlin用于Android…

Python 使用pipreqs命令生成 `requirements.txt`报错

Python 使用pipreqs命令生成 requirements.txt报错:Fatal error in launcher: Unable to create process using ‘“E:\Anaconda\python.exe” “D:\Anaconda\Scripts\pipreqs.exe” ./ --encodingutf-8’: ??? 问题描述—Python 使用pipreqs命令生成 requireme…

47.对齐网格项目和轨道

首先我们先将模块1关闭,模块2使用CSS网格 display: none;.container--2 {/* STARTER */font-family: sans-serif;background-color: black;font-size: 40px;margin: 100px;width: 1000px;height: 600px;/* CSS GRID */display: grid;}然后我们常间一个三列两行的网…

STM32开发(十六)STM32F103 片内资源 —— 实时时钟RTC 详解

文章目录 一、基础知识点二、开发环境三、STM32CubeMX相关配置四、Vscode代码讲解五、结果演示串口显示乱码解决方案 一、基础知识点 本实验通过stm32片内资源RTC实现实时时钟,通过数码管显示时间。设定闹钟,实现准点报时。 数码管相关知识点&#xff1…

maven总结

maven maven1.构建依赖2.依赖冲突的规则3.可选依赖和排除依赖可选依赖排除依赖 4.依赖范围5.项目构建生命周期6.插件7.模块聚合8.模块继承9.属性(类似常量)(1) 自定义属性(2)直接调用内置属性 10.多个环境配…

PingPingPing

拿到题的时候我是没有丝毫的思路,可能是没有做过太多命令注入的题目,所以反应不过来 还是查看的别人的wp,得知这是一道有关命令注入的题目 通过页面提示传入ip并且利用管道符查看所有文件 但是当我们查看flag.php的时候发现页面过滤掉了空格 …

诊断DTC故障码两种形式的转换:符号转数字

诊断DTC code数据由3个字节组成:HighByte + MiddleByte + LowByte。有两种表现形式: 数字:三个字节值用16进制表示,比如0xC07304符号:这种形式更直观地用字符描述该DTC故障所属的系统,故障类型等信息我们重点讲一下符号的形式: 字符形式的DTC故障码,由7个字符组成,代…

Chatbox - 一款适用于 GPT-4 / GPT-3.5 (OpenAI API) 的桌面应用程序

简介 给大家推荐一款适用于 GPT-4 / GPT-3.5 (OpenAI API) 的桌面应用程 ChatBox,开源的 ChatGPT API (OpenAI API) 跨平台桌面客户端,Prompt 的调试与管理工具,也可以用作 ChatGPT Plus 平替。 下载 ► chatBox 下载安装 ⇲ 为什么不直接…

3D目标检测--PointPillars论文和OpenPCDet代码解读

文章目录 1. 论文动机2. PointPillars概述3. PointPillars模型3.1 Pillar Feature Net3.1.1 Pillar Feature Net模块理论解析3.1.2 Pillar Feature Net模块代码解析 3.2 Backbone(2D CNN)3.2.1 Backbone(2D CNN)模块理论解析3.2.2…

LeetCode 312. Burst Balloons【区间DP】困难

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

校验规则引擎

目录 一 架构设计图 二 表设计及数据展示 三 顶层接口 四 压测结果 五 其他规则引擎比较 适用场景:校验场景以及使用该思想进行可视化配置化开发(可大幅提高开发效率,长期维护简单) 例如:履约系统下单中的校验&…