Jvm学习笔记(二)GC

GC

垃圾收集(GC)起源于Lisp,远比Java的历史更久,它主要思考了三件事情:

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

本章就根据这三个点进行分析。

哪些内存需要回收?

在java堆中存放着无数的对象,有些对象任然在使用,有些对象已经死去(不被任何途径所使用),我们需要回收的便是这些死去的变量。

引用计数

有一种判断对象是否存活的办法便是引用计数法,它在对象内存中开辟出一块区域,当此对象有其他对象引用之时,该区域的引用计数器便加1,当失去一个引用时,便减1,当引用计数为0时,代表着该对象处于无用的状态了。

然而,引用计数却不是jvm中用于判断对象是否存活的方法,原因就是使用引用计数还需要许多其他的工作要做,因为单纯的引用计数会有很多问题,最常见的便是循环引用,既 A->B->A,这导致了a和b的引用计数永远不会为0,对象永远也无法回收。

可达性分析

java采用可达性分析的方法判断对象是否存活,具体方法是:通过一系列"GC roots"作为起始节点集,从这些节点根据引用关系向下搜索,形成一条引用链,如果一个对象不在引用链中,则可以认为该对象已死亡。

可达性分析的关键就是GC Roots,具体GC Roots有:

  1. 虚拟机栈中所引用的对象。
  2. 方法区静态属性所引用的对象,比如引用类型的静态变量。
  3. 方法区中常量所引用的对象,比如字符串常量池里的引用。
  4. 本地方法栈中JNI所引用的对象。
  5. 虚拟机内部引用。
  6. 所哟被同步锁(synchornized)持有的对象。
  7. 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

如何回收?

大部分的虚拟机的垃圾收集器,都遵循了分代收集的理论进行设计。分代收集建立在两个分代假说之上:

  1. 弱分代假说:绝大部分对象都是朝生夕死。

  2. 熬过越多次垃圾收集过程的对象就越难消亡。

    这两个假说奠定了多款垃圾收集器的一致设计原则:将Java堆划分成不同的区域,然后根据对象的年龄(熬过垃圾回收的次数)分到不同的区域中存储。如果一个区域中的对象大部分都是朝生夕死,那么只要将其中少量需要存活的对象标记即可实现清理,如果一个区域都是不容易回收的对象,那么就可以降低回收频率。根据不同的区域回收,出现了Minor GC、MajorGC、Full GC。

    根据上文,现代商业虚拟机里,一般会把内存划分为年轻代和老年代。然而实际情况中,对象的相互引会存在跨区引用,单独对一个区域进行GC时,任然会出现不少问题,不得不额外遍历整个区域,于是添加第三条经验法则:

  3. 跨代引用假说:跨代引用相对于同代引用来说仅占少数。

依据这条假说,只需在新生代上建立一个全局的数组结构(记忆集)这个结构把老年代划分为若干小块,标示出那一块会发生跨代引用,当发生Minor GC时,只有包含了跨代引用的小块内存才会被加入到GC ROOTS进行扫描。

根据不同内存区域中对象的特性,虚拟机采用不同的清理算法:1、标记-清除法 2、复制算法 2、标记整理法。

标记-清除算法

标记所有需要回收的对象,然后统一回收掉被标记的对象。

缺点:1:效率低,大量对象需要标记。2:内存碎片化严重

标记-复制算法

将内存分为两个区域,每次只用其中一块,当内存用完需要回收时候,将存活的对象统一复制到第二块区域,第一块区域可以直接清空。

优点:回收内存只需要移动指针就行,清除效率高,同时解决了碎片化的问题。

缺点:内存只有一半可以用,浪费严重。

改进:根据使用经验,大部分对象是朝生夕死,于是将内存分为三块区域,一块Eden两块Survivor,比例是8:1:1。每次只是用Eden和一块Survivor,当需要回收时候,将Eden和Survivor中的对象复制到另一块Survivor,然后清空剩下的区域。这样只有10%的内存被浪费了,兼顾了效率和空间。

标记-整理算法

标记复制算法如果想要百分百完全,必须要用50%的内存做备份,如果按照8:1:1分配,在遇到极端情况下,会出现大部分内存不需要回收从而导致内存不够的问题,对于老年代,这个问题比较常见,于是采用标记整理算法:在标记的基础上,将所有存活的对象向内存空间的一端移动,然后直接清理掉区域外的所有内存。

