JVM——垃圾回收机制

判断对象是否存活

引用计数法和可达性分析都是判断对象是否存活的算法

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1,引用失效时计数器就减1,当引用计数器为0时,这个对象就是不可能再被使用的。
缺点
很难解决对象之间互相引用的问题

两个对象obj1和obj2都有一个成员为instance
obj1.instance = obj2
obj2.instance = obj1

此时这两个对象其实都无法被引用了,但是因为他们互相引用,导致引用计数器不为0,所以引用计数算法无法通知GC 收集器去回收他们。

可达性分析

通过一系列的称为“GC ROOT”的对象作为起始点,从这些节点向下搜索,搜索走过的路径称为“引用链”,当有个对象到“GC ROOT ”没有一条引用链相连时,这个对象就是不可用的。
在这里插入图片描述
可作为GC Root的根节点有哪些?
1.虚拟机栈中的引用的对象
2.方法中静态属性引用的对象
3.本地方法栈中JNI(也就是Native方法) 引用的对象
4.方法区中常量引用的对象

垃圾收集算法

标记—清除

标记清除算法分两个阶段
标记阶段:对堆内存的对象进行可达性分析,将有引用的对象标记一下
清除阶段:判断堆内存的对象是否被标记,将未标记的对象回收

缺点:效率问题与空间问题
效率:标记和清除这两个过程效率都不高。
不管标记、还是清除阶段对我们整个堆内存进行遍历,由于堆内存在JVM内存模型占的空间很大,效率低下。
空间:在清除阶段会产生很多不连续的内存碎片,新对象申请内存会存在内存不足,会引起再次 GC 问题。
在这里插入图片描述

复制算法

为了解决标记-清除中的空间问题(内存碎片)、效率问题(遍历整个堆空间)
将堆的空间分为两部分,将对象放在其中一部分处理。
将有引用的对象复制到另一空间,对当前空间全部清除。
缺点
当对象存活率太高,复制存活对象去另一部分效率太低。
堆空间使用率太低,另外一半空间没有用

标记—复制算法

这是堆的空间模型(永久代不是所有的jvm都有),
在这里插入图片描述

IBM研究表明,年轻代的对象98%都是朝生夕死的,所以没有必要将内存划分为1:1的两块,而是将年轻代的内存分为三块,Eden、From survivor、to survivor,每次使用Eden和From Survivor,每次使用Eden和From Survivor区,回收时,将Ende 和 From Surivor区存活的对象依次性的拷贝到to survivor的里。

标记—整理算法

标记阶段和标记—清除算法一样
清除阶段:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
在这里插入图片描述

分代收集算法

分代收集算法就是对前面几种算法的整合,根据各个年代的特点进行最适当的收集算法
年轻代:对象朝生夕死,每次只有少量对象存活,采用标记-复制算法
老年代:对象存活率高,标记—清除、标记-整理

内存分配与回收策略

对象优先在Eden分配
大多数情况下,对象优先在Eden区分配,当Eden区没有足够的空间区分配的时候,虚拟机将发起一次Minor GC

年轻代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为java 中的对象大多数朝生夕死,所以Minor非常频繁,一般来说回收速度也快.
老年代 GC (Full GC):发生在老年代的GC,出现了 Full GC经常会盘随着至少一次的Minor GC,Full GC 的速度一般会比Minor GC 慢十倍以上

大对象直接进入老年堆
大对象:需要大量连续内存空间的java 对象
目的:为了避免新生代发生大量的内存拷贝
应当避免使用朝生夕死的大对象
长期存活对象进入老年堆
虚拟机为每个对象定义了一个对象年龄计数器,对象在Eden区出生并经过一次GC 之后仍然存活,并且能够被survivor容纳的话,将被移动到survivor空间中去,并将对象的年龄设为1,对象在survivor中每经过一次GC ,年龄就增加一岁,当它的年龄增加到一定程度(默认15岁),就会被晋升到老年代中。
动态年龄分配策略
如果在survivor中相同年龄的对象的总和大于survivor空间的一半,年龄大于等于该年龄的对象就可直接进入老年代
空间分配担保

在发生minor gc之前,虚拟机会检测 : 老年代最大可用的连续空间>新生代all对象总空间
1、满足,minor gc是安全的,可以进行minor gc。
2、不满足,虚拟机查看HandlePromotionFailure参数:
(1)为true,允许担保失败,会继续检测老年代最大可用的连续空间>历次晋升到老年代对象的平均大小。若大于,将尝试进行一次minor gc,若失败,则重新进行一次full gc。
(2)为false,则不允许冒险,要进行full gc(对老年代进行gc)。

新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

猜你喜欢

转载自blog.csdn.net/Alyson_jm/article/details/84329804