记一次匿名内部类导致的内存泄露

914 阅读1分钟

内存泄露一般是由于对象的引用一直被持有,导致 GC 的时候无法被回收。有一种内存泄露是匿名内部类导致的。我们在工作中经常会写下如下的代码:

public class Demo {
    
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            xxxx.....
        }
    };
    
    xxx.setCallBack(new CallBack(){
     @Override
        public void call() {
            xxxx.....
        }
    })

}

上面这两中直接实例化接口的写法,实际上都是实例化的匿名内部类,即实例化了一个匿名的接口实现类,再向上转型成接口类型。这个不是问题的关键。问题的关键是如果这个匿名内部类实例化出的对象被其他对象长期持有或者被缓存。会造成内存泄露。原因是匿名内部类实例化出的对象会默认持有外部类(Demo)的对象。如果Demo 对象的生命周期已经走完。但是 mRunnable或者 CallBack 对象被缓存。这样会导致 Demo 对象无法被回收,从而内存泄露。

可以使用反射来获取匿名内部类对象持有的外部类对象:

     private void test() {
            Callback callback = new Callback() {
                @Override
                public void callback(String s) {
                    System.out.println(getName());
                }
            };
            callback.callback("");
            Class clazz = callback.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field.getName());
                try {
                    System.out.println(field.get(callback));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

控制台输出:

    Main
    this$0
    com.aesean.clazz.Main@330bedb4

那么我们应该怎样在使用匿名内部类的时候避免出现内存溢出呢?可以将匿名内部类改成静态匿名内部类,静态内部类会随着外部类的加载而实例化,所以不会持有外部类的引用了。 即:

public class Demo {
    
    private static Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            xxxx.....
        }
    };