JVM——垃圾回收(垃圾回收算法+分代垃圾回收+垃圾回收器)

1.如何判断对象可以回收

1.1引用计数法

只要一个对象被其他对象所引用,就要让该对象的技术加1,某个对象不再引用其,则让它计数减1。当计数变为0时就可以作为垃圾被回收。

有一个弊端叫做循环引用,两个的引用计数都是1,导致不能作为垃圾回收,会造成内存泄露。

java虚拟机没有采用该算法。

1.2可达性分析算法

该算法需要先确定根对象,根对象的定义就是那些肯定不能当成垃圾被回收的对象。

在垃圾回收之前会先对堆中的所有对象进行扫描,看每一个对象是否被根对象直接或者间接的引用。是的话则该对象不能被回收,否则的话该对象可以被作为垃圾将来被回收。

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
  • 哪些对象可以作为 GC Root ?

使用eclipse提供的一个工具Memory Analyzer(MAT)

一个快速且多功能的java堆分析工具。

MAT MemoryAnalyzer中文使用指南_mat 汉化_Louis.No1的博客-CSDN博客

 案例

 先使用jps查看进程id,然后用jmap抓取进程快照,但是要使用MAT还要将快照转换成文件,使用下面命令

jmap -dump:format=b,live,file=1.bin 21384

format是指定转储文件的格式,b是二进制格式,live是抓取快照时只抓取存活类型,不管被回收的,并且live参数会在抓取前进行一次垃圾回收。file参数决定要把内存快照存为哪个文件。最后跟上进程id

 在代码中抓取了两次,一次在置集合为空之前,一次在置集合为空之后。

置为空之前

有系统类

诸如Object,HashMap,String,等虚拟机运行过程中核心的类对象都可以作为GD root对象

 第二类是java虚拟机在执行时调用的操作系统方法,操作系统执行时应用的java对象也是可以作为根对象。

 第四类是Busy Monitor

synchronized锁住的对象,不会被当做垃圾

 第三类就是活动线程中使用的一些对象

线程运行时产生的栈帧说产生的东西可以作为根对象

 

这里用到的局部变量所引用的对象都可以作为根对象。

引用变量和对象是两个东西 list1是一个引用,后面引用的对象是存在堆里面的,根对象也是指后面堆中的对象。而不是list1这个局部变量引用。

下图中的ArrayList 就是当前活动线程执行过程中局部变量所引用的对象可以作为根对象。

以及下面的那个方法参数引用的字符串数组对象String[0]也是根对象。

 

 虚拟机栈中引用对象,方法区中类静态属性引用对象,方法区常量引用对象,本地方法栈引用对象

置为空之后

再次查找已经没有ArrayList了,因为list1置为空,ArrayList不再被引用,已经被回收了。

1.3四种引用

常见的应该有5种引用。

 图中的实线箭头表示强应用,虚线这是软弱虚终结器引用。

没有使用xxxReference这个类包一层创建的实例对象,都是强引用类型的对象,因为后面会看到,非强引用对象的创建,是带有xxxReference类先包一层再创建的

我们平时学习的就是四种的第一级的强引用,说白了,就Object o = new Object(),四个引用分别是强引用、软引用、弱引用和虚引用,请记住它们的顺序!因为这是它们对内存的敏感程度!

等级: 强 > 软 > 弱 > 虚

1.强引用

Object o = new Object() new一个对象通过=复制给一个变量,则变量强引用了该对象。

特点:

沿着GCRoot的引用链可以去找到它就不会被垃圾回收。

在上面图中沿着C对象实线可以找到A1对象,则A1对象无法被回收。

当如下图所示没有GCRoot直接或者间接应用了A1时,A1才能被回收。

2.软引用

3.弱引用

 

 上图的A2,A3两个对象,只要没有被直接的强引用所引用,当垃圾回收发生时都可能被回收。

上面图中A2,A3就是间接的被C对象间接的引用了,通过一个软引用对象和一个弱引用对象,这是间接的途径,并且又被B对象强引用了,这时不会被回收。

 当B对象不再引用A2,A3时,A2,A3就可以被回收了。

