JVM系列(三)如何判断对象可以被回收
我们知道JVM的垃圾回收主要集中在堆内存上,当新生代和老年代空间不足时都会发生GC,那么具体有什么区别和具体可以回收哪些对象呢?下面我们一起来探讨一下。
Minor GC/Young GC: 又称新生代GC,指发生在新生代的垃圾收集动作;因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;
Major GC/Full GC: 指发生在老年代的GC;
出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);Major GC速度一般比Minor GC慢10倍以上;Full GC会回收新生代和老年代的垃圾。
下面我们再来讨论一下垃圾回收时怎么判断哪些对象可以被回收,在讨论这个问题之前,我们先来了解一下在java中的常见的四种引用。
一、强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:
Object o=new Object(); // 强引用
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:
o=null; // 帮助垃圾收集器回收此对象
显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法
二、软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str = new String(“abc”); // 强引用
SoftReference softRef = new SoftReference(str); // 软引用
三、弱引用
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
String str=new String(“abc”);
WeakReference abcWeakRef = new WeakReference(str);
四、虚引用
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
下面我们来具体说明一下,jvm进行垃圾回收时,如何判断哪些对象可以被回收那些对象不可以被回收。判断对象是否可以被回收主要有两种方式:1.引用计数法 2.可达性分析算法。但是一般虚拟机(HotSpot)都会采用可达性分析算法。具体两者有什么区别呢?
·引用计数法(feference-countint) : 每个对象有一个引用计数器,当对象被引用一次则计数器就加1,当对象引用失效一次则计数器减1,对于计数器为0的对象就意味着可以被回收。
·可达性分析算法(GC Root Tracing) : 从GC Roots 作为起点开始搜索,能和GC Roots根直接相连的对象都被标记为非垃圾对象,其余未标记的都是垃圾对象。
哪些对象可以作为GC Roots根节点: 栈的本地变量表所引用的对象、方法区的静态变量和常量所引用的对象、本地方法栈中所引用的对象。
下面我们结合代码和具体的内存分配情况具体说明为什么虚拟机一般使用可达性分析算法而不使用引用计数法。
public class GcTrace {
public static void main(String[] args) {
// step 1
GcObject obj1 = new GcObject();
// step 2
GcObject obj2 = new GcObject();
// step 3
obj1.instance = obj2;
// step 4
obj2.instance = obj1;
// step 5
obj1 = null;
// step 6
obj2 = null;
}
}
public class GcObject {
public Object instance = null;
}
假设使用引用计数法进行分析;
以上代码分为六个步骤执行:
1: 执行完第一、二步后,obj1和obj2对象的引用计数器的值都为1;
2: 当执行完第三、四步骤后,obj1和obj2对象的引用计数器的值又分别加1,此时都为2;
3: 当执行完第五、六步骤后,obj1和obj2对象的引用计数器的值又分别减1,此时为1。
在进行垃圾回收时,判断对象的应用计数值不为0,所以此对象不会被回收。所以当对象存在循环引用的情况是,使用引用计数法不能将对象进行回收,就有可能造成内存溢出问题。
假设使用可达性分析算法进行分析;
同样代码分为六个步骤执行:
对照下面的内存模型,当六步都执行完成后,内存分配情况如下图所示:栈中指向堆内存的指针已经断开,只有在堆内存中存在相互引用的指针。
那么此时进行垃圾回收,虽然堆内存中对象存在相互引用,但是他并不和GC Roots根直接相连,所以是可以被回收掉的。回顾一下上面讲的可以作为GC Roots根的对象(栈的本地变量表所引用的对象、方法区的静态变量和常量所引用的对象、本地方法栈中所引用的对象)