优点:没有碎片化的问题

缺点:移动整理内存时,必须停止一切用户活动,必须要谨慎考虑使用。

想要减少移动时间,需要使用链式结构,但是会带来更多复杂度,可以看出,无论是否移动对象都存在弊端,移动则回收内存时会更复杂,不移动,则碎片化严重,分配内存时候复杂。具体要根据虚拟机关注的重点来选择。

什么时候回收——如何回收才能降低影响?

可达性分析的垃圾标记有两个环节 GC roots枚举和GC roots 引用链,这两个环节决定了gc时机。GC有一个必须的要求,那就是GC时,整个虚拟机中对象的引用关系是不能变的,或者说回收结果必须是回收发生时就已经决定好的。 这就导致了GC时候不可避免的出现“Stop The World”,会严重导致运行效率,gc的回收时机就变得至关重要。

GC Roots 节点枚举时机

根节点枚举时必须有保障当前对象之间引用关系一致性的快照,一致性指的是枚举期间对象之间的引用关系不回变化,这导致枚举时不可避免停止整个用户线程,哪怕停止的时间非常短。

为了快速完成根节点枚举,引入了OopMap的数据结构,一旦类完成加载,就把对象内什么偏移上是什么数据给计算出来,这样就可以在枚举时,直接获取到需要的数据。

安全点

OopMap虽然有用,但是对象关系是时刻变化的,如果为每一条指令都都创建OopMap,则会大大增加内存,于是在特点的位置创建OopMap,这些位置就是安全点。着意味着,只要在安全点的对象关系是正确的,也只有在这个时刻可以执行GC。

出于以上,安全点会选择**“程序长时间执行”**的位置,因为长时间执行的地方如果没有安全点,就会导致程序长时间无法进入安全点,导致gc等待的时间过长从而导致JVM线程停止过长,但是指令流执行的速度其实都很快,普通指令不太可能出现长时间执行,只有那些指令序列复用的地方会出现长时间执行,比如方法的调用、非counted的循环跳转、异常跳转等,在这些指令处都会产生安全点。(这里就会存在一个优化的方向,在counted循环中,如果循环足够大,就会出现迟迟不能进入安全点导致线程被停止过久)

线程中断

  1. 抢险式中断:会停止所有线程,如果用户线程发现不在安全点上,就会恢复该线程,当运行到安全点上时就会中断该线程。目前没有虚拟机采用这种方案。
  2. 主动式中断:不会中断线程,而是设置一个标志,每个线程在执行中会不断轮训这个标志,当发现标志变化时候,运行到最近的安全点就会主动挂起。HotSpot采用内存保护陷阱的方法,把轮训优化到只有一条汇编指令。

安全区域

当用户线程不执行时,就无法走到安全点然后进入挂起,于是引入安全区域,安全区域指的是在某一段代码片段中,引用关系不会发生变化,可以理解为拉伸开的安全点,当用户线程进入到安全区域以后,会有一个标记,发生GC时候,出于安全区域的线程也视作出于安全点。

GC Roots 引用链

虚拟机中,引用链时通过一个独立线程并行记录的,但是随着用户线程的运行,对象的的引用关系在不停的变化,有两种情况

  1. 本来该消亡的对象任然存在,这种情况可以接受,最多在下几轮去回收这些内存。

  2. 本来应该存在的对象被认定为回收,这种情况会导致严重的错误。重点是处理这种情况。

    想象一下,将已经扫描过所有依赖的对象认定为黑色,将扫描部分但不是全部依赖的对象认定为灰色,将没有扫描过的对象认为是白色,那么GC roots引用链扫描的过程其实就是按照黑色节点->灰色节点->白色节点中不断添加黑色节点以及减少白色节点的过程。黑色节点最终会存活,白色节点最后会回收。当收集过程中,原本一个被灰色节点依赖的黑色节点中的依赖发生变化断开,也即是灰色->黑色断开,此时这个黑色节点变成了白色节点,然后又有新的黑色节点依赖了这个白色节点,那么理想情况这个节点应该变成黑色,但是由于并行的缘故,这个变化发生在扫描之后,这个节点任然被认定为白色被回收,发生了严重的事故。根据一些理论得出来两个条件,只有当都满足时候会出现对象消失的问题:

  3. 赋值器插入了一条或多条从黑色对象到白色对象的新引用。(既此白色对象应该变成黑色对象)

  4. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。(也就是说一个白色节点本身被灰色和黑色同时引用,但是由于黑色节点只会被扫描一次,所以如果删除灰色节点到白色节点的引用,系统会默认引用链已经断开而没有发现任然有黑色节点连接着白色节点)

