第六条:消除过期的对象引用 1.当你从手工管理内存的语言,如C或者C++转换到具有垃圾回收功能的语言的时候,程序员的工作会变得更加容易,因为当你用完了对象之后,他们会被自动回收。当你认为自己不需要在考虑内存管理的事情时,其实则不然: 2.参见Stack:消除过期的对象引用的一个例子,非泛型版本 * <pre> * 1.不严格的讲,这段程序有一个内存泄露,随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。 * 2.在极端的情况下,这种内存泄露会导致磁盘交换,Disk paging,甚至导致程序失败OutOfMemoryError,不过这种失败情形相对比较少见 * 3.如果一个栈先是增长然后再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为栈内部维护着这些对象的过期引用obsolete reference.所谓的过期引用,是指永远也不会再被解除的引用。在本例中,凡是elements数组中的活动部分 * active portion之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素.因为被弹出栈的元素,即大于size的且依然保存在elements * 数组中的不会被回收. * </pre> 3.在支持垃圾回收的语言中,内存泄露是很隐藏的。称该类内存泄露为无意识的对象保持unintentional object retention更为恰当。如果一个对象引用被无意识的保留起来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象。即使只有少量的几个对象引用被无意识的保留下来,也会有许许多多的地方呗排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。 4.修复方法很简单:一旦对象引用已经过期,只需清空这些引用即可。对于该例,只要一个单元被弹出栈指向它的引用就过期了。 5.清空过期引用的另一个好处是,如果他们以后又被错误的解除引用,程序就会立刻抛出NullPointerException异常,而不是悄悄的错误运行下去。尽快的检测出程序中的错误总是有益的。 6.清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用最好的办法是让包含该引用的变量结束其生命周期。如果你是最紧凑的作用域范围内定义一个变量,这种情形就会自然而然的发生。 7.Stack之所以易于遭受内存泄露的影响,问题在于Stack自己管理内存manage its own memory.存储池storage pool包含了elments数组,对象引用单元而不是对象本身。的元素。数组活动区域中的元素是已分配的,而数组其余部分的元素则是自由的。但是垃圾回收期并不知道这一点,对于其而言,elements数组中的所有对象引用都同等有效。只有程序员知道数组的非活动部分是不重要的,程序员可以把这个情况告诉垃圾回收器。做法很简单,一旦数组元素变成了非活动部分的一部分,程序员就手动清空这些数组元素。一般而言,只要类时自己管理内存,程序员就应该警惕内存泄露问题。一旦元素被释放掉,则该元素中包含的任何对象引用都 应该被清空。 8.内存泄露的另一个常见来源是缓存。一旦你把对象放到缓存中,它就很容易被遗忘掉,而从使得他不再有用之后很长一段时间内仍然留在缓存中。对于这个问题,有几种可能解决的方案。如果你正要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义。那么就可以用WeakHashMap代表缓存,当缓存中的项过期之后,他们就会自动被删除。记住只有当所要的缓存项的生命周期是由该键的外部引用而不是用值决定时,WeakHashMap才有用处。 9.更为常见的情形则是:缓存的声明周期是否有意义并不是很容易确定。随着时间的推移,其中的项会变得越来越没有价值。在这种情况下,缓存应该时不时的清除没用的项。这项清除工作可以由一个后台线程可能是Timer或者ScheduledThreadPoolExecutor来完成或者也可以给缓存添加新条目的时候顺便清理。LinkedHashMap类利用它的removeElderestEntry方法可以很容易的实现后一种方案.(注:LRU),对于更加复杂的缓存,必须直接使用java.lang.ref. 10.内存泄露的第三个常见来源是监听器和其他回调。如果你实现了一个API,client在这个API中注册回调,却没有显示的取消注册。那么除非你采取某些动作,否则他们就会积聚。确保回调立即被当做垃圾回收的最佳方式是只保存他们的弱引用weak reference。例如,至将他们保存成WeakHashMap中的键。 11.由于内存泄露通常不会表现为明显的失败,所以他们可以在一个系统中存很多年。往往只有通过仔细检查代码或者借助于Heap剖析工具Heap Profiler才能发现内存泄露问题。因此,如果能够在内存泄露之前就知道如何预测此问题的话并阻止他们发生,那最好不过部分源码:package com.book.chap2.clearOverdueRef;
import java.util.Arrays;
import java.util.EmptyStackException;
/** *//**
*
* 消除过期的对象引用的一个例子,非泛型版本
* <pre>
* 1.不严格的讲,这段程序有一个内存泄露,随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。
* 2.在极端的情况下,这种内存泄露会导致磁盘交换,Disk paging,甚至导致程序失败OutOfMemoryError,不过这种失败情形相对比较少见
* 3.如果一个栈先是增长然后再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为栈内部
* 维护着这些对象的过期引用obsolete reference.所谓的过期引用,是指永远也不会再被解除的引用。在本例中,凡是elements数组中的活动部分
* active portion之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素.因为被弹出栈的元素,即大于size的且依然保存在elements
* 数组中的不会被回收.
* </pre>
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-3-12
*
*/
public class Stack
{
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 10;
public Stack()
{
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
/** *//**
* 入栈
* @param e
*/
public void push(Object e)
{
ensureCapacity();
elements[size++] = e;
}
/** *//**
* 弹栈
* @return
*/
public Object pop()
{
if(size == 0)
{
throw new EmptyStackException();
}
return elements[--size];
}
/** *//**
* 修复pop:一旦对象引用已经过期,只需清空这些引用即可。对于该例,只要一个单元被弹出栈指向它的引用就过期了。
* @return
*/
public Object gcPop()
{
if(size == 0)
{
throw new EmptyStackException();
}
Object result = elements[--size];
//置null,移除过期引用
elements[size] = null;
return result;
}
/** *//**
* 超出空间后,扩展空间
*/
public void e nsureCapacity()
{
if(elements.length == size)
{
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
posted on 2013-03-15 19:15
landon 阅读(1825)
评论(0) 编辑 收藏 所属分类:
Program 、
Book