JVM知识总结(垃圾回收)

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

垃圾回收

GC类型

Minor GC(Young GC):

新生代收集( Minor GC/Young GC ):

  • 当年轻代空间不足时,就会触发Minor GC。

整个年轻代中又可以分为Eden区和survivor区,survivor区又分为survivor0和survivor1。

  • Eden区和Survivor0区和Survivor1区,之间的配比是8:1:1(默认情况下)

年轻代GC回收过程:

  • 当Eden区被对象塞满后就会发生GC。

  • 在eden区中筛选出来有用的对象,把有用的对象复制到survivor0/survivor1,第一次随机分配,假设分配到survivor0,剩下eden区的对象都是垃圾对象,直接干掉。

  • 程序继续执行,eden区的对象又放不下了,又触发Minor GC,这次它不仅会回收eden区,还会回收survivor0区的垃圾对象,把eden区和survivor0区的有用对象一起放到survivor1区,eden区和survivor0区剩余的垃圾对象直接干掉。

  • 程序继续执行,eden区的对象又放不下了,又会触发Minor GC,这次它不仅会回收eden区,还会回收survivor1区的垃圾对象,把eden区和survivor1区的引用对象一起放到survivor0区,eden区和survivor1区剩余的垃圾对象直接干掉。

  • 依次往复循环,直到超过了一定的次数,对象就会放到老年代区,老年代放不下就要执行Full GC了。

老年代收集(Major GC/Old GC):老年代的垃圾收集。

在老年代空间不足时,则触发Major GC。

对象什么情况会进入老年代?

大对象:

需要大量连续内存空间的对象,最典型的大对象就是那种很长的字符串以及数组。

长期存活的对象:

虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当他的年龄增加到一定程度(默认是15岁), 就将会被晋升到老年代中。

  • 对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

动态对象年龄判定:

如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

空间分配担保:

在一次安全Minor GC 中,存活的对象不能在另一个Survivor 完全容纳,则会通过担保机制进入老年代。

什么样的对象会被回收?

引用计数法:

给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就+1。

当引用失效时,计数器值就-1,任何时刻计数器为0的对象就是不可能在被使用。

无法解决对象之间互相引用的情况。

  • A到B,B到C,C到A,相互引用,但是他们垃圾的话,用引用计数是无法被回收的。

可达性分析算法:

通过GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的,这些对象就会被当作垃圾从而被回收掉的。

哪些是GCRoots对象的根:

虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象

方法区中的类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中(Native方法)引用的对象

触发Full GC执行的情况?

调用System.gc()时,系统建议执行Full GC,但是不必然执行。

老年代空间不足。

方法区空间不足。

