JVM进阶(3)

一)什么是垃圾?

垃圾指的是在应用程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾,如果不及时的针对内存中的垃圾进行清理,那么这些垃圾对象所占用的内存空间可能一直保留到应用程序结束,被保留的空间无法被其他对象使用,甚至有可能导致内存溢出

JVM规范说了并不需要必须回收方法区,不具有普遍性,元空间使用的是JVM之外的内存

二)如何判断一个对象是否是垃圾:
2.1)引用计数器:

引用计数器优点:实现简单,垃圾对象便于标识,判断效率高,回收没有延迟性

引用计数器缺点:效率要比可达性分析要强,随时发现,随时回收,实现简单,但是可能存在内存泄漏

1)它需要单独的字段存储计数器,这样的做法增加了存储空间的开销

2)每一次赋值都是需要更新计数器,伴随着加法和减法的操作

3)引用计数器又一个严重的问题,就是无法处理循环引用的问题,这是一条致命缺陷

虽然在JAVA中没有使用循环引用但是Python中使用了两种方法解决了这个问题:

1)手动解除,很好理解,就是在合适的时机,接触引用关系

2)使用弱引用weakref,

2.2)可达性分析:

局部变量表,静态引用变量,通过引用链关联的引用链是不会被回收,局部变量表天然作为GCROOTS,就是只是进行新生代回收的时候老年代的引用也可以作为GCROOTS

1)虚拟机栈中引用的对象(栈帧中的本地方法表)
2)方法区中(1.8称为元空间)的类静态属性引用的对象
一般指被static修饰的对象,加载类的时候就加载到内存中
3)方法区中的常量引用的对象。
4)本地方法栈中的JNI(native方法)引用的对象。
注意即使可达性算法中不可达的对象也不是一定要马上被回收还有可能被抢救一下要真正宣告对象死亡需经过两个过程:
1)可达性分析后没有发现引用链
2)查看对象是否有finalize方法,如果有重写且在方法内完成自救[比如再建立引用],还是可以抢救一下,注意这边一个类的finalize只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。[如果类重写finalize且还没调用过,会将这个对象放到一个叫做F-Queue的序列里,这边finalize不承诺一定会执行,这么做是因为如果里面死循环的话可能会时F-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。

如果要是使用可达性分析算法来判断内存是否要进行回收,那么分析工作必须要在一个能够保持一个一致性的快照来进行,这一点不满足的话分析结果的准确性就无法保证,这一点也就是GC必须进行STW的一个重要原因,即使是号称几乎不会发生停顿的CMS垃圾回收器枚举根节点的时候也是必须要停顿的

三)对象的finalize方法详解:

1)JAVA语言提供了对象终止机制来允许开发人员提供对对象销毁之前的自定义处理逻辑,当垃圾回收器发现没有引用指向一个对象的时候,就是垃圾回收器在进行回收此对象之前,总是会调用这个对象的finalize方法,当垃圾回收器发现没有任何一个引用指向该对象的时候,总是会调用这个对象的finalize()方法,finalize()方法允许在子类中被重写,用于在垃圾回收时进行资源释放和垃圾清理的工作,关闭文件,套接字和数据库连接等等

2)永远不要试图调用某一个对象的finalize方法,应该交给垃圾回收器来调用

2.1)finalize方法可能会导致对象复活

2.2)finalize()方法执行时间是没有保障的,他完全由GC线程所决定,极端情况下,如果不发生GC,那么一个糟糕的finalize()方法会影响程序的执行性能

如果说所有的根节点都无法访问到某一个对象,说明该对象已经不再被使用了,一般来说,此对象需要被回收,但事实上,也并非是非死不可的,这个时候他们暂时处于唤醒状态,一个无法触及的对象很有可能在某一个条件下复活自己,如果这样没那么对于他的回收就是极其不合理的,为此,定义虚拟机中的对象可能的三种状态:

1)可触及的:从根节点开始可以到达这个对象

2)可复活的:对象的所有引用都被释放,但是对象很有可能在finalize()中复活

3)不可触及的:对象的finalize()方法被调用并且没有复活,那么就会进入到不可及状态,不可触及的对象不可能复活,因为finalize()方法只会被调用一次;以上三种状态中,是由于finalize()方法的存在进行的区分,只有在对象不可触及的时候才可以被回收

