美文网首页
非静态内部类导致内存泄露问题详解

非静态内部类导致内存泄露问题详解

作者: stevewang | 来源:发表于2021-08-04 16:54 被阅读0次

内存泄露一直是Java开发中需要避免的问题,也是面试时经常考察的问题。
使用非静态内部类是日常开发中最容易产生内存泄露的场景,本文主要探讨为什么使用非静态内部类可能产生内存泄露以及如何解决此类问题。

非静态内部类持有外部类的引用

要理解为什么使用非静态内部类可能产生内存泄露,首先要知道非静态内部类是隐式持有外部类引用的。看下面这个Outter类,它有一个非静态内部类Inner:

public class Outter {

    private static String TAG = "Outter";

    private class Inner {
        public Inner() {
            System.out.println("Inner Constructor: " + TAG);
        }
    }
}

现在我们把这个类编译一下

$ javac Outter.java
$ ls *.class
Outter$Inner.class Outter.class

编译产物中Outter$Inner.class就是这个非静态内部类的字节码文件,接下来我们分析一下它:

$ javap -c Outter$Inner.class
Compiled from "Outter.java"
class Outter$Inner {
  final Outter this$0;

  public Outter$Inner(Outter);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LOutter;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: new           #4                  // class java/lang/StringBuilder
      15: dup
      16: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      19: ldc           #6                  // String Inner Constructor:
      21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokestatic  #8                  // Method Outter.access$000:()Ljava/lang/String;
      27: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      33: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: return
}

关注字节码中的putfield这一行,表示将外部类Outter对象的引用赋值给了匿名内部类中的this$0,即非静态内部类持有了外部类的引用。

通过同样的方式分析静态内部类的字节码文件,我们可以发现静态内部类并不持有外部类的引用,所以它只能引用的外部类的静态成员,因为静态成员被存放在JVM内存模型的Method Area,可以直接被引用到。

Android Handler可能导致的内存泄露

Android开发中产生内存泄露的最典型场景就是在Activity里创建一个非静态Handler实例来处理线程间通信:

Android Handler可能导致的内存泄露

上图定义的继承于Handler的匿名内部类是非静态的,尽管它内部并未显式持有任何对象的引用,lint依然提示如下警告:

This Handler class should be static or leaks might occur

意思就是这个继承于Handler的匿名内部类应该被定义为static,否则可能产生内存泄露。

通过前面的介绍我们知道,非静态内部类会持有外部类的引用,因此上述继承于Handler的匿名内部类隐式持有了Activity对象的引用,如果此时通过mHandler.sendMessageDelayed(new Message(), 5000);延时5s发送消息,然后立刻退出Activity,这5s内Activity对象将无法被GC回收,也就出现了内存泄露。

此外,下面这种用法也会造成内存泄露,因为实现了Runnable接口的匿名内部类持有外部类的引用:

mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        // do something
    }
}, 5000);

那么如何解决这种内存泄露呢?参考lint给我们的提示,我们需要把上述用到的内部类定义成static,这样它就不再隐式持有外部类的引用;除此之外,如果内部类需要显式持有外部类成员引用以完成某种操作,必须通过弱引用(WeakReference)实现,这样当这些外部类成员所依赖的上下文被回收时,由于只具有弱引用,GC工作时就会将他们回收,从而避免了内存泄露。

特别的,针对Activity中创建非静态Handler处理线程间通信导致内存泄露的场景,更推荐在onDestroy()中调用mHandler.removeCallbacksAndMessages(null)解决

相关文章

  • Android常见内存泄漏汇总

    目录:一、内存泄漏介绍二、常见内存泄漏场景1.单例导致内存泄露2.静态变量导致内存泄漏3.非静态内部类导致内存泄露...

  • Android面试:为什么Handler容易造成内存泄露?

    非静态内部类造成的内存泄露 原因:非静态内部类会持有外部类对象的引用。 结合代码及Profiler查看内存泄露情况...

  • 非静态内部类导致内存泄露问题详解

    内存泄露一直是Java开发中需要避免的问题,也是面试时经常考察的问题。使用非静态内部类是日常开发中最容易产生内存泄...

  • 如何减少Android中存在的内存泄露

    Java细节:1. 非静态内部类会持有外部类的隐式引用。容易导致内存泄露,解决方法:改为静态内部类。2. 这样会有...

  • Android常见内存泄漏

    内存泄漏场景: 静态变量引用非静态内部类/匿名类实例。子线程相关的非静态内部类/匿名内部类引用。Handler导致...

  • Android内存优化——常见内存泄露及优化方案

    内存泄漏情况分类 1、单例模式导致内存泄漏 2、静态变量导致内存泄漏 3、非静态内部类导致的内存泄漏 4、未取消注...

  • Android非静态内部类导致的内存泄露

    从.class文件分析非静态内部类和静态内部类的区别我们看一个例子就明白了.public class OuterC...

  • 内存优化

    内存泄露的原因 四种引用类型 常见的内存泄露 1.内部类导致内存泄露 Handler 2.Context导致内存泄...

  • Java学习——内部类

    内部类 一,成员内部类(包括静态内部类和非静态内部类) 非静态内部类可以直接访问外部类的成员,反之则不行 非静态内...

  • 关于静态内部类

    静态内部类与非静态内部类的区别 非静态内部类会隐式持有外部类的引用,可能引起内存泄漏。 静态内部类只能调用外部类的...

网友评论

      本文标题:非静态内部类导致内存泄露问题详解

      本文链接:https://www.haomeiwen.com/subject/kwecvltx.html