一叶笑天
雄关漫道真如铁, 而今迈步从头越。 从头越, 苍山如海, 残阳如血。
posts - 73,comments - 7,trackbacks - 0
Java语言中给人的印象是不需要考虑内存管理,但是这种理解是不正确的,看下面的简单stack实现代码:
 1// Can you spot the "memory leak"?
 2public class Stack {
 3    private Object[] elements;
 4    private int size = 0;
 5    private static final int DEFAULT_INITIAL_CAPACITY = 16;
 6
 7    public Stack() {
 8        elements = new Object[DEFAULT_INITIAL_CAPACITY];
 9    }

10
11    public void push(Object e) {
12        ensureCapacity();
13        elements[size++= e;
14    }

15
16    public Object pop() {
17        if (size == 0)
18            throw new EmptyStackException();
19        return elements[--size];
20    }

21
22    /**
23     * Ensure space for at least one more element, roughly doubling the capacity
24     * each time the array needs to grow.
25     */

26    private void ensureCapacity() {
27        if (elements.length == size)
28            elements = Arrays.copyOf(elements, 2 * size + 1);
29    }

30}
        这段代码看上去没有什么错,每次测试的时候都可以成功。但是这里有一个潜在的问题,泛义的讲就是程序存在内存泄漏,由于不断增加的垃圾回收器活动或者增加的内存访问,而使性能降低被暴露出来。极端情况下,这种内存泄漏会导致磁盘页面访问甚至程序出现OutOfMemoryError的错误。但是这种错误是很少出现的。
      当stack增长和收缩时,即使程序不再使用它们,出栈的对象不会被垃圾收集。由于stack维护着这些对象的过时的引用。这种过时的引用是一种不再被重新引用的简单引用。在这种情况下,任何出了活动区的元素引用都是过时的。活动区包含着一个小于size的元素index。
        具有垃圾回收器的语言,也称为无意的对象保持,内存泄漏是潜在的。如果一个对象是无意的保持,不仅垃圾回收器不能包含他们,而且其他任何对象也不能引用它们。
       修改这种错误的方法很简单,一旦他们不再使用,设置它的引用为null。例如stack类,当元素出栈后就会变成不再使用的对性。正确的pop函数应该这样写:
1public Object pop() {
2if (size == 0)
3throw new EmptyStackException();
4Object result = elements[--size];
5elements[size] = null// Eliminate obsolete reference
6return result;
7}
     设置过时的对象引用为null的另外一个好处是,如果它们被错误的重新引用,程序会立即抛出NullPointerException,而不是静静的做错误的事情。
     当程序员第一次遇到这种问题后,他们可能会过度在每个对象引用后设置为null。这是不需要而且不可取的。设置对象引用为null应该是一个例外,而不是一个准则。消除这种过时对象引用的最好方法是让包含引用的变量在范围外访问时发生错误。
       设置引用为null的时机是什么?Stack的的那些方面容易遭受内存泄漏?简单的说就是管理自己的内存。存储池保持着一个元素列表,这个列表中处于活动区的元素是被分配的。列表中剩余的元素是空闲的。垃圾回收器不知道这些。对于垃圾回收器,所有列表中的元素都是有效的。只有程序员知道列表中不活动的部分是不重要的。程序员只有设置不活动的部分为null时,垃圾回收器才能感知到。
    总的来说,任何时候,当一个类管理自己的内存,程序员应该警惕内存泄漏。当元素释放的时候,包含元素的对象引用应该设置为null。
    另外一个内存泄漏的例子是缓存cache。当你把一个对象引用放进cache,很容易遗忘掉在它变为不相关的时候它还在cache中。解决这种问题的方法是将cache表示成为一个WeakHashMap,入口点将会在他们过时的时候自动删除。记住,WeakHashMap只在当一个期望的cache入口在被外面的引用到key,而不是值的时候才是有用的。
     更为常见的是,cache的缺少好的有用生命周期定义。在这种情况下,cache应该在当entry没有用的时候清除它们。可以通过后台线程(Timer或者ScheduledThreadPoolExecutor)或者as a side effect of adding new entries to the cache来实现。LinkedHashMap类用removeEldestEntry方法实现了后者方式。对于好的cache,应该直接使用java.lang.ref。
     第三种常见的内存泄漏的例子就是监听器和一些回调listeners and other callbacks.如果你实现一个API,客户注册回调但是不显式的注销回调,它们就会堆积起来,除非你采取措施。确保回调被垃圾回收的最好方式是存储weak reference给它们,例如将它们仅仅作为key存储在WeakHashMap中。
     因为内存泄漏是典型的没有明显的错误特征,它们可能在一个系统中保持数年。但是它们可以被仔细的代码检查或者heap profiler这样的调试工具发现。因此,需要预知这些问题而且避免它们的发生。

posted on 2008-06-23 10:53 一叶笑天 阅读(377) 评论(0)  编辑  收藏 所属分类: JAVA技术

只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问