上一篇说了Java运行时的内存区域及对象的创建,本文将说垃圾回收器及内存分配,上一篇文章链接 Java虚拟机一:Java运行时内存区域及对象的创建
[声明]Java虚拟机设计的知识点主要来源于周志明先生的深入理解Java虚拟机
本文将根据三个方面来描述垃圾回收机制:
一、如何确定哪些是要回收的对象
二、垃圾回收算法思想
三、垃圾收集器
一、如何确定哪些是要回收的对象
1.1。引用计数算法 :
引用计数算法就是给对象添加一个引用计数器,每当有一个地方引用该对象的时候就会+1,相反当失去一个引用的时候就-1,当引用数为0的时候也就说明这个对象不在被使用就可以被回收。这种算法实现简单,效率也很高,但是唯一的缺点就是在两个对象相互引用的时候那么他们的引用就不会为0,那么GC也就无法回收他们,
例子:
public class GcTest {
public Object instance = null;
public static void testGC() {
GcTest objA = new GcTest();//第一步,实例1
GcTest objB = new GcTest();//第二步,实例2
objA.instance = objB;//第三步
objB.instance = objA;//第四步
objA = null;//第五步
objB = null;//第六步
}
}
上面的例子中内存是不会被回收的,这个例子也被广泛的流传,但是又是否真的理解了为什么不能被回收呢?下面我们分析一下为什么:
1。第一步,GcTest 触发了new操作,在栈中创建objA的引用,在堆中创建了GcTest的实例,那么这个时候GcTest的实例1引用数 = 1,被objA引用;2。第二步,GcTest 触发了new操作,在栈中创建objA的引用,在堆中创建了GcTest的实例,那么这个时候GcTest的实例2引用数 = 1,被objB引用;
3。第三步,GcTest实例2的引用 = 2,因为他的实例又被实例1引用了
4。第四部,GcTEst实例1的引用 = 2,因为他的实例又被实例2引用了
5。第五步,这个时候实例1的引用变为了 = 1,因为置null后,这时候实例1只是失去了栈中objA的引用
6。第六步,这个时候实例2的引用变为了 = 1,因为置null后,这时候实例2只是失去了栈中objB的引用
那么我们也就清楚了,这个时候虽然我们的实例1和实例2都不再使用,但是因为它们两个的循环互相引用,那么GC是没办法对它们回收的。这也就是引用计数算法最大的缺点
1.2。可达性分析算法 :
可达性分析算法是以一系列可以成为GC Roots的对象的节点向下搜索,搜索所走的路径称之为引用链,当一个对象没有任何一条引用链和GC Roots相连的时候,那么就认为这个对象是可以被回收的。
可以被称之为GC Roots的对象有:
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(即一般说的Native方法)引用的对象;
总结就是,方法运行时,方法中引用的对象;类的静态变量引用的对象;类中常量引用的对象;Native方法中引用的对象。