开发者

Java匿名内部类导致内存泄露的原因与解决方案详解

开发者 https://www.devze.com 2022-11-29 12:07 出处:网络 作者: IT利刃出鞘
目录简介为什么要持有外部类实例:持有外部类代码编译查看class查看字节码什么时候会内存泄露不会内存泄漏的方案方案1:不返回内部类对象引用方案2:匿名内部类改为静态的简介说明本文用示例介绍匿名内部类会...
目录
  • 简介
  • 为什么要持有外部类
  • 实例:持有外部类
    • 代码
    • 编译查看class
    • 查看字节码
  • 什么时候会内存泄露
    • 不会内存泄漏的方案
      • 方案1:不返回内部类对象引用
      • 方案2:匿名内部类改为静态的

    简介

    说明

    本文用示例介绍匿名内部类会导致内存泄漏的原因及其解决方案。

    相关网址

    普通内部类的内存泄露:Java内部类持有外部类导致内存泄露--原因/解决方案

    为什么要持有外部类

    Java 语言中,非静态内部类的主要作用有两个:

    当匿名内部类只在外部类(主类)中使用时,匿名内部类可以让外部不知道它的存在,从而减少了代码的维护工作。

    当匿名内部类持有外部类时,它就可以直接使用外部类中的变量了,这样可以很方便的完成调用,如下代码所示:

    package org.example.a;
     
    import java.util.ArrayList;
    import java.util.List;
     
    public class Demo {
        private static String name = "Tony";
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>() {{
                add("a");
                add("b");
                add(name);
            }};
            System.out.println(list);
        }
    }

    实例:持有外部类

    代码

    package org.example.a;
     
    import java.util.ArrayList;
    import java.util.List;
     
    class Test{
        public List<String> createList() {
            List<String> list = new ArrayList<String>() {{
                add(编程"a");
                add("b");
            }};
            return list;
        }
    }
     
    public class Demo {
        public static void main(String[] args) {
            System.out.println(new Test().createList());
        }
    }

    编译查看class

    命令:javac Demo.java

    结果:

    Java匿名内部类导致内存泄露的原因与解决方案详解

    Idea查看Test$1.class(可以发现:持有了一个外部类Test对象)

    package org.example.a;
     
    import java.util.ArrayList;
     
    class Test$1 extends ArrayList<String> {
        Test$1(Test var1) {
            this.this$0 = var1;
            this.add("a");
            this.add("b");
        }
    }
    

    Idea查看Test.class

    package org.example.a;
     
    import java.util.ArrayList;
    import java.util.List;
     
    class Test {
        Test() {
        }
     
        public List<String> createList() {
            ArrayList var1 = new ArrayList<String>() {
                {
                    this.add("a");
                    this.add("b");
                }
            };
            return var1;
        }
    }

    Idea查看Demo.class

    package org.example.a;
     
    public class Demo {
        public Demo() {
        }
     
        public static void main(String[] var0) {
            System.out.println((new Test()).createList());
        }
    }
    

    编程客栈看字节码

    命令

    javap -c Test$1.class

    结果

    Compiled from "Demo.java"

    class org.example.a.Test$1 extends java.util.ArrayList<java.lang.String> {

      final org.example.a.Test this$0;

     

      org.example.a.Test$1(org开发者_Python入门.example.a.Test);

        Code:

           0: aload_0

           1: aload_1

           2: putfield      #1                  // Field this$0:Lorg/example/a/Test;

           5: aload_0

           6: invokespecial #2                  // Method java/util/ArrayList."<init>":()V

           9: aload_0

          10: ldc           #3                  // String a

          12: invokevirtual #4                  // Method add:(Ljava/lang/Object;)Z

          15: pop

          16: aload_0

          17: ldc           #5                  // String b

          19: invokevirtual #4                  // Method add:(Ljajsva/lang/Object;)Z

          22: pop

          23: return

    }

    分析

    关键代码的在 putfield 这一行,此行表示有一个对 Test 的引用被存入到 this$0 中,也就是说这个匿名内部类持有了外部类的引用。 

    代码验证

    package org.example.a;
     
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.List;
     
    class Test{
        public List<String> createList() {
            List<String> list = new ArrayList<String>() {{
                add("a");
                add("b");
            }};
            return list;
        }
    }
     
    public class Demo {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            List<String> list = new Test().createList();
            // 获取一个类的所有字段
            Field field = list.getClass().getDeclaredField("this$0");
            // 设置允许方法私有的 private 修饰的变量
            field.setAccessible(true);
            System.out.println(field.get(list).getClass());
        }
    }

    打个断点(注意:我这里是用Object模式(右键Variables里的this=> View as=> Object))

    可见:它是持有外部类Test的对象的。

    Java匿名内部类导致内存泄露的原因与解决方案详解

    执行结果:

    class org.example.a.Test

    什么时候会内存泄露

    非静态方法返回匿名内部类的引用可能导致内存泄露,例:

    ​class Test{
        public List<String> createList() {
            List<String> list = new ArrayList<String>() {{
                add("a");
                add("b");
            }};
            return list;
        }
    }
    

    跟上边“普通内部类” 一样,若Test类里边有比较大的对象,而这些大对象根本没被用到,则会内存泄露。

    不会内存泄漏的方案

    方案1:不返回内部类对象引用

    业务直接处理android,不返回内部类对象引用

    class Test{
        public void createList() {
            List<String> list = new ArrayList<String>() {{
                add("a");
                add("b");
            }};
            System.out.println(list);
        }
    }
    

    方案2:匿名内部类改为静态的

    将匿名内部类改为静态的。此时,内部类不会持有外部类的对象的引用。

    为什么这样就不会内存泄露了?

    因为匿名内部类是静态的之后,它所引用的对象或属性也必须是静态的了,因此就可以直接从 JVM 的 Method Area(方法区)获取到引用而无需持久外部对象了。

    代码

    package org.example.a;
     
    import java.util.ArrayList;
    import java.util.List;
     
    class Test{
        public static List<androidString> createList() {
            List<String> list = new ArrayList<String>() {{
                add("a");
                add("b");
            }};
            return list;
        }
    }
     
    public class Demo {
        public static void main(String[] args) {
            System.out.println(Test.createList());
        }
    }

    执行结果

    [a, b]

    编译

    命令:javac Demo.java

    结果

    Java匿名内部类导致内存泄露的原因与解决方案详解

    Idea查看Test$1.class

    package org.example.a;
     
    import java.util.ArrayList;
     
    final class Test$1 extends ArrayList<String> {
        Test$1() {
            this.add("a");
            this.add("b");
        }
    }

    到此这篇关于Java匿名内部类导致内存泄露的原因与解决方案详解的文章就介绍到这了,更多相关Java内存泄露内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号