什么是垃圾?怎样确定垃圾?
背景:垃圾收集并非java语言的伴生产物,垃圾回收比java更加久远,1960年MIT的Lisp是第一门真正使用内存冬天分配和垃圾收集的语言。经过50多年的发展,内存动态分配和回收技术发展到了相当成熟的阶段,除了java语言,很多其它计算机语言也使用动态内存分配与垃圾回收,例如ruby、python等。
什么是内存垃圾,即那些不会被后续程序使用到的内存对象和结构。
怎么确定内存垃圾,常用的有两种算法:引用计数算法和根搜索算法。
引用计数算法,如名称描述,即对内存中的每一个对象都设置一个计数器。为对象第一次分配内存时,计数器为1,当有其它地方引用该对象时,该对象的计数器加1,引用失效时,计算器减1,直到计算器为0时,该对象则可以被回收。该算法简单而且非常有效,在很多语言中均使用了该算法进行垃圾回收,例如python语言。但是该算法无法解决相互引用的问题,例如代码1所示, 当这类语言遇到相互引用时,则会导致内存泄露,所以程序员在使用该类语言时需要注意相互引用的问题。
根搜索算法,该算法的思想是从一系列根对象出发,遍历对象引用链,从所有根对象都无法到达的对象,则是能被回收的对象。在主流的商用计算机语言(java c#等)均使用根搜索算法作为垃圾回收的算法。在java语言中,作为垃圾回收时的根的对象有以下几类:虚拟机栈中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;本地方法栈中JNI的引用对象。JVM进行GC时,从以上几类根对象出发,遍历内存中的对象,没有被引用的对象,则是可能(GC调用有jvm触发)被回收的对象。在JDK1.2之前,引用被定义得很具体,一个对象要么被引用,要么不被引用。在JDK1.2后,java对引用进行了概念扩充,引用被分为强引用(strong reference),软引用(soft reference),弱引用(weak reference),虚引用(phantom reference),这四种引用强度依次减弱。
在根搜索算法中不可达的对象中,这些对象也不是立即被当作垃圾进行回收,而是有一个“缓刑期”。即对象要真正被回收,至少要进行两次标记过程。第一次标记将所有不可达的对象进行标记,并且进行筛选,筛选出那些finalize方法被覆盖的对象并且该对象没有被调用finalize(),则将该对象放置到一个F-Queue队列中,其余对象则被回收。第二次标记即对F-Queue队列中的对象进行调用finalize()方法,如果在finalize方法中,对象实现了自我救赎(例如把this指针赋值给其它对象),那么将移除“即将回收”对象集合,否则下次对“即将回收”对象集合进行真正垃圾回收。
代码1中所示逻辑,在引用计数算法则对象不能被回收,然而在java中会被回收,可以利用相应参数查看结果。
代码1
package jvm.com.cn;
//-verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class ReferenceCountingGC {
public Object instance = null;
private byte[] bigSize = new byte[2*1024*1024];
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ReferenceCountingGC instanceA = new ReferenceCountingGC();
ReferenceCountingGC instanceB = new ReferenceCountingGC();
instanceA.instance = instanceB;
instanceB.instance = instanceA;
instanceA = null;
instanceB = null;
System.gc();
}
}