详细分析内部类的发生内存泄漏的原因

避免内部类中的内存泄漏

使用内部类时要当心垃圾收集
如果您已了解静态类和内部类,则应该熟悉使用Java代码中的嵌套类的基础知识。在这个相关的技巧中,我将带您了解嵌套类的陷阱之一,这是内部类在JVM中导致内存泄漏和内存不足错误的潜力。

之所以会发生这种类型的内存泄漏,是因为内部类必须始终能够访问其外部类-并非总是与JVM的计划一起使用。

从简单的嵌套过程到内存不足错误(并可能关闭JVM)是​​一个过程。理解它的最好方法是看它的源码。
在这里插入图片描述

步骤1:内部类引用其外部类

内部类的任何实例都包含对其外部类的隐式引用。例如,考虑以下EnclosingClass带有嵌套的EnclosedClass非静态成员类的声明:

public class EnclosingClass
{
   public class EnclosedClass
   {
   }
}

为了更好地理解这种联系,我们可以将上面的源代码(javac EnclosingClass.java)编译为EnclosingClass.class和EnclosingClass$EnclosedClass.class,然后检查后者的类文件。

JDK包含一个javap(Java打印)工具,用于反汇编类文件。在命令行上,使用javap运行EnclosingClass$EnclosedClass如下:
在这里插入图片描述

javap EnclosingClass$EnclosedClass

您可以看到以下输出,该输出显示了包含以下内容final EnclosingClass this$0字段EnclosingClass:

public class com.github.crab2died.EnclosingClass$EnclosedClass {
  final com.github.crab2died.EnclosingClass this$0;
  public com.github.crab2died.EnclosingClass$EnclosedClass(com.github.crab2died.EnclosingClass);
}

步骤2:构造函数获取封闭的类引用

上面的输出显示了带有EnclosingClass参数的构造函数。使用javap -v(verbose)选项执行,您将观察到构造函数将EnclosingClass对象引用保存在this$0字段中:

Classfile /D:/aplus/EnclosingClass$EnclosedClass.class
  Last modified 2020-3-15; size 440 bytes
  MD5 checksum 308ea24edb49a4d49669d101fff55d5a
  Compiled from "EnclosingClass.java"
