如何避免内存泄漏

Posted 史红星

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何避免内存泄漏相关的知识,希望对你有一定的参考价值。

本来这一章应该是介绍与GC相关的内容,不过在此之前,我准备先和各位探讨一下一个编程的小技巧。当然,这个小技巧其实也是与GC密切相关的。

不知道各位猿友有没看过一些JAVA内存相关的文章,里面在罗列建议的时候,经常会写出这样一条建议。

第XX条、请在使用完对象之后,显示的将对象设置为null。

原话不一定如此,但意思是一样的。话里所描述的意思,就是说我们以后写代码应该这么写。

Object obj = new Object();  
//to do something   
obj = null;  

这段代码有点C/C++的风格,obj=null代替了C/C++中的delete obj或者是free(obj),相当于在提示我们,就算有了GC,我们也要像没有GC一样,使用完对象就得将其赋为空值。

首先,LZ这里要说明的是,将obj赋为空值,与C/C++中的delete其实有着天壤之别,LZ之所以说它们代替了delete和free,仅仅是因为它们出现在代码中的位置类似而已。

 obj=null只做了一件事,就是将obj这个引用变量与new Object()创造的实例的关联断开,实际上,实例所占用的内存空间依然没有释放。

这里解释一下为何在有些时候不将对象赋为空值会造成内存泄露,我们考虑下面一段代码。

import java.util.Arrays;

public class Stack {
    
    private static final int INIT_SIZE = 10;

    private Object[] datas;
    
    private int size;

    public Stack() {
        super();
        datas = new Object[INIT_SIZE];
    }
    
    public void push(Object data){
        if (size == datas.length) {
            extend();
        }
        datas[size++] = data;
    }
    
    public Object pop(){
        if (size == 0) {
            throw new IndexOutOfBoundsException("size is zero");
        }
        return datas[--size];
    }
    
    private void extend(){
        datas = Arrays.copyOf(datas, 2 * size + 1);
    }
    
}

  这段代码是一个简单的长度可伸缩的栈实现,在你写一段测试代码去测试它的时候,会发现它毫无问题。但是不好意思,这里面就有一个明显的“内存泄露”,这就是因为一些对象或者说引用没有设置为空值所造成的。       

          如果你还没看出来,LZ稍微提示一下,各位就会意识到了。这段代码里面,数组里的对象只会无限增长,而不会随着栈中元素的弹出而减少,减小的仅仅是对外展示的栈的大小size。考虑一个极端的场景,假设你曾经往栈中放入了100万个对象,最后使用了99万9千9百9十9个,从外部看来,栈中还只剩一个可用对象了,但是我们的栈依然持有着100万个对象的引用。如果JVM可以活过来的话,想必一定会把你蹂躏到死的。

          我们缺少的就是将对象赋为null值的那一步,所以pop方法应该改为下面的方式,而且各位可以去看一下ArrayList中remove方法的源码,也是类似的思路。

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
 /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

这个内存泄露是非常隐蔽的,但是对于我们做集合类的删除时候就要充分给gc很好可以回收的代码,这样的代码就是优雅的代码。

 

以上是关于如何避免内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

您如何检测/避免(非托管)代码中的内存泄漏? [关闭]

如何避免本机反应中的内存泄漏?

如何避免内部类中的内存泄漏

Kotlin是如何帮助你避免内存泄漏的?

如何避免内存泄漏

彻底搞清楚内存泄漏的原因,如何避免内存泄漏,如何定位内存泄漏