• Java中常见的内存泄漏例子


      Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存。理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同。

    JAVA 中的内存泄露

      Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。
      在C++中,所有被分配了内存的对象,不再使用后,都必须程序员手动的释放他们。所以,每个类,都会含有一个析构函数,作用就是完成清理工作,如果我们忘记了某些对象的释放,就会造成内存泄露。
      但是在Java中,我们不用(也没办法)自己释放内存,无用的对象由GC自动清理,这也极大的简化了我们的编程工作。但,实际有时候一些不再会被使用的对象,在GC看来不能被释放,就会造成内存泄露。
      我们知道,对象都是有生命周期的,有的长,有的短,如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。我们举一个简单的例子:

    public class Simple {
        Object object;
        public void method1(){
            object = new Object();
            //...其他代码
        }
    }
    

      这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量。当然,如果一定要这么写,可以改为这样:

    public class Simple {
        Object object;
        public void method1(){
            object = new Object();
            //...其他代码
            object = null;
        }
    }
    

    一些容易发生内存泄露的例子和解决方法

      像上面例子中的情况很容易发生,也是我们最容易忽略并引发内存泄露的情况,解决的原则就是尽量减小对象的作用域(比如android studio中,上面的代码就会发出警告,并给出的建议是将类的成员变量改写为方法内的局部变量)以及手动设置null值。

    LinkedList源码举例

      至于作用域,需要在我们编写代码时多注意;null值的手动设置,我们可以看一下Java容器LinkedList源码(可参考:Java之LinkedList源码解读(JDK 1.8))的删除指定节点的内部方法:

    //删除指定节点并返回被删除的元素值
    E unlink(Node<E> x) {
        //获取当前值和前后节点
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        if (prev == null) {
            first = next; //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
        } else {
            prev.next = next;//如果前一个节点不为空,那么他先后指向当前的下一个节点
            x.prev = null;
        }
        if (next == null) {
            last = prev; //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
        } else {
            next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }
    

      除了修改节点间的关联关系,我们还要做的就是赋值为null的操作,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象。

    ArrayList源码举例

      我们知道Java容器ArrayList是数组实现的(可参考:Java之ArrayList源码解读(JDK 1.8)),如果我们要为其写一个pop()(弹出)方法,可能会是这样:

    public E pop(){
        if(size == 0)
            return null;
        else
            return (E) elementData[--size];
    }
    

      写法很简洁,但这里却会造成内存溢出:elementData[size-1]依然持有E类型对象的引用,并且暂时不能被GC回收。我们可以如下修改:

    public E pop(){
        if(size == 0)
            return null;
        else{
            E e = (E) elementData[--size];
            elementData[size] = null;
            return e;
        }
    }
    

      我们写代码并不能一味的追求简洁,首要是保证其正确性。

  • 相关阅读:
    [置顶] 寻找数组中的值
    详解 Java 的八大基本类型,写得非常好!
    从入门到放弃的 Java 架构师面试题!
    通往大牛之路,百度Java面试题前200页!
    HTML与CSS简单页面效果实例
    JS面向对象
    JS瀑布流效果
    CSS常用操作-图片
    CSS常用操作-导航栏
    CSS常用操作-对齐
  • 原文地址:https://www.cnblogs.com/flyingrun/p/13719018.html
Copyright © 2020-2023  润新知