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

Serena ·
更新时间:2024-11-14
· 515 次阅读

文章目录避免内部类中的内存泄漏步骤1:内部类引用其外部类步骤2:构造函数获取封闭的类引用步骤3:声明一种新方法内存泄漏的解剖 避免内部类中的内存泄漏

使用内部类时要当心垃圾收集
如果您已了解静态类和内部类,则应该熟悉使用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."":()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 #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 // "":()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."":()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."":()V #2 = Class #18 // com/github/crab2died/EnclosingClass #3 = Methodref #2.#17 // com/github/crab2died/EnclosingClass."":()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."":(Lcom/github/crab2died/EnclosingClass;)V #7 = Class #22 // java/lang/Object #8 = Utf8 EnclosedClass #9 = Utf8 InnerClasses #10 = Utf8 #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 test #15 = Utf8 SourceFile #16 = Utf8 EnclosingClass.java #17 = NameAndType #10:#11 // "":()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 // "":(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."":()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 "":()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."":(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.(MemoryLeak.java:9) at MemoryLeak.main(MemoryLeak.java:30)
作者:境里婆娑



内部类 内存泄漏

需要 登录 后方可回复, 如果你还没有账号请 注册新账号