3)所以说判断一个对象是否可以进行回收至少要经历两次标记过程

3.1)如果说对象A到Gcroots不存在引用链,那么就进行第一次标记

3.2)如果筛选,进行判断对象是否执行了finalize()方法

a)如果对象没有重写finalize()方法或者是finalize()方法已经被虚拟机调用过,那么虚拟机不会再重新调用该方法,直接该对象就被标记成不可达的

b)如果对象A重写了finalize()方法,还没有被执行过,那么该对象会被插入到一个队列中,这是由虚拟机自动创建的低优先级的finalizer线程触发其finalizer方法执行

c)finalize()方法是对象进行逃脱死亡的最后机会,稍后GC就会对队列中的对象做第二次标记,如果该对象和引用链上面的任意一个对象建立了联系,那么在第二次标记的过程中此对象会被移出即将回收的集合,之后,对象会再次出现没有引用存在的情况,在这种情况下fnalize()方法不会被再次调用,对象会直接变成不可触及的状态

代码执行两次,一次分为finalize()方法没有被注释,一种有注释

public class Test {public static Test obj;//这是一个类变量//    @Override
//    protected void finalize() throws Throwable {
//        System.out.println("调用当前链上的finalize方法");
//        obj=this;//当前带回收的对象在finalize方法上和一个引用链上面的对象建立了联系
//    }public static void main(String[] args) throws InterruptedException {obj=new Test();//对象第一次拯救自己obj=null;System.gc();//调用垃圾回收器System.out.println("第一次GC");//因为finalizer线程优先级很低,主线程暂停2s来等待他Thread.sleep(3000);if(obj==null){System.out.println("对象已经死了");}else{System.out.println("对象还活着");}obj=null;System.gc();//调用垃圾回收器System.out.println("第二次GC");//因为finalizer线程优先级很低,暂停2s来等待他Thread.sleep(3000);if(obj==null){System.out.println("对象已经死了");}else{System.out.println("对象还活着");}}
}
四)垃圾回收算法:

垃圾回收任何时候都可能,当系统觉得你内存不足了就会开始回收常见的比如分配对象内存不足时这里的内存不足有可能 不是占用真的很高,可能是内存足够,但是没有连续内存空间去放这个对象,当前堆内存占用超过阈值时,手动 调用 System.gc() 建议开始GC时,系统整体内存不足时等

4.1)标记清除算法:

标记是非垃圾的对象就是可达的对象,然后清除的是垃圾对象,要先递归进行遍历所有可达对象,然后清除的时候需要再开始遍历一遍整个内存空间,还需要进行维护空闲列表

就比如说我们的硬盘,只要你不小心点击了格式化,此时也不是真正的进行格式化,只是标记性删除,但是千万不要再向里面存放数据,因为数据会覆盖,就不好恢复了

当堆中有效的空间被耗尽的时候就会停止整个程序进行STW,首先进行标记,然后进行清除

1)标记:从引用根节点进行标记,标记所有可达的对象,也就不是垃圾的对象,一般是在对象的header头里面标记成可达的对象;

2)清除:对整个堆内存进行从头到尾的进行线性的遍历,如果发现某一个对象在header中没有被标记,那么直接将其回收

缺点:效率不算太高,在进行GC的时候需要终止整个应用程序,用户体验差,还有就是这种方式进行清理出来的空闲内存不是连续的,而是会产生内存碎片,还需要维护一个空闲列表

注意这里面的清空并不是真正的清空,而是需要把消除的对象的地址保存在一个空闲列表里面,下次有新的对象需要加载的时候,要判断垃圾的位置空间是否足够,如果够就进行存放

4.2)标记整理算法:内存利用率贼低

首先经过可达性分析在A区找到可达的对象,一旦找到了可达的对象就不需要进行标记,直接将可达的对象进行复制算法放到另一块区域B,另一块空间的所有区域B的对象都是连续的

将活着的内存空间分为两块,每一次只是用其中一块,再进行垃圾回收的时候将正在使用的内存中的存活对象复制到还没有被使用到的内存快里面,然后最后清楚正在使用到的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收