A2回收时刻:当垃圾回收发生后,内存依旧不足时回收

A3回收时刻:  当垃圾回收发生时,不管内存充足与否,都会把弱引用的对象回收。

引用队列

软弱引用还可以配合一种叫做引用队列的一起工作。

当软引用的对象被回收后,软引用自身也是一个对象。如果创建时给它分配了一个引用队列,那么现在软引用会进入该队列。弱引用也是一样,会进入弱引用的引用队列。

 原因: 软引用和弱引用自身都要占用一定的内存,如果要对它们的内存做进一步释放,需要使用应用队列将它们释放。

4.虚引用

与软弱引用不同,虚终应用必须配合引用队列使用。它们创建时就会关联一个引用队列。

工作:  在直接内存部分创建一个ByteBuffer实现类对象时就会创建一个名为Cleaner的虚引用对象。

        ByteBuffer分配到一片直接内存之后会将直接内存地址传递给虚应用对象。

当ByteBuffer不再被强引用所引用后会被垃圾回收。

 但是直接内存不归垃圾回收管,虚引用对象进入引用队列后,会定时由一个线程检查是否有虚引用入队,有的话会调用Cleaner中的freeMeory方法将直接内存释放。

5.终结器引用

所有的java对象都会继承自Object父类,都会有一个finallize()终结方法。

 当这个A4对象重写了终结方法,并且没有被强引用所引用时就可以被当成垃圾回收。

在A4被回收前会先将终结器引用对象放入引用队列,再由一个优先级很低的线程在某些时刻过来检查并找到要作为垃圾回收的A4对象并且调用A4的finallize方法,调用完后的下一次垃圾回收就会回收掉A4.

由于线程优先级很低会导致A4对象迟迟无法被释放。不推荐使用finallize释放资源。

软引用——应用

 上面代码是一个listh集合,然后不断往里面添加byte数组,用-Xmx20m设置了堆内存大小20m.

报错如下。

 在业务场景中那4mb资源可能不是核心资源,当其过多时使用强引用来引用会到值内存溢出。

对于不重要的资源需要在内存紧张时将其占用资源释放,以后再用时再读取。

这里就要用到软引用。

在上面的这段代码中list不再直接引用byte数组,而是在中间加了个软引用对象。

现在list对SoftReference是强引用, SoftReference对byte是软引用。

这次就不会出现堆内存溢出。

 

 在循环时调用软引用的get去取时还会有,等到循环结束后就已经取不到了 ,前四个元素都变成了null,只剩最后一个。

这里通过打印垃圾回收的详细参数演示。

-XX:+PrinyGCDetails   -verbose:gc

 

在第三次循环时就已经出现内存不足发生了一次minor GC回收,回收了新生代,第四次之后又来了一次 minor GC,但是发生效率不高,4696k->4696k,于是来了一次Full GC,把老年代也回收了。

但是发现效率还是低,于是触发软引用的垃圾回收。

然后新生代4549k->0k,老年代12500k->639k.这里把前四个软引用的byte数组都回收了。 

软引用——引用队列

在上面最后一次循环,前四个都是null了,已经没有保留的必要。

所以这里要把软引用本身也清理掉,配合引用队列使用。

这里的poll!=null是指这个软引用本身不为空,而不是引用的值不为空。

只有byte数组被回收的软引用才会进这个队列,所以只有4个需要被回收的软引用在这里。

 最后再次循环就只剩一个在list里面了。

弱引用——应用

 

 弱引用的使用和软引用类似。这里弱引用的byte对象在每次GC是都会被回收。

导致最后只剩四个byte数组。

加大循环次数,我们可以得知前三个对象没有被垃圾回收掉是因为晋升到了老年代。

加到6个对象时,第四个和第五个因为是新生代所以被回收了。

 增大到10个时,最后一次直接回收了前面9个byte数组,因为弱引用本身也有内存,导致放不下引发了一次fullGC,去回收了老年代的byte数组。

因为年轻代已经放不下其他对象了,后续对象都是放到老年代。???

这里有点乱,后续再来看。