public class com.github.crab2died.EnclosingClass$EnclosedClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Fieldref           #3.#13         // com/github/crab2died/EnclosingClass$EnclosedClass.this$0:Lcom/github/crab2died/EnclosingClass;
   #2 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #3 = Class              #16            // com/github/crab2died/EnclosingClass$EnclosedClass
   #4 = Class              #19            // java/lang/Object
   #5 = Utf8               this$0
   #6 = Utf8               Lcom/github/crab2died/EnclosingClass;
   #7 = Utf8               <init>
   #8 = Utf8               (Lcom/github/crab2died/EnclosingClass;)V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               SourceFile
  #12 = Utf8               EnclosingClass.java
  #13 = NameAndType        #5:#6          // this$0:Lcom/github/crab2died/EnclosingClass;
  #14 = NameAndType        #7:#20         // "<init>":()V
  #15 = Class              #21            // com/github/crab2died/EnclosingClass
  #16 = Utf8               com/github/crab2died/EnclosingClass$EnclosedClass
  #17 = Utf8               EnclosedClass
  #18 = Utf8               InnerClasses
  #19 = Utf8               java/lang/Object
  #20 = Utf8               ()V
  #21 = Utf8               com/github/crab2died/EnclosingClass
{
  final com.github.crab2died.EnclosingClass this$0;
    descriptor: Lcom/github/crab2died/EnclosingClass;
    flags: ACC_FINAL, ACC_SYNTHETIC

  public com.github.crab2died.EnclosingClass$EnclosedClass(com.github.crab2died.EnclosingClass);
    descriptor: (Lcom/github/crab2died/EnclosingClass;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this$0:Lcom/github/crab2died/EnclosingClass;
         5: aload_0
         6: invokespecial #2                  // Method java/lang/Object."<init>":()V
         9: return
      LineNumberTable:
        line 12: 0
}
SourceFile: "EnclosingClass.java"
InnerClasses:
     public #17= #3 of #15; //EnclosedClass=class com/github/crab2died/EnclosingClass$EnclosedClass of class com/github/crab2died/EnclosingClass

步骤3:声明一种新方法

在实例化类中声明了EnclosingClass,然后创建EnclosedClass。

EnclosingClass ec = new EnclosingClass();
ec.new EnclosedClass();
 Last modified 2020-3-15; size 502 bytes
  MD5 checksum d31832f98dbbf557e995ac447cc55fb2
  Compiled from "EnclosingClass.java"
public class com.github.crab2died.EnclosingClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // com/github/crab2died/EnclosingClass
   #3 = Methodref          #2.#17         // com/github/crab2died/EnclosingClass."<init>":()V
   #4 = Class              #19            // com/github/crab2died/EnclosingClass$EnclosedClass
   #5 = Methodref          #7.#20         // java/lang/Object.getClass:()Ljava/lang/Class;
   #6 = Methodref          #4.#21         // com/github/crab2died/EnclosingClass$EnclosedClass."<init>":(Lcom/github/crab2died/EnclosingClass;)V
   #7 = Class              #22            // java/lang/Object
   #8 = Utf8               EnclosedClass
   #9 = Utf8               InnerClasses
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               test
  #15 = Utf8               SourceFile
  #16 = Utf8               EnclosingClass.java
  #17 = NameAndType        #10:#11        // "<init>":()V
  #18 = Utf8               com/github/crab2died/EnclosingClass
  #19 = Utf8               com/github/crab2died/EnclosingClass$EnclosedClass
  #20 = NameAndType        #23:#24        // getClass:()Ljava/lang/Class;
  #21 = NameAndType        #10:#25        // "<init>":(Lcom/github/crab2died/EnclosingClass;)V
  #22 = Utf8               java/lang/Object
  #23 = Utf8               getClass
  #24 = Utf8               ()Ljava/lang/Class;
  #25 = Utf8               (Lcom/github/crab2died/EnclosingClass;)V
{
  public com.github.crab2died.EnclosingClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #2                  // class com/github/crab2died/EnclosingClass
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: new           #4                  // class com/github/crab2died/EnclosingClass$EnclosedClass
        11: dup
        12: aload_1
        13: dup
        14: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        17: pop
        18: invokespecial #6                  // Method com/github/crab2died/EnclosingClass$EnclosedClass."<init>":(Lcom/github/crab2died/EnclosingClass;)V
        21: pop
        22: return
      LineNumberTable:
        line 17: 0
        line 18: 8
        line 19: 22
}
SourceFile: "EnclosingClass.java"
InnerClasses:
     public #8= #4 of #2; //EnclosedClass=class com/github/crab2died/EnclosingClass$EnclosedClass of class com/github/crab2died/EnclosingClass

内存泄漏的解剖

在以上示例中,我们已将封闭类的引用存储在封闭类的制造变量中。这可能导致内存泄漏,其中的封闭类引用了无法垃圾回收的大型对象图。根据应用程序代码,可能会耗尽内存并收到内存不足错误,从而导致JVM终止。下面的例子演示这种情况。

MemoryLeak.java

import java.util.ArrayList;

class EnclosingClass
{
   private int[] data;

   public EnclosingClass(int size)
   {
      data = new int[size];
   }

   class EnclosedClass
   {
   }

   EnclosedClass getEnclosedClassObject()
   {
      return new EnclosedClass();
   }
}

public class MemoryLeak
{
   public static void main(String[] args)
   {
      ArrayList al = new ArrayList<>();
      int counter = 0;
      while (true)
      {
         al.add(new EnclosingClass(100000).getEnclosedClassObject());
         System.out.println(counter++);
      }
   }
}

该EnclosingClass声明一个私有data引用整数数组领域。数组的大小传递给此类的构造函数,并实例化该数组。

的EnclosingClass还声明EnclosedClass,一个嵌套非静态成员的类,和一种方法,其实例化EnclosedClass,返回此实例。

MemoryLeak的main()方法首先创建一个java.util.ArrayList存储EnclosingClass.EnclosedClass对象。暂时不使用包和泛型以及将包和泛型ArrayList(将对象存储在动态数组中)的使用-重要的一点是观察内存泄漏是如何发生的。

将计数器初始化为0后,main()进入无限while循环,该循环重复实例化EnclosedClass并将其添加到数组列表中。然后打印(或递增)计数器。在实例化封闭的类之前,EnclosingClass必须实例化该实例,并将100000其作为数组大小传递。

每个存储的EnclosedClass对象维护对其封闭对象的引用,该对象引用100,000个32位整数(或400,000字节)的数组。在对内部对象进行垃圾收集之前,无法对外部对象进行垃圾收集。最终,该应用程序将耗尽内存。

我观察到输出的以下后缀-请注意,您可能会观察到不同的最终计数器值:


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)
	
发布了143 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/TreeShu321/article/details/104878504