1)因为在新生代,对于常规应用的垃圾回收,一般情况下是可以回收很多的内存空间的,回收性价比就比较高

2)没有标记和清除过程,实现简单,运行高效

3)复制过去以后保证空间的连续性,不会出现内存碎片问题

缺点:维护引用和对象的地址映射

1)需要两倍的内存空间

2)回收的对象比较少,剩余存活的对象比较多,那么移动的对象比较多,但是还要大量维护指针和对象的关系,老年代不适合使用复制算法,因为很多对象都不死,老年代复制对象开销太大

3)对于G1这种拆分成大量的Regin的GC,复制而不是移动,意味着GC需要维护Regin之间的对象引用关系,不管是内存占用还是空间开销也不少,如果系统中的垃圾对象很多,复制算法需要复制的存活的数量不算太大,或者说非常低才可以

4.3)标记整理算法:

从根节点标记所有被根节点引用的对象,将所有的存活对象压缩到内存的一端,按照顺序进行存放,最后清除所有边界以外的空间,还要移动位置,还要修改引用对象关系很麻烦,这个算法比标记清除算法效率还低

标记压缩算法的最终效果就是等同于标记清除算法执行完成以后,再来进行一次碎片整理,二者的本质差异就是标记清除算法是一种非移动式的回收算法,标记压缩算法是非移动式的,是否移动回收后的存活对象是一项优缺点并存的风险策略,还可以看到标记的存活对象会被清理,需要按照内存地址进行依次排列,而没有被标记的内存会被回收掉清理掉,如此一来JVM在进行分配内存空间的时候,JVM只是需要维护一个内存的起始地址就可以了,这笔维护一个空闲列表节省了很多开销

优点:消除了标记清楚算法中的的内存区域分散的特点,我们需要给新对象分配内存的时候,JVM只是需要持有一个内存的起始地址即可,消除了复制算法中内存减半的高额代价

缺点:从效率上来说,标记整理算法要低于复制算法,移动对象的时候如果对象被其他引用所指向,还需要调整引用的地址,移动过程中,需要全程暂停用户应用程序;

4.4)分代回收:

1)在前面的这些算法中,没有一个算法可以完全代替其他算法,它们都具有自己独特的优势和特点,分代收集算法应运而生,分代收集算法是基于这样一个事实,不同对象的生命周期是不一样的,因此不同生命周期的对象可以采取不同的收集方式,一边用力啊提升回收效率,一般是吧JAVA堆分成新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,来提升垃圾回收的效率;

2)在JAVA程序运行的过程中会产生大量的对象,其中有一些对象是和业务信息相关,比如说Http请求的session对象,线程,Socket连接,这类对象和业务直接挂钩,因此生命周期比较端,比如说String对象,由于不可变的特性,系统会产生大量的这些对象,甚至有的对象只使用一次就被回收,新生代:老年代=1:2,edin区:幸存者1区:幸存者2区;

3)目前几乎所有的GC都是采用粉黛收集算法来执行垃圾回收的,在HotSpot虚拟机中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点

年轻代:

区域相比于老年代较小,对象的生命周期比较短,存活率低,回收比较频繁,这种情况复制算法的回收整理,速度是最快的,渎职算法的效率值和当前存活对象有关,因此很是适合年轻代的回收,而复制算法解决的是内存利用率不高的问题,通过两个幸存者区得到缓解

老年代:

区域比较大,对象的生命周期比较长,存活率高,回收不及年轻代频繁,这种情况存在大量存活度高的对象,复制算法明显是非常不合适的,一般是由标记整理或者是标记清楚算法的混合实现,标记阶段的开销和存活对象的数量成正比,清除阶段的开销和所管理区域的大小成正比,整理阶段的开销和存活对象的数据成正比;

标记的开销和存活的对象成正比,因为标记只能标记存活的对象

清除阶段要进行全堆空间线性的遍历,压缩或者是整理和存活对象的大小成正比

以HotSpot中的CMS垃圾回收器为例,CMS是基于标记压缩清除来实现的,对于对象的回收效率很高,但是对于碎片问题,CMS会使用基于标记压缩算法的Serial Old回收器作为补偿机制,当内存回收不佳的时候,将采用Serial Old执行Full GC来达到对于老年代内存的管理

对于STW的理解:

先确定GCROOTS,枚举根节点,此时要进行Stop The World,确保数据的一致性

stop the world停止的是用户线程,就是为保证一致性

可达性分析算法中枚举根节点会导致所有Java执行线程停顿

衡量一个垃圾回收器的标准就是吞吐量和低延迟

4.5)增量收集算法:

用户暂停时间/垃圾回收时间

吞吐量:工作线程一共的执行业务的时间

比如说我现在有一个房子,我一直不进行清理,一直制造垃圾,直到三个月之后才清理一次,此时清理的时间就比较长,阻隔用户线程的时间就比较长,但是如果说隔一会清理一会效果就会比较好,用户线程和回收线程协调交替执行,看样子就是并发的执行从而到达一种低延迟的行为,就是为了让用户感觉好一点;

被STW中断的应用程序线程会在完成GC之后恢复,频繁的中断会让用户感觉像是网速不快造成的电影卡顿一样,CMS自称低延迟,开发中不要显示的进行GC,导致STW

就是类似于洗衣服,假设现在我只是做两件事情:在宿舍给别人讲题和在卫生间洗衣服

工作线程:在宿舍给别人讲题

GC线程:在卫生间洗衣服

吞吐量:给宿舍讲题时间越长,吞吐量越高

停顿时间:在卫生间洗衣服越长,STW时间就越长

如果想要达到极高的吞吐量,那么就少去卫生间洗衣服,一次洗的多一点,这样吞吐量就特别高

停顿时间:多去洗衣服,每一次一会就回来,这样会使停顿时间最短,不让舍友等待时间过长,但是存在着从宿舍去卫生间和从卫生间回到宿舍时间的开销,会降低讲题总时间

4.5)分区算法降低停顿时间,主要是保证低延迟而不是吞吐量

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文详解汽车电CAN总线

1.什么是CAN总线 CAN总线(控制器区域网络)是一个中央网络系统,连接不同的电子控制单元(ECU),车辆中的其他设备。现代汽车可以有100个ECU,因此CAN总线通信变得非常重要。 2.CAN总线流行的背景 集中式:CAN总线系统允许对连接到网络的ECU进行集…

前端移动web高级详细解析一