2.垃圾回收算法

2.1标记清除

 第一个阶段是标记没有被根对象引用的对象。

第二阶段要将垃圾所占的空间释放,这里是将垃圾的起始结束地址放入一个空闲地址列表里面,下次分配新对象时会到空闲列表中找有没有一块足够的空间容纳新对象。这里和OS的内存管理很像。

优点:

速度快

缺点:

容易产生空间不连续的内存碎片。 

2.2标记整理

标记过程和上面一样,但是第二个阶段会使用紧凑技术整理。

优点:

没有碎片

缺点:

移动过程效率低 

2.3复制

将内存区划分成大小相同的两块区域,其中TO始终空闲。

 第一个阶段也是标记,第二阶段如下图所示,将存活对象移动到TO区域并清理from区域

 第三阶段交换from和to区域,是To总是保持空闲。

优点:

不会产生碎片

缺点:

会占用双倍内存空间。

3.分代垃圾回收

java虚拟机不会采用单独的一种算法,而是三种协同工作,具体实现就是分代的垃圾回收机制。

划分如下,有新生代和老年代。

老年代放的是长时间使用的对象。老年代的垃圾可以等到内存不足时调用fullGC清理。

新生代放的是用完就可以丢弃的对象。

永久代就是jvm进程在运行中永远不会删除的内存区域,也就是方法区。

针对生命周期不同采用不同的策略,老年代的垃圾回收很久才有一次,新生代的GC较为频繁。

分代垃圾回收机制工作

 ​​​​​​​

 新创建的对象会被分配到伊甸园。

当伊甸园放不下后会出发一次垃圾回收叫做MinorGC,用可达性分析算法去标记。

标记完后会把存活对象放到幸存区TO中,并且让其寿命+1。伊甸园剩下的都会回收。

然后幸存区from和to就会交换位置。 

 

然后放着放着伊甸园又满了,这时出发第二次垃圾回收。同时会去幸存区From找是否有需要回收的。

 

然后把伊甸园和From中存活的对象放入TO中再次加1,然后调换From和To.并把新对象放入伊甸园。 

当幸存区的对象寿命超过预值后会晋升到老年代当中。

 当出现下面的情况,一个新对象在新生代和From和老年代都放不下的时候会触发一次full GC.

fullGC会出发新生代和老年代的清理。

minor gc引发的 stop the world会把其他用户的线程都暂停,因为垃圾回收时会发生对象地址的改变,其他线程在根据原来的地址是找不到的。

新生代触发的STW时间较短。

老年代触发的STW时间更长。

 ​​​​​​​​​​​​​

  •  如果full gc之后还是空间不足就会报内存溢出错误。

 Minor GC 与 Full GC 的触发条件

Minor GC 

 Eden 区没有足够的空间分配给新创建的对象.

Full GC

  • 老年代空间不足,这个很简单,就是字面上的不足,例如:大对象不停的直接进入老年代,最终造成空间不足。
  • 方法区空间不足。
  • Minor GC 引发 Full GC

年轻代的对象在经历Minor GC 过后,部分对象存活对象或全部存活对象会进入老年代。

3.1相关VM参数

幸存区比例默认是8,有10mb时,8mb是伊甸园,剩下的2mb是幸存区。

 分析

 参数中设置垃圾回收器为SerialGC,虚拟存储器比例不会动态调整。

运行后

new generation新生代  tenured generation老年代  Metaspace老年代

可以看见新生代里面有eden from to区域按照8:1:1

按照上面代码,往新生代放入7mb的byte数组,但是eden只有8mb,并且已经用了28%,则一定会触发垃圾回收。下面只显示GC表示是新生代的GC,Full GC则是老年代的GC.

如下所示,因为触发了垃圾回收,from区已经有东西了。

 

 在7mb的基础上再一次放两个512kb,放一次没满,放两次就溢出了,所以又触发了一次垃圾回收。​​​​​​​

并且这里很多对象晋升到了老年代。因为内存紧张所以没到15次就让一部分对象及晋升到老年代,这里直接把7mb的放入了老年代。

 大对象_oom

