文章分享-19周-垃圾收集器与内存分配策略(上)

本文章内容基于《深入了解Java虚拟机》第二版,对里面知识点进行总结,JDK主要是1.6和1.7。

1. 确定对象可以被回收

1.1 引用计数算法

每个对象拥有一个计算器值,每当一个地方引用他则计数器值+1;引用失效则-1.
当计数器值为0,则说明对象可以被回收
缺点:很难解决对象之间互相虚幻引用的问题,目前虚拟机很少使用该算法。

1.2 可达性分析算法

通过一系列“GC Roots”的对象作为起始点,从这些节点向下搜索,经过路径为“引用链”,如果搜索到则可达,否则不可达。
GC Roots对象包括:
1、虚拟机栈(栈帧中的本地变量表)中引用的对象。
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(一般为Native方法)引用的对象


可达性分析

1.3 引用四种类型

1、强引用:普遍存在,例如“Object obj = new Object()”,只要引用存在,永远不会被回收
2、软引用:当系统要发现内存溢出异常之前,对这部分对象列入回收范围进行二次回收。SoftReference类实现
3、弱引用:垃圾收集器工作时,这部分对象都会被回收。WeakReference类实现
4、虚引用:无法通过虚引用获取一个对象的实例,唯一目的就是该对象被收集器回收时收到一个系统通知。PhantomReference类实现

1.4 finalize()函数

每个对象被标记成不可达,会经历两次标记过程。如果对象没有覆盖finalize()函数或已经被执行故一次,就判断对象已经死亡。
其实执行finalize()函数可以将不可达本身对象变成可达,让对象依然可以存活。(但是建议不使用finalize()函数)

1.5 回收方法区(永生代)

这部分区域回收内容:废弃常量和无用的类

1、废弃的常量:例如常量池中字符串,跟Java堆的对象一样;常量池的字符串没有String对象引用,就会被收回。

2、无用的类:需要同时满足以下条件
a、该类所有的实例都被回收。(Java堆不存在该类的任何实例)
b、加载该类的ClassLoader已经被回收。
c、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
及时满足以上条件,虚拟机也非必然回收。虚拟机都会提供参数来控制是否对类进行回收。

注意:在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景需要虚拟机具备类协助的功能,保证方法区不会溢出。

2. 垃圾收集算法

2.1 标记-清除算法

先把标记出所有需要回收的对象,标记之后统一回收所有被标记的对象。
缺点:1、效率不高 2、消除之后生产大量不连续的内存碎片

“标记-消除”算法示意图

2.2 复制算法

复制算法为了解决效率问题和碎片问题
将内存分为两个相同大小,每次只使用其中一块。当这一块内存使用完,则将还存活的对象复制到另外一个块,然后对使用过内存空一次性清理掉。
缺点:内存要一分为二,内存浪费比较高

复制算法

目前商用虚拟机都采用该算法回收新生代。
其他知识点:IBM公司研究新生代98%都是很快消亡,不需要1:1划分内存。
只需要一块较大Eden空间和两块较小的Survivor空间。每次使用Eden和其中一块Survivor空间。回收时候将存活对象复制到另外一块Survivor空间。目前HostSpot虚拟机默认Eden和Survivor比例为8:1。
注意:无法保证每次回收都不多于10%的对象存活,当Survivor空间不够时使用其他内存进行分配担保(比如老年代)。

2.3 标记-整理算法

复制算法在对象存活率较高的情况之下,由于执行过多的复制操作,效率将变低。内存空间浪费较多,老生代一般不使用该算法。
针对老生代,提出标记-整理算法,将所有存活的对象都向一端移动,然后直接清除端边界以外的内存。


标记-整理算法

2.4 分代收集算法

目前主要为新生代和老年代,针对这两块的空间对应的收集算法是不同的。

3. HotSpot的算法实现

3.1 枚举根节点

可达性分析问题:
1、如果从全局性的引用和执行上下问(栈帧的本地变量表)中区搜索对象是否可达会消耗很多时间。
2、可达性分析为了保持“一致性”,会停止系统所有执行线程。(主要还是优化时间长度,无法完全解决)

目前所有虚拟机采用准确式GC
HotSpot使用一组OopMap的数据结构来实现虚拟机直接知道对象引用存放的地方。(不需要检查所有上下文和全局引用)(记录对象内各种类型数据的偏移量)
JIT编译过程,会在特定位置记录下栈和寄存器哪些位置是引用。

3.2 安全点

引用关系变化或OopMap内容变化的指令很多,如果为每一条指令都生成对应的OopMap,则需要大量的额外空间。
HotSpot实现并非为每一条命令都创建OopMap。
上一节所说的特定位置指的就是安全点。即程序执行时并非所有地方都可以停顿,只有在到达安全点才能暂停,然后在开始GC。

问题:如果让所有线程在开始GC之后都执行到安全点上停顿下来。
方案1:抢先式中断-先让所有线程中断,然后恢复让不在安全点线程继续执行到安全点。(目前已经没有这种实现方式)
方案2:主动式中断-设置一个标志,各线程执行时主动区轮询该标志。发现中断标志就中断刮起(中断标志和安全点是重合、还有创建对象需要分配内存的地方)

3.1 安全区域

如果程序没有分配CPU时间,就无法执行到安全点然后挂起。(部分Sleep状态或Blocked状态)通过安全区域来解决该问题。
安全区域表示该段代码中,引用关系不会发生变化。
JVM执行GC时,不用管执行到安全区域的线程(状态判断)。线程要离开安全区需要判断系统是否完成根节点枚举(或整个GC)

你可能感兴趣的:(文章分享-19周-垃圾收集器与内存分配策略(上))