于是只要破坏两个条件之一就可以消除这种现象,分别产生了两个方案:增量更新、原始快照。

  1. 增量更新:将黑色节点新增到白色节点的引用时,记录这个引用,当扫描完成以后针对这个引用再次扫描,相当于把黑色节点重新置为灰色节点
  2. 原始快照:当灰色对象删除指向白色对象的引用时,将这个删除的引用记录下来,等扫描结束以后,在这些引用关系中的灰色对象为根重新扫描一次。


分代收集的跨区引用问题

分代回收内存有一个跨区引用问题,既测量回收A区的对象时,在B区有对象引用了该对象,如何处理。
复制代码

记忆集与卡表

通过引入记忆集的数据结构解决分区引用问题,记忆集记录了从非收集区域(B)指向收集区域(A)的指针集合。不考虑成本问题最简单的实现方式是数组就可以。

数组成本太过高昂,实际上只需要知道某一块区域是否有指向收集区域的引用即可,所以有三种精度:字长精度、对象精度、卡精度。最常用的是卡卡精度,卡表的作用是映射关系,类似map。HotSpot采用字节数组来实现卡表:

CARD_TABLE [this address >> 9]=1;

每块区域大小2的9次幂既512字节,在这块区域内如果有引用其他区域对象,CARD_TABLE[curAddress]=1 设置为1。

结语

本章主要介绍了一下java中GC的一些原理和细节,但是GC的真正的实现者——垃圾收集器这里没有提到,垃圾收集器有许多中,它们针对了不同的分代内存区域,相互搭配,按照不同的规则回收内存。但是篇幅有限这里没有展开细说,我准备作为番外篇单独介绍一下各个垃圾收集器。

感谢收看~。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mac浏览器无法上网但可以用微信等

可以使用微信QQ等,但是浏览器无法上网,Mac浏览器无法上网怎么办,Mac浏览器无法上网但可以用微信等(百度了一下,没有找到原因是为什么,只找到了解决方法,记录一下) 1.首先我们打开Ma…

Centos安装docker以及通过docker部署Mysql,照做就行!

1.安装docker 1.1给虚拟机联网(反斜杠带表该语句没写完) yum install -y yum-utils \device-mapper-persistent-data \lvm2 --skip-broken 1.2更新本地文件镜像 # 设置docker镜像源 yum-config-manager \--add-repo \https://mirrors.aliyun.com/doc…

【Python_Selenium学习笔记(四)】基于Selenium模块实现键盘操作

基于Selenium模块实现键盘操作 前言 在 Selenium 模块中,提供了一个 Keys 类,来处理键盘操作; 在 Selenium 模块中,使用 send_keys() 方法,来模拟键盘输入, 此篇文章主要介绍如何使用 Keys 类 和 send_ke…

vue请求本地JSON文件(注意路径 否则会404)

npm i axios // main.jsimport axios from "axios";Vue.prototype.$axios axios; //全局注册,使用方法为:this.$axios...vue/cli 2 json文件存放目录为 根目录下static/json/aaa.json // 使用getVideoData() {this.$axios.create({baseURL: "&quo…

springboot 部署k8s(二)

系列文章目录 目录 系列文章目录 前言 操作步骤 1.springboot.yaml文件 2.查看deployment 3.查看service服务 4.验证服务 总结 前言 springboot 部署到k8s 上。里面涉及了deployment, Service, NodePort. 操作步骤 1.springboot.yaml文件 apiVersion: apps/v1 kind: …

codeblocks20.3配置wxWidget3.2.2.1

codeblocks20.3 # 英文版自带gcc810,不汉化 wxWidget3.2.2.1 github下载源码 win11专业版 1.下载wxWidget3.2.2.1 源码 2.下载后解压到一个目录中,不要含中文和空格。我放在:d:\wxWidget3.2.2.1 3.打开终端cd build/msw 4.编译wxWidgets 为 …

本行卡转账基本流程说明

1、业务大致流程 2、基本业务描述 大概流程说明:(牵涉到调用硬件、不便多说) 用户插卡后、选择转账交易、依次输入转入账户卡号和转账金额后,用户确定转账信息;转账交易发送前:需要根据插卡账户信息和转账…