这里直接一个大对象超过了新生代的容量,不会触发垃圾回收,会直接进入老年代。

再放一个8mb的大对象,这次没有地方能放得下了,就会直接内存溢出。

再内存溢出前还会尝试进行垃圾回收 ,先是minor GC不行,然后Full GC也不行就直接报错了。

误区:

在一个线程中内存溢出了并不会导致整个线程结束。

当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,但是这里释放不了,老年代里的8mb字节对象被list引用着。

子线程里放入大对象时,发现继续放入会造成内存泄漏,所以最后这个8mb对象就没有放入,但还是会给程序反馈报异常。主线程依然可以使用堆内存。

4.垃圾回收器

后两种需要多核CPU才能充分发挥性能。区别在,响应时间优先是让每次时间最短,发生5次,每次0.1,。吞吐量是单位时间内最短,一个小时发生两次,每次0.2,最终单位时间内0.4秒,少于0.5,单位时间内垃圾回收时间占比越低,吞吐量占比越高。

4.1串行

 Serial运行在新生代,使用复制算法,SerialOld生活在老年代,使用标记+整理算法。

如上图所示多核CPU在运行时发生内存不足了,要让所有线程在一个安全点停下。

因为Serial和SerialOld都是单线程的垃圾回收器,所以只有一个线程在进行垃圾回收。

4.2吞吐量优先

在jdk1.8中,默认使用的就是ParallelGC,并行的垃圾回收器,后面那个加了个Old是老年代用的,回收算法上和串行的使用一样。开启其中一个另一个也会自动开启。

这里会开启多个垃圾回收线程进行垃圾回收,默认与CPU核数相关。

每次发生垃圾回收时CPU占用率如下所示,因为是动用了所有核去进行垃圾回收。

 这里垃圾回收线程数可以通过参数设置修改

 相关参数

采用自适应的大小调整策略,调整新生代中的大小,伊甸园和幸存区的比例,晋升预值等。

可以根据设定目标尝试调整堆的大小来达到期望目标。

1是吊证吞吐量的目标,垃圾回收时间和总时间的占比。公式:1/(1+ratio).ratio默认值是99,有

       1/(100)= 0.01,则说的是垃圾回收时间不能超过总时间的百分之一。超过的话一般会把堆的大小增大,使垃圾回收发生次数减少,使的总时间下降。

2是最大暂停毫秒数,默认是200mx,2和1冲突,1会增大垃圾回收的总时间,使其可能超过200ms。而且2会将堆的大小减小,防止因为堆过大导致垃圾回收时间过长。

 ​​​​​​​

4.3 响应时间优先(CMS)

 

 要开启该垃圾回收器,虚拟机参数为UseConcMarkSweepGC

Conc=Concurrent并发 Mark标记 Sweep清除。

基于标记清除算法的并发垃圾回收器。垃圾回收器在工作的同时其他用户线程也能进行,在垃圾回收的部分阶段不需要Stop the World. 这是工作在老年代的垃圾回收器。

与其对应的是ParNewGC,工作在新生代的垃圾回收器。

有时CMS并发失败的时候会切换到SerialOld垃圾回收器。

 如上图所示,运行过程中老年代发生内存不足,CMS开始执行初始标记(标记根对象,暂停时间短)的工作,需要stop the world并会阻塞用户线程。

并发标记阶段用户线程恢复运行,并且CMS可以把剩余的垃圾找出来.

重新标记阶段需要stop the world,因为并发标记阶段用户线程可能会产生新对象,改变对象的引用,对垃圾回收有干扰。

最后并发清理阶段就又可以并发运行了。

细节:

CMS受到两个参数影响,一个是并行的垃圾回收线程数,第二个是并发的垃圾线程数,一般要设置为并行线程总数的四分之一。并行是4,并发是1时,有4个CPU会工作,有一个用来执行垃圾回收线程。

 CMS对CPU的占用没有上面的高,因为这里只有四分之一的CPU用去垃圾回收,但用户工作线程只能占用原本3/4的线程数量,对应用程序吞吐量有影响。

 在其他用户线程运行的时候其他用户线程可能会产生新的垃圾,称为"浮动垃圾",这些要等到下一次才能回收。要预留空间保存垃圾,上面的参数就是用于控制什么时候执行CMS垃圾回收。

