finalize():方法是一个在Object类中定义的方法,如果我们重写了finalize()方法,那么在对象被回收之前将会调用finalize()方法,如果我们在finalize()方法中将对象和某个还在生命周期的对象关联上,那么这个对象还有可能在回收之前被复活,当然这种机会只有一次,当第二次遇到回收时,将不会再调用finalize方法。 Java对象是否存活的判断算法——根搜索算法 。
这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”(GC 根)。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。 3. 这个算法的关键就在于GC Roots的定义,有四种可以作为GC Roots的对象 第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。 第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。 第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不被修改,所以方法区常量池里的引用的对象也会作为GC Roots。 最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
Java中的引用一共有四种,它们分别是强引用、软引用、弱引用和虚引用
强引用 就是我们平常用的类似于“Object obj = new Object()”的引用,只要obj的生命周期没结束,或者没有显示地把obj指向为null,那么JVM就永远不会回收这种对象。软引用 相对强引用来说就要脆弱一点,JVM正常运行时,软引用和强引用没什么区别,但是当内存不够用时,濒临逸出的情况下,JVM的垃圾收集器就会把软引用的对象回收。在JDK中提供了SoftReference类来实现软引用,如下图的代码示例所示:
public class TestSoftReference {
@Override
protected void finalize ( ) throws Throwable {
super . finalize ( ) ;
System. out. println ( "当内存溢出时,调用垃圾回收机制" ) ;
}
public static void main ( String[ ] args) {
TestSoftReference test = new TestSoftReference ( ) ;
SoftReference< TestSoftReference> reference = new SoftReference < > ( test) ;
test = null;
System. out. println ( reference. get ( ) ) ;
List list = new ArrayList < String> ( ) ;
int i = 0 ;
while ( i++ < 1000000 ) {
list. add ( String. valueOf ( i) ) ;
}
System. out. println ( "结束" ) ;
}
}
如上代码,我们重写finalize()方法,然后再main()方法中往List集合中不断加值,我们的while循环次数只要相对的多一点,就会出现内存溢出,然后会去调用finalize()方法,输出finalize()方法中的内容。 软引用输出结果 弱引用 比软引用更加脆弱,弱引用的对象将会在下一次的GC时被回收,不管JVM内存被占用多少。在JDK中使用WeakReference来实现弱引用,代码示例如下:
public class TestWeakReference {
@Override
protected void finalize ( ) throws Throwable {
super . finalize ( ) ;
System. out. println ( "弱引用,不管任何时候调用垃圾回收机制,都会被回收" ) ;
}
public static void main ( String[ ] args) throws InterruptedException {
TestWeakReference test = new TestWeakReference ( ) ;
ReferenceQueue< TestWeakReference> queue = new ReferenceQueue < > ( ) ;
WeakReference< TestWeakReference> reference = new WeakReference < > ( test, queue) ;
test = null;
System. out. println ( reference. get ( ) ) ;
System. gc ( ) ;
Thread. sleep ( 100 ) ;
System. out. println ( reference. get ( ) ) ;
}
}
在以上代码中我们主动的调用了一次GC进行垃圾回收,在内存还是充足的情况下依然回收了弱引用的对象,执行结果如下: 虚引用 是最脆弱的引用,我们无法通过一个虚引用来获得对象,即使在没有GC之前。虚引用需要和一个引用队列配合使用。
public class TestPhantomReference {
@Override
protected void finalize ( ) throws Throwable {
super . finalize ( ) ;
System. out. println ( "虚引用,垃圾回收机制" ) ;
}
public static void main ( String[ ] args) throws InterruptedException {
TestPhantomReference test = new TestPhantomReference ( ) ;
ReferenceQueue< TestPhantomReference> queue = new ReferenceQueue < > ( ) ;
PhantomReference< TestPhantomReference> reference = new PhantomReference < > ( test, queue) ;
test = null;
System. out. println ( reference. get ( ) ) ;
System. out. println ( reference. isEnqueued ( ) ) ;
System. gc ( ) ;
Thread. sleep ( 100 ) ;
System. out. println ( "---------------------" ) ;
System. out. println ( reference. get ( ) ) ;
System. out. println ( reference. isEnqueued ( ) ) ;
System. out. println ( queue. remove ( 2000 ) ) ;
System. gc ( ) ;
Thread. sleep ( 100 ) ;
System. out. println ( "---------------------" ) ;
System. out. println ( reference. get ( ) ) ;
System. out. println ( reference. isEnqueued ( ) ) ;
System. out. println ( queue. remove ( 2000 ) ) ;
}
}
于虚引用没有办法访问对象实例,所以我们无法通过对象实例来判断是否被回收,但是我们传入引用队列,在对象被真正清除时,将会被加入到引用队列中,referenceQueue.remove(2000)将会阻塞2秒等待对象入队列,并移除打印。可以看下图输出,可以看出第一次gc虽然执行了finalize方法,但是对象并没有马上被清除,而是在第二次gc的时候才真正被清除。这是由于PhantomReference的处理过程和上面的引用不同,如果重写了finalize方法,那么必须保证finalize方法运行完之后才能加入引用队列,所以如果将代码中的finalize方法去掉,那么在第一次gc之后就可以加入到引用队列。运行结果如下: