上一篇文章了解的Java引用的四种模式,接下来将为大家讲解对象标记算法。当一个对象不再被引用时,该对象就变得useless了,其实及时它目前没有任何一个reference的target,并且认为它今后也不再会被引用(这是用从逻辑上说,实际上此刻没有被引用的对象,今后也不会被引用了)
1.引用计数法(Reference Counting)
核心思想:很简单,每个对象都有一个引用计数器,当在某处该对象被引用的时候,它的引用计数器就加一,引用失效就减一。引用计数中的值一但变为0,则该对象就成为垃圾了。但目前的JVM没有用这种标记方式。为什么呢?
因为引用计数无法解决循环引用(对象引用关系组成“有向有环图”的情况,涉及一些图论知识,在“根搜索算法”中会解释)的问题。例如下面的例子(该程序为Android测试程序):
package com.jony.gallery; import android.app.Activity; import android.os.Bundle; public class GalleryTestActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // _1MB_Data data1 = new _1MB_Data(); // _1MB_Data data2 = new _1MB_Data(); // data1.instance = data2; // data2.instance = data1; // data1 = null; // data2 = null; // System.gc(); } } class _1MB_Data { public Object instance = null; private byte[] data = new byte[1024 * 1024*8]; }
当在手机上运行时,可以在DDMS中看到JVM内存的使用情况,具体如图所示:
从图中可以看出堆栈(Heap Size)大小为2.895MB;当取消上面的代码的注释后,再次运行程序,结果如下图所示:
通过以上运行结果可以得出结论:Dalvik虚拟机并没有采用引用计数法,事实上,现在没有什么流行的JVM会采用简陋而且问题多多的引用计数算法来标记。不过要承认,它确实简单而且大多数时候有效。
那么,这些主流的JVM都是使用什么标记算法呢?
2.根搜索算法(Grabage Collection Roots Tracing)
根搜索算法思路很简单(算法领域,除了红黑树、KMP等等比较复杂外,大多数思路都很简单),可以概括为如下几步:
选定一些对象,作为GCRoots,组成基对象集;
有基对象集的对象出发,搜索所有可达的对象;
其余的不可达的对象,就是可以被垃圾回收的对象。
这里的“可达”与“不可达”与图论中的定义一样,所有的对象被看做点,引用被看做有向链接,整个引用关系就是一个有向图,在“引用计数法”中提到的循环引用,其实就是有向图中有环的情况,即构成“有向有环图”。引用计数法不适用于“有向有环图”,而根搜索算法使用于所有“有向图”,包括有环的和无环的。那么是如何解决的呢?
GC Roots
如果你的逻辑思维够清晰,你会说“一定与选取基对象集的方法有关”。是的,没错。选取GC Roots组成的基对象,其实就是选取如下这些对象:
方法区(Method Area,即 Non-Heap)中的类的static成员引用的对象,和final成员引用的对象;
Java方法栈(Java Method Statck)的局部变量表(Local Variable Table)中引用的对象;
本地方法栈(Native Method Stack)中JNI中引用的对象。
由系统类加载器加载的类相应的对象:这些类永远不会被卸载,且这些类创建的对象都是static的。注意用户使用的类加载器加载的类创建的对象,不属于GC Roots,除非是java.lang.Class的相应实例有可能会称为其他类的GC Roots。
正在运行的线程
Java方法栈(Java Method Stack)的局部变量表(Local Variable Table)中引用的对象。
本地方法栈(Native Method Stack)的局部变量表(Local Variable Table)中引用的对象。
JNI中引用的对象。
同步监控器使用的对象。
由JVM的GC控制的对象:这些对象是由于JVM内部的,是实现相关的。一般情况下,可能包括系统类加载器、JVM内部的一些重要的异常类的对象、异常句柄的预分配对象和在类加载过程中自定义的类加载器。不幸的是,JVM并不提供这些对象的任何额外的详细信息。因此这些实现相关的内容,需要依靠分析来判定。
综上所述:这个算法实施起来有两部分,第一部分就是到JVM的几个内存区域中“找对象”,第二部分就是运用图论算法。
(备注:JVM的标记算法并不是JVM垃圾回收策略中最重要的,正真的核心,是回收算法,当然标记算法是基础)
参考资料:
http://www.uml.org.cn/j2ee/201203121.asp