Java基础(四)数组

1. 数组的概述 1.1 为什么需要数组 需求分析1: 需要统计某公司50个员工的工资情况,例如计算平均工资、找到最高工资等。用之前知识,首先需要声明50个变量来分别记录每位员工的工资,这样会很麻烦。因此我们可以将所有的数据全部存…

TCP报文 Flood攻击原理与防御方式

TCP交互过程中包含SYN、SYN-ACK、ACK、FIN和RST报文,这几类报文也可能会被攻击者利用,海量的攻击报文会导致被攻击目标系统资源耗尽、网络拥塞,无法正常提供服务。接下来我们介绍几种常见的Flood攻击的原理和防御方式。 SYN Flood攻击与防御…

【Qt】项目开发遇到问题及解决总结

1.控件的触发:toggle()、triggered()、clicked() 区别: 都是按钮点击后发射的信号 clicked():用于Button发射的信号triggered():用于QAction发射的信号, trigger是一次性的。 点击后,无法改变状态。 要么…

【案例教程】基于RWEQ模型的土壤风蚀模数估算及其变化归因分析实践技术

土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一,土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2,占国土总面积的16.7%,严重影响这些地区的资源开发和社会经…

论文笔记|ECCV2022:Self-Promoted Supervision for Few-Shot Transformer

论文地址:https://arxiv.org/abs/2203.07057 代码链接:https://github.com/DongSky/few-shot-vit 这篇论文在2022年发表在ECCV上,论文的题目是用于小样本Transformer的self-promoted supervision(自我推荐监督) 1 Mot…

求给定集合中好数对的个数

已知一个集合A,对A中任意两个不同的元素求和,若求得的和仍在A内,则称其为好数对。例如,集合A{1 2 3 4},123,134,则1,2和1,3 是两个好数对。 编写程序求给定集合中好数对的个数。 注:…

java设计模式(1) 适配器模式、装饰器模式

适配器模式 适配器就是一种适配中间件,它存在于不匹配的了两者之间,用于连接两者,使不匹配变得匹配。 手机充电需要将220V的交流电转化为手机锂电池需要的5V直流电 知识补充:手机充电器输入的电流是交流,通过变压整流…

XML复习

目录什么是XMLXML中的内容可以干什么XML文件的创建以及其格式XML的文档约束-DTD约数XML的文档约束-schema约束Dom4J 解析XML 文档什么是XML XML 全称(extensible Markup Lanage) 可扩展标记语言它是一种数据的表示形式, 可以存储复杂的数据格式以及我们自己定义的格式.XML经常…

windows安装ubuntu时错误WslRegisterDistribution failed with error: 0x8007023e的解决方法

cmd或者powershell安装,或者打开linux时 莫名的出现了如下错误: Installing, this may take a few minutes... WslRegisterDistribution failed with error: 0x8007023e Error: 0x8007023e {Application Error} The exception s (0x尝试了很多的方法都不…

Qt图片显示有波纹

现象 Qt中当渲染显示的分辨率比原图片分辨率小时,就会有波纹。如下图所示,左边是正常显示,右边衬衫那里产生严重的波纹。这种波纹在计算机图形学叫摩尔纹,这是纹理贴图采样出现走样的现象,纹理分辨率过大时就会出现这…

解决Windows微信和 PowerToys 的键盘管理器冲突

Windows开机之后PowerToys能正常使用, 但是打开微信之后设置好的快捷键映射就全部失效了 打开微信 -> 左下角三条杠 -> 设置 -> 快捷键 首先我把微信的快捷键全部清空了,发现还是没用 然后发现了设置里默认勾选了检测快捷键,我在想程序肯定是一直在后台检测,而powerTo…

可以计算“如何把程序写好”吗?

其实简单理解这个问题就是“可不可以用机器来判断人的程序写得好不好? 后面我查阅了资料,历史上有一个对计算机领域影响颇深的可计算理论,“计算”应该就来源于这里。其实继续深挖还能找出很多涉及计算机本源的有趣的知识,比如图…

异构计算给我们带来了哪些思考?

虽然异构计算的快速发展给企业创新带来了更加强大的算力支撑,但真正推动异构计算的高速发展和应用落地,笔者认为还需要在以下三个方面做好功课。 从2022年火爆全球的元宇宙,到今年的ChatGPT,以人工智能为代表的科学技术正在创造出…