等于80时,只要老年代的内存占用达到80%时就进行垃圾回收。

用于在重新标记阶段的特殊场景:

        新生代的对象引用老年代的对象,在重新标记阶段做可达性分析时对性能影响很大。

原因:

        新生代的对象创建个数较多,并且很多都是要作为垃圾的,就算去找老年代也是要被回收,多做了无用的查找功。

用上面的参数在重新标记前对新生代用UseParNewGC做垃圾回收,可以减少存活对象,减轻重新标记阶段的压力。

特点:

CMS采用标记清除算法,可能产生较多的内存碎片。导致将来分配对象时新生代内存不足,结果老年代也不足,造成并发失败。使得垃圾回收器退化为SerialOld,做一次整理,使得碎片少了才能继续工作。

一旦有垃圾回收失败的问题,垃圾回收时间也会大幅上升。

未完待续........

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

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

相关文章

STM32 printf函数

printf函数输出流程 用户调用printf()函数到C标准库调用printf函数相关部分,printf函数由编译器提供的stdio.h解析。包含在usart.h文件中。fputc()最终实现输出。用户需要根据最终输出的硬件重新定义该函数,此过程为:printf重定向。 printf的…

测试框架pytest教程(8)失败重试-pytest-rerunfailures

pytest-rerunfailures是一个pytest插件,用于重新运行失败的测试用例。当测试用例在第一次运行时失败,该插件会自动重新运行指定次数的失败用例,以提高稳定性和减少偶发性错误的影响。 要使用pytest-rerunfailures插件,需要按照以…

MySQL双主架构、主从架构

为什么要对数据库做优化? MySQL官方说法: 单表2000万数据就达到瓶颈了。所以为了保证查询效率,要让每张表的大小得到控制。 MySQL主主架构 主数据库都负责增删改查。 比如有1000W的数据,有两个主数据库,就将数据分流给…

学习网络编程No.4【socket编程实战】

引言 北京时间:2023/8/19/23:01,耍了好几天,主要归咎于《我欲封天》这本小说,听了几个晚上之后逐渐入门,在闲暇时间又看了一下,小高潮直接来临,最终在三个昼夜下追完了,哈哈哈&…

在 Pytorch 中使用 TensorBoard

机器学习的训练过程中会产生各类数据,包括 “标量scalar”、“图像image”、“统计图diagram”、“视频video”、“音频audio”、“文本text”、“嵌入Embedding” 等等。为了更好地追踪和分析这些数据,许多可视化工具应运而生,比如之前介绍的…

使用 ChatGPT 创建 PowerPoint 演示文稿

让 ChatGPT 成为您的助手来帮助您编写电子邮件很简单,因为众所周知,它非常能够生成文本。很明显,ChatGPT 无法帮助您做饭。但您可能想知道它是否可以生成文本以外的其他内容。在上一篇文章中,您了解到 ChatGPT 只能通过中间语言为您生成图形。在这篇文章中,您将了解使用中…

Ubuntu 配置国内源

配置国内源 因为众所周知的原因,国外的很多网站在国内是访问不了或者访问极慢的,这其中就包括了Ubuntu的官方源。 所以,想要流畅的使用apt安装应用,就需要配置国内源的镜像。 市面上Ubuntu的国内镜像源非常多,比较有…

PyTorch训练简单的生成对抗网络GAN

文章目录 原理代码结果参考 原理 同时训练两个网络:辨别器Discriminator 和 生成器Generator Generator是 造假者,用来生成假数据。 Discriminator 是警察,尽可能的分辨出来哪些是造假的,哪些是真实的数据。 目的:使…

小区新冠疫情管理系统的设计与实现/基于springboot的小区疫情管理系统

摘要 采用更加便于维护和使用的Java语言,其可拓展性高且更富于表现力,基于mysql数据库、Springboot框架开发的小区新冠疫情管理系统,方便用户查看物资信息、疫苗信息。通过Eclipse来进行网页编程,其方便易用、移植适用性广、更加安…

