Effective Java学习之——消除过期对象引用_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > Effective Java学习之——消除过期对象引用

Effective Java学习之——消除过期对象引用

 2013/9/27 21:47:40  sungang_1120  程序员俱乐部  我要评论(0)
  • 摘要:当你从手工管理内存语言(比如C++/C)转换到具有垃圾回收功能的语言的时候,程序员的工作变的更加容易,因此当你用完了对象之后,它们会被自动回收。当你第一次经历对象回收功能的时候,会觉得这简直有点不可思议。这很容易给你留下这样的印象,认为自己不再需要考虑内存管理的事情了。其实不然!!!考虑下面这个简单的栈实现的例子importjava.util.Arrays;importjava.util.EmptyStackException;publicclassStack
  • 标签:学习 Java

? ? ? 当你从手工管理内存语言(比如C++/C)转换到具有垃圾回收功能的语言的时候,程序员的工作变的更加容易,因此当你用完了对象之后,它们会被自动回收。当你第一次经历对象回收功能的时候,会觉得这简直有点不可思议。这很容易给你留下这样的印象,认为自己不再需要考虑内存管理的事情了。其实不然!!!

?

考虑下面这个简单的栈实现的例子

?

class="java">import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack {
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_INITIAL_CAPACITY = 16;
	
	public Stack(){
		elements = new Object[DEFAULT_INITIAL_CAPACITY];
	}
	
	public void push(Object s){
		ensuerCapacity();
		elements[size++] = s;
	}
	
	public Object pop(){
		if (size == 0) {
			throw new EmptyStackException();
		}
		
		return elements[--size];
	}
	
	private void ensuerCapacity(){
		if (elements.length == size) {
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}
}

? ? ? 这段程序中并没有很明显的错误,无论如何测试,他都会成功通过每一次测试,但是这个程序中隐含这样一个问题。不严格的讲,这段程序有一个“内存泄漏”,随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。在极端的情况下,这种内存泄漏导致磁盘交换,甚至导致程序失败,但是这种失败情形相对比较少见。

?

?

? ? ?那么,程序中那里发生了内存泄漏呢?如果一个栈先是增长,然后在收缩,那么,从栈中弹出来的对象将不会当作垃圾回收,即使使用栈的程序不再引用这个对象了,他们也不会被回收。这是因为,栈内部维护这这些对象的过期引用(obsolete reference)。所谓的过期引用,就是指永远不会被解除的引用。在本例中,凡是在elements数组的“活动部分”之外的任何引用都是过期引用。活动部分是指elements中下标小于size的那些元素。

?

在支持垃圾回收的语言中,内存泄漏是很隐藏的(称这类内存泄漏为“无意识的对象保持(unintentional object retention)” 更为恰当)。如果一个对引用被无意识的保留起来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理这个被这个对象对象所引用的所有他们对象。即使只有少量的几个对象引用被无意识的保留下来,也会有许许多多的对象被排斥在垃圾回收机制之外,从而对性能在造成潜在的中大影响。

?

? ? ? 这类问题的修复方法很简单:一旦对象的引用已经过期,只需要清空这些引用即可。对于上述的例子中的Stack类而言,只要一个单元被弹出栈,指向他的引用就过期了。pop方法的修订版本如下所示:

?

	public Object pop(){
		if (size == 0) {
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		elements[size] = null;//消除过时的引用
		return elements[--size];
	}

? ? ? ?清空过时的引用的另一个好处是,如果他们以后又被错误的解除引用,程序就会立即抛出NullPointerException异常,而不是悄悄的错误运行下去。尽快的检测出程序中的错误总是有益的。

?

? ? ? 一般当第一次被这样的问题困扰的时候,就会往往过份的小心:对于每一个对象的引用,一旦程序不再用到它,就会把它清空。其实这样做既没有必要,也不是我们所期待的,因为,这样做会把程序弄的很乱。清空对象引用应该是一种列外,而不是一种规范行为。消除过期引用的最好办法是让包含该引用变量结束声明周期。如果你是在最紧凑的作用域范围内定义的每一个变量,这种情形就会自然而然的发生。

?

? ? ? ?那么何时应该清空引用呢?Stack类的那些方面特性使它易于遭受内存泄漏的影响呢?简而言之,问题在于,Stack类自己管理内存。存储池包含elements数组(对象引用单元;而不是对象本身)的元素。数组活动区域(同前面的定义)中的元素是已分配的,而数组其余部分的元素则是自由的。但是垃圾回收期并不知道这一点,对于垃圾回收器而言,elements数组中的所有随想引用都等同有效,只有程序员知道数组中的非活动部分是不重要的。程序员可以把这个情况告知垃圾回收器,做法很简单:一旦数组元素变成了非活动部分的一部分,程序员就手工清空这些数组元素。

?

? ? ? 一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。

?

? ? ? 内存泄漏的另一个常见的来源是缓存。一旦你把对象引用放到了缓存中,他就很容易被遗忘掉。从而使得他不再有用之后很长一段时间内仍在缓存中。对于这个问题,有几种可能解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存,当缓存中的项过期之后,他们就会自动被删除,记住只有当所有的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。

?

? ? ? 更为常见的情形则是,“缓存项声明周期是否有意义”并不是很容易确定,随着时间的退役,其中的项变得越来越没有简直。在这种情况下,缓存应该时不时的清除掉没用的项。这项清除工作由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便进行清理。LinkedHashMap类利用他的removeEldedtEntry方法可以很容易的实现后一种方案。对于更加复杂的缓存,必须直接使用java,lang,ref。

?

? ? ? 内存泄漏的第三和常见来源是监听器和其它回调。如果你实现了一个API,客户端在这个API中注册回调,却没有显式的取消注册,那么除非你采取某些动作,否则他们就会积聚。确保回调立即被当作垃圾回收的最佳方法是保存他们的弱引用,例如,只将他们保存成WeakHashMap中的键。

?

? ? ? 由于内存泄漏通常不会表现成明显的失败,所以他们可以在一个系统中存在很多年,往往只有通过自己检查代码,或者借助于Heap剖析工具才能发现内存泄漏问题,因此,如果能够在内存泄漏发生之前就知道如何预测此类问题,并阻止它们发生,那是最好不过的了

?

发表评论
用户名: 匿名