JVM进阶(3)

news2024/9/23 21:27:56
一)什么是垃圾?

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

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.coloradmin.cn/o/1143622.html

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

相关文章

前端开发---在vue项目中使用openLayers

前端开发之在vue项目中使用openLayers 前言效果图在vue中渲染地图安装ol插件1、调用插件2、 初始话地图3、地图点击事件4、重置坐标5、通过坐标改变视图6、保存坐标点 vue中使用的源码 前言 本篇文章主要讲解openLayers的初步使用,包括渲染地图、获取点坐标、标记点…

Data Analysis With Python

文章目录 Data Analysis With PythonAnalyzing Numerical Data with NumPyCreating NumPy ArrayNumPy Array SlicingNumPy Array BroadcastingAnalyzing Data Using Pandas In this article, we will discuss how to do data analysis with Python. We will discuss all sorts …

平衡二叉树AVL的插入删除

在AVL树的插入操作中,假设插入一个结点后,当前节点p的平衡因子是﹣2,其左子结点的平衡因子是+1,左子结点的右子结点的平衡因子是﹣1。如图所示,请给出票转调整之后的结构。

微机原理与接口技术-第八章常用接口技术

文章目录 定时控制接口8253/8254定时器定时器的应用 并行接口并行接口电路8255内部引脚工作方式工作方式0:基本输入输出方式工作方式1:选通输入输出方式 编程 并行接口的应用用8255方式0与打印机接口 数码管及其接口数码管的工作原理单个数码管的显示多个…

目录和文件操作

在自己电脑任一盘符中新建以OS_Test命名的文件夹,并在该文件夹中新建新建3个以.txt,3个 .xlsx为扩展名的文件(文件名由代码随机生成,长度为8,由字母数字组成)。,请写一个程序,删除掉…

Unity的unity_ObjectToWorld里的每一列分别代表什么意思?换个方向反向理解-更简单

官方关键UnityObjectToWorldNormal() 代码 从乐乐姐的书中得知,当我们在shader想获得法线,大概会这么些 o.wordDir UnityObjectToWorldNormal(i.normal) (这行代码就包含了官方对“unity_ObjectToWorld”的终极理解…

视频批量剪辑技巧:如何实现震撼的嵌套合并效果

随着视频制作需求的不断增长,视频批量剪辑技巧在提高制作效率和质量方面显得尤为重要。本文将介绍云炫AI智剪实现震撼嵌套合并效果的方法,帮助您在视频制作过程中更高效地完成任务。 视频批量剪辑技巧是一种利用计算机技术实现自动化视频剪辑的方法。通…

串口占用检测工具

串口占用检测工具 平时需要检测哪个程序占用了串口,下面介绍一款非常方便的工具,它的工具箱里包含一个串口占用检测工具,可以非常方便的检测出来哪个程序占用了串口,并给出程序名和PID。 官网下载地址:http://www.red…

2023枣庄麒瑞音乐嘉年华济南新闻发布会

2023枣庄麒瑞音乐嘉年华新闻发布会今日在济南市西元大厦隆重举行,演唱会主办方枣庄恒立城市发展投资有限公司副经理刘畅先生、枣庄麒瑞文化董事长孙振敏女士,演唱会冠名方滕州爱啤士精酿啤酒有限公司总经理周静女士等和国内及山东省近30家主流新闻媒体到…

命令模式——让程序舒畅执行

● 命令模式介绍 命令模式(Command Pattern),是行为型设计模式之一。命令模式相对于其他的设计模式来说并没有那么多条条框框,其实并不是一个很“规矩”的模式,不过,就是基于一点,命令模式相对于…

局域网内两台电脑共享文件夹(通过网线直连共享数据)

文章目录 2.设置共享文件夹3.访问共享文件夹 1.将两台电脑置于同一局域网下 用网线将两台电脑连接关闭两台电脑防火墙将两台电脑IP地址设置在同一局域网下 测试是否在同一局域网下,使用ping命令 ping 192.168.0.122.设置共享文件夹 选择想要共享的文件夹&#xff…

刷题学习记录

sql注入(bugkuctf) 打开显示一个登录框 照常用admin用户名登录,密码随便填一个,显示密码错误 接着用admin为用户名登录,密码照样随便填,结果显示用户名不存在 题目提示基于布尔的SQL盲注,猜测后端是判断用…

【torch高级】一种新型的概率学语言pyro(02/2)

前文链接:【torch高级】一种新型的概率学语言pyro(01/2) 七、Pyro 中的推理 7.1 背景:变分推理 引言中的每项计算(后验分布、边际似然和后验预测分布)都需要执行积分,而这通常是不可能的或计算…

静力触探数据智能预处理(4)

静力触探数据智能预处理(4) 前言 数据处理方式已由手工1.0、计算机辅助2.0向人工智能3.0的趋势发展。机器学习是人工智能的基础,本文尝试应用机器学习中K均值聚类算法对孔压静力触探数据进行土的分类,分类结果不理想&#xff0c…

buuctf_练[安洵杯 2019]easy_web

[安洵杯 2019]easy_web 文章目录 [安洵杯 2019]easy_web掌握知识解题思路代码分析正式解题 关键paylaod 掌握知识 url地址和源代码的信息捕捉;图片和base64之间转换;base64和十六进制编码的了解;代码审计,绕过正则匹配对关键字的…

简易但很实用的javaswing/gui音乐播放器

视频浏览地址 很实用的一个javaswing音乐播放器。可以展示歌名,上一曲下一曲。 源码下载地址 支持:远程部署/安装/调试、讲解、二次开发/修改/定制

Java八股文 ----Redis篇

问题大纲 缓存穿透 原因:入侵者大量查询不存在的数据 使得Redis不断去访问数据库 然而Redis也无法缓存,就导致每次都会查询数据库...数据库的并发度不高 就会宕机 解决办法 布隆过滤器:作用:拦截不存在的数据 布隆过滤器 原理:把数据的id通过多次哈希计算标记数组,新来个数…

Easex样式样式

eg1&#xff1a;线形样式和描边 #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #define PI 3.14 // 1PI 180度 2PI 360度int main() {initgraph(800, 600);setorigin(400, 300);setaspectratio(1, -1);/*void setl…

基于Ubuntu20.04安装ROS系统

文章目录 一、ROS简介二、ROS安装三、ROS安装测试四、安装问题解决1. sudo rosdepc init&#xff1a;找不到命令2. ERROR: cannot download default sources list from...3. Command roscore not found...4. Resource not found: roslaunch... 一、ROS简介 ROS是用于编写机器人…

行业追踪,2023-10-27

自动复盘 2023-10-27 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…