Java的内存管理1:“并不只有C++程序员关心内存回收”——Java的内存管理2:"不中用的finalize( )方法"

通常Java的缓存管理会由垃圾回收器(Java Garbage Collection)定时处理,无须程序员操心。但Java Garbage Collection仅有权回收那些非“强引用”(Strong Reference)类型的类。也就是说,如果程序生成大量的强引用对象,JVM存放对象实例的“堆”(heap)容量不够时(默认64M,可设置),就会抛出java.lang.OutOfMemoryError: Java heap space的错误,看下面的代码:

编译器报错:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

JDK用自带的类java.lang.ref.SoftReference提供解决办法,而不是像C++那样自己动手写缓存管理队列。SoftReference将我们新实例化的类转变成“软引用”(Soft Reference)型,以便Garbage Collection能够在JVM的heap不足时,回收那些相对较旧的“软引用”实例,腾出heap空间存放新的对象实例。需要注意的是,尽管“软引用”实例被回收,但它们的Reference已经在JVM中完成登记,一旦它们被再次引用,JVM能够复原它们(仍旧是“软引用”型)。下面的代码显示如何使用SoftReference类:

上述代码可以用:BookShelf shelf = (BookShelf)shelves[i].get();得到BookShelf的实例。这时所有实例都会被创建:

JavaDoc1.6中写道,一个实例的引用类型,可用它在引用链中的可达性(reachable)表示,JVM共有5种可达性:强可到达(strongly reachable);软可到达(softly reachable);弱可到达(weakly reachable);虚可到达(phantomly reachable);不可到达(unreachable). 除了Strong Reference,其他四种对象都可以被回收。

可到达性

从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:

  • 如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象(注:这就是第一段代码中BookShelf不能被gc回收的原因:BookShelf对Liberary来说是强可到达对象)。
  • 如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
  • 如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
  • 如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。
  • 最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。

java.lang.ref包中对应还有WeakReference、PhantomReference两个类,有机会再实践下。

import java.lang.ref.SoftReference;

class Book
{
	char[] symbols = new char[300000];
}

class BookShelf
{
	Book[] books = new Book[100];

	public BookShelf()
	{
		for (int i = 0; i < books.length; i++)
		{
			books[i] = new Book();
		}
		// TODO Auto-generated constructor stub
	}
}

public class Library
{
	public static void main(String[] args)
	{
		// BookShelf[] shelves = new BookShelf[50];
		SoftReference[] shelves = new SoftReference[50];
		for (int i = 0; i < shelves.length; i++)
		{
			// shelves[i] = new BookShelf();
			shelves[i] = new SoftReference(new BookShelf());
			System.out.println("Creating bookshelf: " + (i + 1));
		}
	}
}

《Java的内存管理1》提醒我们:如果需要在同一对象中不停地new一些占用大量内存的实例,务必使用java.lang.ref.SoftReference类对实例进行“包装”,防止JVM因Java Garbage Collection不能回收新建实例,而造成的内存溢出错误。

我想到可以用finalize()方法,观察实例在什么时候会被GC回收(任何继承Object的类都可Override),finalize()会在一个实例即将被GC回收的时候被调用一次,因此可用它记录一个实例的“临终遗言”,我对代码做了如下修改:(顺道实现了java1.5的泛型)

运行结果却出乎意料:

Creating bookshelf: 1
Creating bookshelf: 2
Creating bookshelf: 3
Creating bookshelf: 4
Creating bookshelf: 5
Creating bookshelf: 6
Creating bookshelf: 7
Creating bookshelf: 8
Creating bookshelf: 9
Creating bookshelf: 10
Creating bookshelf: 11
BookShelf: 10 is dying!
BookShelf: 9 is dying!
BookShelf: 8 is dying!
BookShelf: 7 is dying!
BookShelf: 6 is dying!
BookShelf: 5 is dying!
BookShelf: 4 is dying!
BookShelf: 3 is dying!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at edu.thinkingjava.chap4.Book.<init>(Library.java:5)
at edu.thinkingjava.chap4.BookShelf.<init>(Library.java:15)
at edu.thinkingjava.chap4.Library.main(Library.java:28)
BookShelf: 2 is dying!
BookShelf: 1 is dying!
BookShelf: 11 is dying!

即便使用了SoftReference做缓存保护,控制台再次抛出OutOfMemoryError的异常。输出的BookShelf销毁顺序,每次都有不同,颇有多线程的味道。由此看出GC的回收机制充满不确定性。JavaDoc1.6解释如下:

在启用某个对象的 finalize 方法后,将不会执行进一步操作,直到 Java 虚拟机再次确定尚未终止的任何线程无法再通过任何方法访问此对象,其中包括由准备终止的其他对象或类执行的可能操作,在执行该操作时,对象可能被丢弃。 对于任何给定对象,Java 虚拟机最多只调用一次 finalize 方法。

JavaDoc实际上在说:finalize()方法阻挡了GC对BookShelf实例的首次回收,JVM转而去调用finalize()方法,所幸Java 虚拟机最多只调用一次 finalize 方法,当下一次JVM发现内存吃紧,BookShelf实例才能最终被回收。

由此例推断:JVM释放内存的进度被finalize方法耽误了,因此出现内存溢出异常(大家可以试着把代码第19行注释掉,内存又恢复正常了)。


import java.lang.ref.SoftReference;

class Book
{
	char[] symbols = new char[300000];
}

class BookShelf
{
	private int ID;

	Book[] books = new Book[100];

	public BookShelf()
	{
		for (int i = 0; i < books.length; i++)
		{
			books[i] = new Book();
		}
		// TODO Auto-generated constructor stub
	}

	public BookShelf(int ID)
	{
		this.ID = ID;
		for (int i = 0; i < books.length; i++)
		{
			books[i] = new Book();
		}
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void finalize() throws Throwable
	{
		// TODO Auto-generated method stub
		super.finalize();
		System.out.println("BookShelf: " + ID + " is dying!");
	}
}

public class Library
{
	public static void main(String[] args)
	{
		// BookShelf[] shelves = new BookShelf[50];
		SoftReference[] shelves = new SoftReference[50];
		for (int i = 0; i < shelves.length; i++)
		{
			// shelves[i] = new BookShelf();
			shelves[i] = new SoftReference(new BookShelf(i + 1));
			System.out.println("Creating bookshelf: " + (i + 1));
		}
	}
}


posted on 2014-02-10 14:58  Java码界探秘  阅读(90)  评论(0编辑  收藏  举报

导航