发布于 4年前

Java防止非静态内部类内存泄漏

内存泄漏

一个不会被使用的对象,因为另一个正在使用的对象持有该对象的引用,导致它不能正常被回收,而停留在堆内存中,从而导致内存泄漏。

最坏的情况下,由于大量的内存泄漏,最终导致jvm的内存耗尽,致使程序奔溃。也可能会导致内存空间不足,jvm出现频繁的GC。

代码示例

import java.util.ArrayList;
class OuterClass
{
   private int[] data;
   public OuterClass(int size)
   {
      data = new int[size];
   }
   class InnerClass
   {
   }
   InnerClass getInnerClassObject()
   {
      return new InnerClass();
   }
}
public class MemoryLeak
{
   public static void main(String[] args)
   {
      ArrayList al = new ArrayList<>();
      int counter = 0;
      while (true)
      {
         al.add(new OuterClass(100000).getInnerClassObject());
         System.out.println(counter++);
      }
   }
}

执行以上代码,输出结果,最终导致了堆内存溢出。

7639
7640
7641
7642
7643
7644
7645
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at EnclosingClass.<init>(MemoryLeak.java:9)
    at MemoryLeak.main(MemoryLeak.java:30)

原因分析

我们使用java提供的工具javap,可以对编译生成的.class文件做分析。也可以使用一些反编译工具对生成的.class反编译,也可以看到内部类的代码实现。

javap OuterClass$InnerClass

输出类似于:

Compiled from "OuterClass.java"
public class OuterClass$InnerClass {
  final OuterClass this$0;
  public OuterClass$InnerClass(OuterClass);
}

可以看到内部类InnerClass存在一个this$0的OuterClass的变量,此变量是通过InnerClass的构造函数传进来的。

示例代码中创建了一个数组列表ArrayList,它将用来存放InnerClass的对象。生成InnerClass对象前,需要先构造OuterClass。OuterClass构造时默认创建了一个100000大小的整型数组。相当于一个初始化的OuterClass默认占用10000个int整型的空间。在MemoryLeak,通过循环while不断向a1添加InnerClass对象。对于每次循环来说new OuterClass后,就不会再使用OuterClass对象。但通过分析内部了的实现,即使OuterClass对象不会再被使用,内部类InnerClass对象里还是保存了大对象OuterClass,导致OuterClass的生命周期是和InnerClass一样,最终导致内存泄漏。

非静态内部类使用注意

1、如果外部类是一个大对象,必须要谨慎使用非静态内部类,特别时生命周期长的非静态内部类,这样容易造成内存溢出OutOfMemoryError。

2、之所以强调非静态,对于静态内部类来说,它的内部实现是不存放外部类的,所以再合理编码的情况下,使用静态内部类。

©2020 edoou.com   京ICP备16001874号-3