从一个栈引出的内存泄露问题

我记得在有一次面试中,面试官问我自己实现的一个栈中会不会有内存泄露的问题,我努力搜索可能的问题,就是感受不到可能出现的问题。当时忽然意识到,内存泄露这个问题一直被我忽略,因为用的是java/C#,这些语言中都有内存自动回收的机制,我突然发现自己对这个问题竟然一无所知。面试中的栈就是下面这个:

// 你能检查出"内存泄露"吗?
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 e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * 保证栈能自动增长,当栈中空间不足时,自动增长为原长度的两倍
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

这段程序不管你怎么测试都是没有问题的,但是他确实可能引起“内存泄露”。定位到pop()函数,在return语句中,当我们弹出一个元素时,只是简单的让栈顶指针(size)-1。逻辑上,栈中的这个元素已经弹出,已经没有用了。但是事实上,被弹出的元素依然存在于elements数组中,它依然被elements数组所引用,GC是无法回收被引用着的对象的。也许你期望等这整个栈失去引用(将被GC回收时),栈内的elements数组一起被GC回收。但是实际的使用过程中,又有谁能够预料到这个栈会存活多长时间。为了保险起见,我们需要在弹出一个元素的时候,就让这个元素失去引用,便于GC回收。我们只需要让Pop()函数弹出时,同时解除对弹出元素的引用即可。

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

从上面的例子中,我们可以发现当类中持有过期的元素的引用时,就有可能造成内存泄露问题。而且通常这种内存泄露问题都是我们无意识造成的,上面的栈中,逻辑上我们认为弹出的元素就应该被GC回收掉,但事实上GC没有办法回收,因此elements数组依然持有它。这种问题很隐蔽,通常只要类自己管理内存(如类中有一个Array或List型的结构),那么我们就应该警惕内存泄露的问题。

 

内存泄露来源及解决

    内存泄露可能来源于缓存。我们为了让下次的程序的处理速度更快,常常需要将一些信息缓存在内存中,但是这些过期的缓存又很容易被遗忘,从而使得它不再有用之后很长一段时间内仍然留在缓存中。例如像一个要显示图片墙的程序,我们需要缓存图片和相关的信息,为了方便GC回收过期的缓存,我们可以使用WeakHashMap来实现缓存,当界面显示图片的时候,界面持有相关图片的引用,这些引用同时也存在于WeakHashMap中。而其他不被界面持有的过期缓存,则WeakHashMap会自动将这些剔除。

    总的说来,只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存;当缓存中的项过期之后,它们就自动被删除。记住只有当所有的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。

    更为常见的情景则是,"缓存项的生命周期是否有意义"并不是非常容易确定,随着时间推移,其中的项会变得越来越没有价值。在这种情况下,缓存应该是不是地清除掉没有的项。这项清除工作可以由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便进行清理。LinkedHashMap类利用它的removeEldestEntry方法可以很容易地实现后一种方案。对于更加复杂的缓存,必须直接使用java.lang.ref. 
    内存泄露的第三个常见来源是监听器和其他回调。如果你实现了一个API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非你采取某些动作,否则他们就会积聚。确保回调立即被当成垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将它们保存成WeakHashMap中的键。

 

部分文字直接截取自《Effective Java》

作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/kissazi2/p/3618464.html

时间: 2024-10-02 05:10:17

从一个栈引出的内存泄露问题的相关文章

C++关于一个函数中new内存泄露的列子

首先明白几个基础 1.函数按值传递和按值返回的时候都会调用复制构造函数 2.一般在函数体内定义的栈变量是不能返回其地址或者引用给主调函数的,因为在函数结束的时候这些栈变量将释放 3.可以使用new的方式建立堆内存的方式,然后返回引用或者指针,因为new这种方式建立的堆内存并不随函数的结束而结束,      而指针变量释放但是指针本生的值已经返回.同时也可以按值放回,但是这种情况下将可能出现内存泄露 来看下面的代码 点击(此处)折叠或打开 /****************************

用java创建一个内存泄露的步骤?

问题: 我之前参加了一个面试, 被问到在java中如何创建一个内存泄露.不用说我当时不知道说啥,如何创建一个,我到现在也没有头绪.可以给我示范一个例子么? 回答: 有一个方式可以创建一个纯Java的内存泄露(运行代码中对象不可达,但仍然驻留在内存里) 1. 应用创建了一个长时间运行的线程(或者使用线程池,这会使内存泄露更快)2.线程从类加载器加载一个类3. 这个类分配一个大内存块(例如new byte[1000000]) ,把它通过强引用指向一个静态成员变量,然后把它自己的引用存储到Thread

PHP CURL 内存泄露问题解决方法

这篇文章主要介绍了PHP CURL 内存泄露问题解决方法,CRUL长时间访问HTTPS网站时有内存泄露问题,本文经过反复调试找到了解决方法,需要的朋友可以参考下 phpcurl使用privoxy代理访问https://www.google.com/search?q=xxx curl配置平淡无奇,长时间运行发现一个严重问题,内存泄露!不论用单线程和多线程都无法避免!是curl访问https站点的时候有bug! 内存泄露可以通过linux的top命令发现,使用php函数memory_get_usag

Android应用内存泄露分析、改善经验总结

前言 通过这几天对好几个应用的内存泄露检测和改善,效果明显: 完全退出应用时,手动触发GC,从原来占有内存100多M降到低于20M: 手动触发GC后,通过adb shell dumpsys meminfo packagename -d查看Activity和View的数量也趋近于0了(没有做到归零是因为SDK中存在内存泄露,需要中间层去处理): 发现了一个SDK中的内存泄露(Android InputMethodManager 导致的内存泄露及解决方案): 发现一个MTK Webview的内存泄露

android的GC内存泄露问题_Android

1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

PHP CURL 内存泄露问题解决方法_php实例

phpcurl使用privoxy代理访问https://www.google.com/search?q=xxx curl配置平淡无奇,长时间运行发现一个严重问题,内存泄露!不论用单线程和多线程都无法避免!是curl访问https站点的时候有bug! 内存泄露可以通过linux的top命令发现,使用php函数memory_get_usage()不会发现. 经过反复调试找到解决办法,curl配置添加如下几项解决问题: 复制代码 代码如下: [CURLOPT_HTTPPROXYTUNNEL] = tr

android的GC内存泄露问题

1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

一个笛卡尔积的update from引发的问题(内存泄露?)

场景 PostgreSQL支持多表JOIN的更新操作,但是如果SQL没有写好,可能会导致出现笛卡尔积的情况. 如果是条查询语句,出现笛卡尔积时,没什么问题,大不了就是查询慢一点. 如果是条更新语句,现在看来可能代码中有内存泄露的BUG,已反馈给PG社区. 另外需要注意PostgreSQL不允许自关联的更新,但实际上使用别名可以规避这个语法错误 如果业务确实有自关联的更新操作需求,可以使用别名的方法. postgres=# explain (analyze,verbose,timing,buffe

关于js内存泄露的一个好例子_javascript技巧

我把别人的例子改了一下,觉得这样写更紧凑!套用别人的原话,当一个DOM对象包含一个Js对象的引用(例如一个Event Handler), 而这个Js对象又持有对这个DOM对象的引用时,一个环状引用就行成了,于是在ie下就出现了内存泄露.点击"运行代码"并打开任务管理器看内存变化.分别在ie8和ff下测试,差距不用多说. 运行代码 复制代码 代码如下: <html>  <head>    <title>Memory leak</title>