01-平面转换 简介 作用:为元素添加动态效果,一般与过渡配合使用 概念:改变盒子在平面内的形态(位移、旋转、缩放、倾斜) 平面转换也叫 2D 转换,属性是 transform 平移 transform: translate(X轴移动距…

Android开发知识学习——编码、加密、Hash、序列化和字符集

文章目录 学习资源来自:扔物线加密古代密码学现代密码学对称加密非对称加密密码学密钥和登录密码Base64URL 使用的百分号编码压缩与解压缩图片与音频、视频编解码 序列化Hash字符集课后题 学习资源来自:扔物线 加密 古代密码学 起源:古代战…

C/C++面试常见问题——const关键字的作用和用法

首先我们需要一下const关键字的定义,const名叫常量限定符,当const修饰变量时,就是在告诉编译器该变量只可访问不可修改,而编译器对于被const修饰的变量有一个优化,编译器不会专门为其开辟空间,而是将变量名…

Win10中Pro/E鼠标滚轮不能缩放该怎么办?

Pro/E安装好后,鼠标滚轮不能缩放模型,该怎么办?问题多发生在win8/win10上,新装了PROE,发现滑动鼠标中键不能放大缩小。 彩虹图纸管理软件_图纸管理系统_图纸文档管理软件系统_彩虹EDM【官网】彩虹EDM图纸管理软件系统…

Windows下安装Anaconda、Pycharm以及iflycode插件图解

目录 一、下载Anaconda、Pycharm以及iflycode插件 二、创建相关文件夹 三、Pycharm社区版安装详细步骤 四、Anaconda安装详细步骤 五、配置Pycharm 六、安装iflycode插件 Anaconda是一款集成的Python环境,anaconda可以看做Python的一个集成安装,安…

WebGL笔记:矩阵的变换之平移的实现

矩阵的变换 变换 变换有三种状态:平移、旋转、缩放。当我们变换一个图形时,实际上就是在移动这个图形的所有顶点。解释 webgl 要绘图的话,它是先定顶点的,就比如说我要画个三角形,那它会先把这三角形的三个顶点定出来…

云端代码编辑器Atheos

什么是 Atheos ? Atheos是一个基于 Web 的 IDE 框架,占用空间小且要求最低,构建于 Codiad 之上,不过 Atheos 已从原始 Codiad 项目完全重写,以利用更现代的工具、更简洁的代码和更广泛的功能。 注意事项 群晖内核版本太…

【计算机毕设小程序案例】基于微信小程序的图书馆座位预定系统

前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 👉IT源码社-SpringBoot优质案例推荐👈 👉IT源码社-小程序优质案例…

理解V3中的proxy和reflect

现有如下面试题 结合GeexCode和Gpt // 这个函数名为onWatch,接受三个参数obj、setBind和getlogger。 // obj是需要进行监视的对象。 // setBind是一个回调函数,用于在设置属性时进行绑定操作。 // getlogger是一个回调函数,用于在获取属性时…

项目管理工具ConceptDraw PROJECT mac中文版自定义列功能

ConceptDraw PROJECT Mac是一款专业的项目管理工具,适用于MacOS平台。它提供了成功规划和执行项目所需的完整功能,包括任务和资源管理、报告和变更控制。 这款软件可以与ConceptDraw office集成,利用思维导图和数据可视化的强大功能来改进项目…

【Linux】操作系统以及虚拟机的安装与配置

🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Linux的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一.操作系统的介绍 二.VMWare虚拟机的安装…

安装虚拟机(VMware)保姆级教程及配置虚拟网络编辑器和安装WindowsServer以及宿主机访问虚拟机和配置服务器环境

目录 一、操作系统 1.1.什么是操作系统 1.2.常见操作系统 1.3.个人版本和服务器版本的区别 1.4.Linux的各个版本 二、VMware Wworkstation Pro虚拟机的安装 1.下载与安装 注意:VMWare虚拟网卡 2.配置虚拟网络编辑器 三、安装配置 WindowsServer 1.创建虚拟…

底层全部重构,小米澎湃OS完整系统架构公布

上周,雷军发文称小米全新操作系统澎湃 OS 正式版已完成封包,将逐步接替 MIUI。而后,又有网友曝光小米澎湃 OS 界面。 今日,雷军再度发表长文预热小米澎湃 OS,正式公布了完整系统架构。 据介绍,从架构设计之…

【咕咕送书 | 第四期】《ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践》

🎬 鸽芷咕:个人主页 🔥 个人专栏:《粉丝福利》 《C语言进阶篇》 ⛺️生活的理想,就是为了理想的生活! 文章目录 ⛳️ 写在前面参与规则一、前言1.0 人工智能新技术如何创新工作 ? 二、内容简介三、作者简介四、专家推…

请解释一下React中的条件渲染(conditional rendering)。

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Linux 系统调用IO口,利用光标偏移实现文件复制

用系统调用IO函数实现从一个文件读取最后2KB数据并复制到另一个文件中,源文件以只读方式打开,目标文件以只写的方式打开,若目标文件不存在,可以创建并设置初始值为0664,写出相应代码,要对出错情况有一定的处…

【C++项目】高并发内存池第六讲 当申请内存大于256K时的处理

目录 1.申请过程2.释放过程 1.申请过程 当申请的内存大于256kb时直接向堆中申请: static void* ConcurrentAlloc(size_t size) {if (size > MAX_BYTES){size_t alignSize SizeClass::RoundUp(size);size_t kpage alignSize >> PAGE_SHIFT;PageCache::…

Python爬虫核心模块urllib的学习

​ 因为在玩Python challenge的时候,有用过这个模块,而且学习这个模块之后也对系统学习网络爬虫有用。 ​ 当时查了各种资料学习,没有碰官网文档(因为还是对英语有抗拒性),但是还是官方的文档最具权威和学…

go创建完美的枚举类型

文章目录 一.前言二. 枚举基本要素描述三. 枚举设计源码3.1 EnumCommon-通用能力3.2 Enum_news 业务枚举3.3 定制化业务枚举 一.前言 用惯了springboot和Jakarta.才发现springboot和Jakarta的语言是多么精妙! 一些场景我们需要使用枚举: 如建立字典值映射,仅通过代码实现方便快…