简介

说明

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

相关网址

为什么要持有外部类

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

  1. 当匿名内部类只在外部类(主类)中使用时,匿名内部类可以让外部不知道它的存在,从而减少了代码的维护工作。
  2. 当匿名内部类持有外部类时,它就可以直接使用外部类中的变量了,这样可以很方便的完成调用,如下代码所示:
  1. package org.example.a;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class Demo {
  5. private static String name = "Tony";
  6. public static void main(String[] args) {
  7. List<String> list = new ArrayList<String>() {{
  8. add("a");
  9. add("b");
  10. add(name);
  11. }};
  12. System.out.println(list);
  13. }
  14. }

实例:持有外部类

代码

  1. package org.example.a;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. class Test{
  5. public List<String> createList() {
  6. List<String> list = new ArrayList<String>() {{
  7. add("a");
  8. add("b");
  9. }};
  10. return list;
  11. }
  12. }
  13. public class Demo {
  14. public static void main(String[] args) {
  15. System.out.println(new Test().createList());
  16. }
  17. }

编译查看class

命令:javac Demo.java

结果:

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

  1. package org.example.a;
  2. import java.util.ArrayList;
  3. class Test$1 extends ArrayList<String> {
  4. Test$1(Test var1) {
  5. this.this$0 = var1;
  6. this.add("a");
  7. this.add("b");
  8. }
  9. }

Idea查看Test.class

  1. package org.example.a;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. class Test {
  5. Test() {
  6. }
  7. public List<String> createList() {
  8. ArrayList var1 = new ArrayList<String>() {
  9. {
  10. this.add("a");
  11. this.add("b");
  12. }
  13. };
  14. return var1;
  15. }
  16. }

Idea查看Demo.class

  1. package org.example.a;
  2. public class Demo {
  3. public Demo() {
  4. }
  5. public static void main(String[] var0) {
  6. System.out.println((new Test()).createList());
  7. }
  8. }

查看字节码

命令

javap -c Test$1.class

结果

  1. Compiled from "Demo.java"
  2. class org.example.a.Test$1 extends java.util.ArrayList<java.lang.String> {
  3. final org.example.a.Test this$0;
  4. org.example.a.Test$1(org.example.a.Test);
  5. Code:
  6. 0: aload_0
  7. 1: aload_1
  8. 2: putfield #1 // Field this$0:Lorg/example/a/Test;
  9. 5: aload_0
  10. 6: invokespecial #2 // Method java/util/ArrayList."<init>":()V
  11. 9: aload_0
  12. 10: ldc #3 // String a
  13. 12: invokevirtual #4 // Method add:(Ljava/lang/Object;)Z
  14. 15: pop
  15. 16: aload_0
  16. 17: ldc #5 // String b
  17. 19: invokevirtual #4 // Method add:(Ljava/lang/Object;)Z
  18. 22: pop
  19. 23: return
  20. }

分析

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

代码验证

  1. package org.example.a;
  2. import java.lang.reflect.Field;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. class Test{
  6. public List<String> createList() {
  7. List<String> list = new ArrayList<String>() {{
  8. add("a");
  9. add("b");
  10. }};
  11. return list;
  12. }
  13. }
  14. public class Demo {
  15. public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
  16. List<String> list = new Test().createList();
  17. // 获取一个类的所有字段
  18. Field field = list.getClass().getDeclaredField("this$0");
  19. // 设置允许方法私有的 private 修饰的变量
  20. field.setAccessible(true);
  21. System.out.println(field.get(list).getClass());
  22. }
  23. }

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

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

执行结果:

class org.example.a.Test

什么时候会内存泄露

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

  1. ​class Test{
  2. public List<String> createList() {
  3. List<String> list = new ArrayList<String>() {{
  4. add("a");
  5. add("b");
  6. }};
  7. return list;
  8. }
  9. }

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

不会内存泄漏的方案

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

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

  1. class Test{
  2. public void createList() {
  3. List<String> list = new ArrayList<String>() {{
  4. add("a");
  5. add("b");
  6. }};
  7. System.out.println(list);
  8. }
  9. }

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

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

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

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

代码

  1. package org.example.a;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. class Test{
  5. public static List<String> createList() {
  6. List<String> list = new ArrayList<String>() {{
  7. add("a");
  8. add("b");
  9. }};
  10. return list;
  11. }
  12. }
  13. public class Demo {
  14. public static void main(String[] args) {
  15. System.out.println(Test.createList());
  16. }
  17. }

执行结果

[a, b]

编译

命令:javac Demo.java

结果

Idea查看Test$1.class

  1. package org.example.a;
  2. import java.util.ArrayList;
  3. final class Test$1 extends ArrayList<String> {
  4. Test$1() {
  5. this.add("a");
  6. this.add("b");
  7. }
  8. }