日志搞不定?手把手教你如何使用Log4j2

系列文章目录 从零开始,手把手教你搭建Spring Boot后台工程并说明 Spring框架与SpringBoot的关联与区别 SpringBean生成流程详解 —— 由浅入深(附超精细流程图) Spring监听器用法与原理详解 Spring事务畅谈 —— 由浅入深彻底弄懂 Transactional注解 面试热点详解…

装备制造企业如何执行精益管理?

导 读 ( 文/ 2358 ) 精益管理是一种以提高效率、降低成本和优化流程为目标的管理方法。装备制造行业具备人工参与度高,产成品价值高,质量要求高的特点。 在装备制造企业中实施精益管理可以帮助企业提高竞争力、提升生产效率并提供高质量的产品。本文将…

边缘计算节点BEC典型实践:如何快速上手PC-Farm服务器?

百度智能云边缘计算节点BEC(Baidu Edge Computing)基于运营商边缘节点和网络构建,一站式提供靠近终端用户的弹性计算资源。边缘计算节点在海外覆盖五大洲,在国内覆盖全国七大区、三大运营商。BEC通过就近计算和处理,大…

【算法日志】贪心算法刷题:单调递增数列,贪心算法总结(day32)

代码随想录刷题60Day 目录 前言 单调递增数列 贪心算法总结 前言 今天是贪心算法刷题的最后一天,今天本来是打算刷两道题,其中的一道hard题做了好久都没有做出来(主要思路错了)。然后再总结一下。 单调递增数列 int monotoneIncreasingDigits(int n…

Wlan——STA上线流程与802.11MAC帧讲解以及报文转发路径

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 不同802.11帧的转发路径 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标…

Linux内核学习(八)—— 内存管理(基于Linux 2.6内核)

目录 一、页(page) 二、区(zone) 三、页操作 四、kmalloc() 五、vmalloc() 六、slab 分配器 七、在栈上的静态分配 一、页(page) 内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻 …

网工内推 | 锐捷招云工程师,HCIE、CCIE、RHCE优先,25k*13薪

01 锐捷网络 招聘岗位:云方案工程师 职责描述: 1、负责云数据中心方案项目方案设计撰写、项目实施交付、故障处理、业务割接、客户培训、现场保障、网络优化、网络巡检等技术相关业务 2、负责云数据中心方案新技术文档沉淀、体系建设、工具开发等标准化…

使用element-plus组件,默认显示英文 转换为中文

最近在边写项目边学习vue3 所以这几天没有更新 找机会把vue3的知识也统计一下吧 先说今天遇到的问题 最近做项目的时候使用element-plus分页组件时发现&#xff0c;显示的不是中文的了&#xff0c;是英文的 解决方法 在app.vue里面配置 <template><el-config-provi…

Pyqt5打开电脑摄像头进行拍照

目录 1、设计UI界面 2、设计逻辑代码&#xff0c;建立连接显示窗口 3、结果 1、设计UI界面 将ui界面转为py文件后获得的逻辑代码为&#xff1a;&#xff08;文件名为 Camera.py&#xff09; # -*- coding: utf-8 -*-# Form implementation generated from reading ui file …

【算法专题突破】双指针 - 移动零(1)

目录 写在前面 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 写在前面 在进行了剑指Offer和LeetCode hot100的毒打之后&#xff0c; 我决心系统地学习一些经典算法&#xff0c;增强我的综合算法能力。 1. 题目解析 题目链接&#xff1a;283. 移动零 - 力…

centos7.9和redhat6.9 离线升级OpenSSH和openssl (2023年的版本)

升级注意事项&#xff01; 1、多开几个连接窗口&#xff08;xshell&#xff09;&#xff0c;避免升级openssh失败无法再次连接终端&#xff0c;否则要跑机房了。 2、可开启telnet服务、vnc服务、打快照。多几个“保命”的路数。一、centos7.9的信息 [rootnode2 ~]# openssl v…