内存泄漏
一个不会被使用的对象,因为另一个正在使用的对象持有该对象的引用,导致它不能正常被回收,而停留在堆内存中,从而导致内存泄漏。
最坏的情况下,由于大量的内存泄漏,最终导致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、之所以强调非静态,对于静态内部类来说,它的内部实现是不存放外部类的,所以再合理编码的情况下,使用静态内部类。