通过Minor GC后进入老年代的平均大小 大于 老年代的可用内存。

  • Eden、Survivor Space0(From Space)区 向Survivor Space1(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存 小于 该对象大小。

Full GC问题排查

查看GC日志:

  • 通过jstat -gcutil -t pid 1000 1000查看GC日志。

查看堆内存情况:

  • jmap -heap pid查看堆内存情况。

查看哪些类占用的空间多:

  • 通过jmap -hsito pid查看哪些类占用的空间多。

查看堆内存日志:

  • 通过jmap -dump:format=b,file=xxxx.hprofDump查看堆内存日志。
  • 通过MAT内存分析工具分析日志。

空间分配担保机制

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间 是否大于 新生代所有对象的总空间。

  • 如果大于,则此次Minor GC是安全的。

  • 如果小于,则虚拟机会查看 -XX:HandlePromotionFailure 设置值是否允许担保失败。

如果HandlePromotionFailure=true,那么会继续 检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。

  • 如果大于,则尝试进行一次Minor GC。

  • 如果小于,则进行一次Full GC。

如果HandlePromotionFailure=false,则进行一次Full GC。

在这里插入图片描述

安全点

程序执行时并非在所有地方都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置称为安全点(Safepoint)。

当JVM触发GC时,首先会让所有的用户线程到达安全点SafePoint时阻塞,也就是STW,然后枚举根节点,即找到所有的GC Roots,然后就可以从这些GC Roots向下搜寻,可达的对象就保留,不可达的对象就回收。

三色标记算法

根据CMS的运行过程来看,它的低停顿是在并发标记并发收集阶段,做成并行,而没有STW,但是这样做会带来一个问题:

  • 由于用户程序的执行,对象的状态很容易发生变化,原本有GC Roots引用的对象,现在没有GC Roots引用了/原本是垃圾对象,后面又复活了,又不是垃圾对象了,引用状态的改变,对JVM来说是很不可控的一种行为。

为了解决这个问题,JVM引入了三色标记这个解决方案。

GCRoot如果想查找到存活对象,会根据可达分析算法分析,遍历整个引用链,按照是否访问过该对象分成三种不同的颜色:

  • 白色、灰色、黑色。

白色:

  • 对象没有被访问过 (没有被GCRoot扫描过,有可能是为垃圾对象)。

灰色:

  • 对象已经被访问过(被GCRoot扫描过),且对象中的属性没有被GCRoot扫描,该对象就是为灰色对象。
  • 如果该对象的属性被扫描的情况下,从灰色变为黑色。

黑色:

  • 对象已经被访问过(被GCRoot扫描过),且本对象中的属性已经被GCRoot扫描过,该对象就是为黑色对象。

三色标记算法原理:

在这里插入图片描述

在初始阶段的时候,所有的对象都是存放在白色容器中。

初始标记阶段,GCRoot标记直接关联对象置为灰色。

并发标记阶段,扫描整个引用链,有子节点的话,则当前节点变为黑色,子节点变为灰色。

在白色盒子剩下的对象都是为没有被GCRoot关联的对象,可能会被垃圾回收机制清理。

下次GCRoot起点从灰色节点开始计算。

三色标记算法缺陷:

在并发标记阶段的时候,因为用户线程与GC线程同时运行,有可能会产生多标或者漏标。

多标:

  • 在并发标记阶段,把一个GC Roots引用链上的对象已经标记了,但是用户线程没有停止,当方法结束的时候,这个对象链上可能都是垃圾对象,称为浮动垃圾。

漏标:

  • 在并发标记阶段,原先已经被扫描过的对象重新有了新的引用,导致无法被扫描。

CMS解决漏标问题:增量更新方式:

当黑色对象插入新的指向白色对象的引用关系的时候,就将这个新插入的引用记录下来,等并发标记结束之后,等到重新标记阶段,会stop the world再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。

G1如何解决漏标问题:原始快照STAB方式:

当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用关系记录下来,在并发标记结束之后,等到重新标记阶段,会stop the world再讲这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色对象,将这些白色对象标记成黑色对象**,**目的就是让这种对象在本次GC中存下来,等待下一轮GC的时候重新扫描,这个对象也有可能是浮动垃圾。

增量更新针对的是新增,原始快照针对的是删除

无论是插入删除,JVM的记录操作都是通过写屏障实现的,STAB是写前屏障,增量更新是写后屏障。

跨代引用

跨代引用是指年老代空间中的对象引用了新生代的对象,或者新生代中的对象引用了年老代中的对象。

面对这种情况,在进行可达性分析扫描存活对象时,不可能从新生代一直扫描至年老代的,因为这样就会出现整堆扫描的情况,效率必然会很低。

在HotSpot虚拟机中,为了解决跨代引用的问题,会专门在内存中开辟一块小空间用于维护这些特殊的引用,从而达到让GC不必扫描整个堆空间的目的,而开辟的这块小空间则被称为记忆集、卡表

记忆集(Remember Set):

为了解决跨代引用的问题,在新生代引入了记录集的数据结构,记录从非收集区到收集区的引用指针集合,避免在通过根可达算法判断对象存活时把整个老年代加入扫描范围。

GC时,GC收集器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需进行详细的根搜索过程。

卡表(Card Table):

卡表是HotSpot虚拟机中记忆集的实现方式,卡表中记录中记忆集的记录精度、与堆内存区域的映射关系等。

如果有年老代的对象引用了新生代的对象,那么该新生代对象所在区域对应的卡页元素设置为1,反之则为0。

G1以后的GC收集器不分代,所以G1以后的记忆集不是通过数组实现的,而是通过哈希表结构实现。

  • JVM对于卡页的维护也是通过写屏障的方式。

Remembered Set:

G1中实现的一种新的数据结构:简称为RSet,也被称为双向卡表。

在每个Region区中都存在一个RSet,其中记录了其他Region中的对象引用当前Region中对象的关系,也就是记录着谁引用了我的对象,属于points-into结构。

之前的卡表则是属于points-out结构,记录着我引用了谁的对象,在卡表中存在多个卡页,每个卡页记录着一定范围(512KB)的堆。

RSet也好,CardTable也好,都是记忆集的一种具体实现,你也可以将RSet理解成一种CardTable的进阶实现方式。

G1中的RSet本质上就是一个哈希表结构(HashTable),Key为其他引用当前区内对象的Region起始地址,Value则是一个集合,里面的元素为其他Region中每个引用当前区内对象的地址。

实际上G1中的RSet对内存的开销也并不小,当JVM中分区较多且运行时间较长的情况下,这块的内存开销可能会占用到20%以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值