java虚拟机之垃圾回收篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010339039/article/details/88411912

java虚拟机之内存模型篇
java虚拟机之垃圾回收篇
java虚拟机之垃圾收集器篇
java虚拟机之虚拟机类加载机制
java虚拟机之Java内存模型与线程

垃圾收集与内存分配策略

对象存亡

1.引用计数法

给对象添加一个引用计数器,每当有一个地方引用他,计数器就+1,引用失效计数器就-1,任何时刻计数器为0,就是不再使用的对象。这个方法在很多地方也确实在使用,但是 有个很难解决的问题就是对象之间的相互循环引用,比如a持有b的引用,b也持有a的引用,各自的计数器都是1所以无法回收。

2.可达性分析算法

这个算法就是通过一系列称为“gc root”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到gc root没有任何引用链相连时,则证明此对象时不可用的。

可达性分析
** 可以作为“gc root”对象**

在这里插入图片描述

引用

强引用:“Object obj = new Object()” 只要强引用存在,gc永远不会回收被引用对象
软引用(SoftReference):有用但是非必须的对象,在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果还是没有足够的内存就发生oom。
弱引用(WeakReference):也是非必须对象,但是这个对象只能生存到下次垃圾收集发生之前,当gc的时候不管是否内存足够,都会回收掉只被弱引用关联的对象。
虚引用(幽灵引用,幻影引用):是最弱的引用关系,一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,这个的目的就是在垃圾回收它的时候得到一个通知。

注意 :即使不可达的对象,也不是“非死不可”,真正宣告一个对象死亡,至少需要两次标记过程,如果对象到gc root没有相连接引用链,那么就第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,那么就将这两种情况视为“没有必要执行”
如果被判定是有必要执行finalize()方法,那么这个对象将会放置一个F-Queue的队列中,稍后由一个由虚拟机自动建立的低优先级的finalizer线程去执行,但是并不承诺他会运行结束,这时候你如果想让对象不死亡,那么就让他有到gc root的引用链。

回收方法区

永久代的垃圾收集主要两部分内容:废弃常量和无用的类。

常量:没有任何String对象指向“abc”,也没有其他地方引用了这个字面量,如果这个时候gc就会回收掉这个对象。

无用的类:
A.该类的所有实例已经被回收,就是java堆中不存在该类的任何实例
B.加载该类ClassLoader已经被回收。
C.该类对象的java.lang.Class 对象没有任何地方被应用,无法在任何地方通过反射访问该类的方法。

垃圾收集算法

标记-清除算法

最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

在这里插入图片描述
复制算法

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
复制算法的缺点显而易见,可使用的内存降为原来一半。

在这里插入图片描述

注意: 现代商业虚拟机使用此种方式来回收新生代(分配一个较大的Eden,和两块较小的Survivor空间)

标记-整理算法

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
这个在老年代使用,因为对象存活率较高。
在这里插入图片描述

分代收集算法

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法,java虚拟机垃圾收集器关注的内存结构如下:

在这里插入图片描述

Hotspot 算法实现

枚举根节点

可达性分析需要考虑下面两个点:

1.如果方法区大小就有数百兆,如果逐一检查引用,则肯定消耗性能,所以不可能这么做

2.在执行可达性分析时,必须要保证这个过程期间对象的引用关系不能再变化,否则不能保证分析结果正确性

必须要停止所有线程去执行枚举根节点,被称为Stop the World

解决方法

OopMap数据解构: 保存GC Roots 节点,避免全局扫描去一一查找。(目前主流java虚拟机都是准确式GC)

安全点: 精简指令,为特定位置(安全点: Safepoint)上的指令生成对应的OopMap,暂停进行GC的位置也是在安全点,在这个地方引用关系不会发生改变

GC发生时,让所有线程(不包括执行JNI调用的线程) 都“跑”到最近的安全点上再停顿:
抢先式中断(Preemptive Suspension) : GC发生时,中断全部线程,如果发现线程不在安全点,则恢复让其”跑” 到安全点
主动式中断(Voluntary Suspension ): 设置一个标志,然后采用轮询触发,就是线程跑到这里点的时候检查是否jvm有中断请求,如果是就挂起。

安全区域 :在这一段区域中,引用关系不会发生变化,在这个区域中的任意地方开始GC都是安全的。处理没有被分配CPU时间的线程。
当线程在sleep() , 或者blocked 的时候,这个时候线程是无法响应jvm的中断请求,让线程走到安全区域,然后挂起。
所以需要安全区域来处理,这个区域中,在这个区域中任意地方开始gc都是安全的,当线程指向到这个区域的时候,标识自己进入了safe region,当jvm gc的时候就不管进入了安全区域的线程了。线程要离开安全区域的时候,检查系统是否已经完成了根节点枚举(或者整个gc过程)。
我整理的文档下载

猜你喜欢

转载自blog.csdn.net/u010339039/article/details/88411912