参考书籍:深入理解Java虚拟机:JVM高级特性与最佳实践(第三章)
看了《深入理解Java虚拟机:JVM高级特性与最佳实践》之第三章垃圾收集器与内存分配策略,了解了虚拟机的垃圾回收的东西,但是尚未有自己更深的见解,因此在此,只是把看过的自己能记住的一些东西记录下来,依旧是待完善。
java不需要像C一样管理内存溢出等问题,这正是因为有了java虚拟机的GC特性(Garbage Collect,垃圾回收特性)。因此接下来记录的思路是这样的:
1.垃圾回收顾名思义是收集掉不需要了的对象,腾出内存空间给其他的对象使用,那么问题来了,什么样的对象是不再需要的呢?据传有两种判断方式:
1)引用计数算法。给对象添加一个引用计数器,当对象被引用时计算器+1,引用失效时-1,这样当计数器为0时,说明该对象不再被使用,这种情况下,可以判定对象已死。这种方式的优点是:执行效率高;但是缺点是:不能处理相互循环引用。
2)可达性分析算法。在第二篇博客里提到的“有向图方式”就是现在所说的可达性分析算法。即从根节点出发,向下查找对象,如果可以到达对象,则说明该对象还处于被引用中,否则就说明对象已死。如图所示(图来自于参考书籍中),其中1,2,3,4对象仍然存活,5,6,7已经死亡。该算法的优点是,有效的解决了对象相互引用的问题,但是缺点是效率略低,不过java虚拟机有自己的方式来提高效率,接下来会做介绍。
java虚拟机的垃圾回收算法中到底是采用哪种方式来判断对象已死呢,二者的最大区别就是对于相互循环引用的对象是否能够回收,如果能够回收,说明采用了可达性分析算法,否则是计数法,因此,接下来我们通过一段程序来验证下。
public class Velocity{
public Object instance = null;
public static void testGC(){
Velocity objA = new Velocity();
Velocity objB = new Velocity();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
通过运行后的GC日志分析得出,并没有因为objA和objB相互引用就不回收,这说明java虚拟机采用了第二种算法。
但是第二种算法存在一个问题,很多应用仅仅方法就有几百兆,如果一个个的去检查引用,势必会使得效率特别低,而且在GC在执行时会出现一个停顿(Stop the world STW),这是为了保持一致性,也就是说在分析对象引用关系时,不会出现引用关系还在不断变化的情况,因此会出现这样的一个停顿。在停顿时,GC也不是一个不漏的去检查引用,在HotSpot的实现中,使用了一组OopMap的数据结构,在类加载完成时把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特点的位置记录下栈和寄存器中哪些位置是引用,这样GC在扫描时就可以直接知道引用关系了,而且GC没有为每个指令都生成OopMap,只在特定的位置生成,这个点称为安全点(Safespoint),程序在运行到该点的时候会停顿下来执行GC。但是如果有些程序没处于执行状态,比如线程处于Sleep或者Blocked状态,这种情况下就不会走到安全点,对于这种情况,就通过安全区域来解决该问题,该区域的引用关系不会发生变化,因此可以走到这里时可以发起GC。
对于GC采用什么算法有了大致的了解,但是还有两点需要提到的是,什么样的对象可以作为GC Roots对象?引用如何划分?
该书是这么说的,在Java语言中,可作为GC Roots的对象包括下面几种:
a.虚拟机栈(栈帧中的本地变量表)中引用的对象
b.方法区中类静态属性引用的对象
c.方法区中常量引用的对象
d.本地方法栈中JNI(即一般说的Native方法)引用的对象。
Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种强度依次逐渐减弱。
强引用:有强引用存在,垃圾收集器就不会回收掉该引用的对象。
软引用:……