JVM GC主要针对的是Java堆和方法区
如何判断对象已死:
可达性分析算法(Reachability Analysis):通过一系列的称为“GC Roots”的对象作为起点,从对象节点开始向下搜索,搜索所走过的路径为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链的时(从GC Roots到这个对象不可达),则证明此对象不可用。
附:引用计数法(Reference Counting):此方法因为很难解决对象之间相互引用的问题,所以暂时不讨论。
Java内置对象引用工具:
1、强引用(Strong Reference):只要强引用存在,GC不会回收,甚至OOM也在所不惜
2、软引用(Soft Reference):在系统将要发生溢出之前才回收
3、弱引用(Weak Reference):只能生存到下一次GC回收之前
4、虚引用(Phantom Reference):仅仅是理论上存在,唯一的目的就是能在这个对象没收集器回收时收到一个通知
Java回收流程:
即使在可达性分析算法中为不可达,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,想要宣告一个对象死亡,至少要经过两次标记过程:
第一次标记:对象没有与GC Roots相连接的引用链,且finalize()方法被重写且没有被虚拟机调用过,将会被第一次标记,且放到F-Queue的队列之中,虚拟机会自动启动一个低优先级的Finalizer线程去执行对象finalize()方法。
第二次标记:finalize()方法将会是对象逃脱死亡的最后一次机会,GC将对F-Queue中的对象进行第二次小规模标记,如果对象依然没有GC Roots相连接的引用链,那么对象直接被回收。
方法区回收(永久代):
主要回收废弃常量和无用的类。
无用类判断必须满足以下三个条件:
1、该类所有的实例都已经被回收,也就是java堆中不存在该类任何实例
2、加载该类的ClassLoader已经被回收
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法
分代收集算法(Generational Collection):
当前商业虚拟机采用分代收集算法,这种算法根据对象的存活周期的不同将Java堆划分为几块,一般是将虚拟机划分为新生代和老年代,新生代中有大批的对象死去只有少量存活,老年代存放趋于稳定的对象。
由于新生代对象存活下来的相对较少,所以可以使用复制算法,而老年代因为对象存活率高,可采用标记清理或标记整理算法。
标记-清除算法(Mark-Sweep):首先标记出所有需要回收的对象,在标记完成后统一回收所有标记的对象。缺点:效率不足,标记清楚后会产生大量不连续的内存碎片
标记-整理(Mark-compat):首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存
复制算法(Copying):根据需要将容量划分为两块,每次只使用一块,当这一块内存用完了,就将还在存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉,缺点:将内存缩小了
HotSpot的算法实现
这部分内容比较抽象,如果更具体的去了解,建议查看《深入理解JVM虚拟机》3.4章节和百度相关资料。以下是个人白话文理解,希望有所帮助。
1、GC停顿(Stop The Word):暂停所有Java执行线程,进行可达性分析工作。之所以需要暂停所有Java运行线程,是因为需要保证在GC过程中,不能出现对象引用关系变化,保证分析结果的准确性。
2、枚举根节点:将GC Roots的节点保存在OopMap中,在GC时不用重新遍历(可能耗时)GC Roots 根节点,而是直接通过OopMap获取。不是每条指令都生成OopMap,只是在特定的位置记录。可作为GCRoots的节点主要在全局性的引用(如常量和类静态属性)与执行上下文(例如栈帧中的本地变量表),在类加载完成的时候,虚拟机就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会特定的记录下栈和寄存器中哪些位置是引用。
3、安全点(Safepoint):不是每条指令都生成OopMap,只是在特定的位置记录,这些特定的点被称为安全点。程序执行时并非所有的地方都能停下来GC,只有到达安全点的时候才能暂停,所以安全点可以理解为对象是否接受GC考验的临界条件。
安全点的选择有两种方案:
a、抢先式中断(Preemptive Suspension):GC线程主动中断全部线程,如果发现有线程不在安全点上,则让线程运行到安全点上,再进行中断
b、主动式中断(Voluntary Suspension):不对线程进行操作,仅仅是设置一个标志位,所有线程主动去轮询这个标志位,然后自动挂起。
Safepoint指的特定位置主要有:
a. 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
b. 方法返回前
c. 调用方法的call之后
d. 抛出异常的位置
4、安全域(Safe Region):安全区域是指在一段代码中,引用关系不会发生变化,在这个区域中的任意地方GC都是安全的。主要针对处于Sleep状态和Blocked状态的线程,当线程离开安全区域时,它需要检查系统是否已经完成了根节点枚举(或者整个GC过程),如果GC没完成,则需要等待直到收到可以离开安全区域为止。