非静态内部类持有外部类引用导致内存溢出

简介: 非静态内部类持有外部类引用导致内存溢出


为什么内部类持有外部类会导致内存泄露

非静态内部类会持有外部类,如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类(即使外部类已经没有其他地方在使用了)。

解决方案
不要让其他的地方持有这个非静态内部类的引用,直接在这个非静态内部类执行业务。

将非静态内部类改为静态内部类。内部类改为静态的之后,它所引用的对象或属性也必须是静态的,所以静态内部类无法获得外部对象的引用,只能从 JVM 的 Method Area(方法区)获取到static类型的引用。

为什么要持有外部类

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

当内部类只在外部类中使用时,匿名内部类可以让外部不知道它的存在,从而减少了代码的维护工作。
当内部类持有外部类时,它就可以直接使用外部类中的变量了,这样可以很方便的完成调用,如下代码所示:
package org.example.a;

class Outer{
private String outerName = "Tony";

class Inner{
    private String name;

    public Inner() {
        this.name = outerName;
    }
}

Inner createInner() {
    return new Inner();
}

}

public class Demo {
public static void main(String[] args) {
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}

但是,静态内部类就无法持有外部类和其非静态字段了。

比如下边这样就会报错:

package org.example.a;

class Outer{
private String outerName = "Tony";

static class Inner{
    private String name;

    public Inner() {
        this.name = outerName;
    }
}

Inner createInner() {
    return new Inner();
}

}

public class Demo {
public static void main(String[] args) {
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
【由于Inner为static,以上会报错】

实例:持有外部类
代码
package org.example.a;

class Outer{
class Inner {

}

Inner createInner() {
    return new Inner();
}

}

public class Demo {
public static void main(String[] args) {
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}

断点调试
可以看到:内部类持有外部类的对象的引用,是以“this$0”这个字段来保存的。

实例:不持有外部类
package org.example.a;

class Outer{
static class Inner {

}

Inner createInner() {
    return new Inner();
}

}

public class Demo {
public static void main(String[] args) {
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
断点调试
可以发现:内部类不再持有外部类了。

实例:内存泄露
简介
若内部类持有外部类的引用,对内部类的使用很多时,会导致外部类数目很多。此时,就算是外部类的数据没有被用到,外部类的数据所占空间也不会被释放。

本处在外部类存放大量的数据来模拟。

代码
package org.example.a;

import java.util.ArrayList;
import java.util.List;

class Outer{
private int[] data;

public Outer(int size) {
    this.data = new int[size];
}

class Innner{

}

Innner createInner() {
    return new Innner();
}

}

public class Demo {
public static void main(String[] args) {
List list = new ArrayList<>();
int counter = 0;
while (true) {
list.add(new Outer(100000).createInner());
System.out.println(counter++);
}
}
}

测试
可以看到:运行了八千多次的时候就内存溢出了。

不会内存泄露的方案
简介
内部类改为静态的之后,它所引用的对象或属性也必须是静态的,所以静态内部类无法获得外部对象的引用,只能从 JVM 的 Method Area(方法区)获取到 static 类型的引用。

代码
package org.example.a;

import java.util.ArrayList;
import java.util.List;

class Outer{
private int[] data;

public Outer(int size) {
    this.data = new int[size];
}

static class Inner {

}

Inner createInner() {
    return new Inner();
}

}

public class Demo {
public static void main(String[] args) {
List list = new ArrayList<>();
int counter = 0;
while (true) {
list.add(new Outer(100000).createInner());
System.out.println(counter++);
}
}
}

测试
可以发现:循环了四十多万次都没有内存溢出。

相关文章
|
9月前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
99 0
|
2月前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
44 6
|
5月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
369 4
|
5月前
|
存储 Java 程序员
结构体和类的内存管理方式在不同编程语言中的表现有何异同?
不同编程语言中结构体和类的内存管理方式既有相似之处,又有各自的特点。了解这些异同点有助于开发者在不同的编程语言中更有效地使用结构体和类来进行编程,合理地管理内存,提高程序的性能和可靠性。
89 3
|
5月前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
5月前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
6月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
6月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(三)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
102 0
|
6月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(一)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
8月前
|
存储 程序员 Python
Python类的定义_类和对象的关系_对象的内存模型
通过类的定义来创建对象,我们可以应用面向对象编程(OOP)的原则,例如封装、继承和多态,这些原则帮助程序员构建可复用的代码和模块化的系统。Python语言支持这样的OOP特性,使其成为强大而灵活的编程语言,适用于各种软件开发项目。
76 1

热门文章

最新文章

下一篇
oss创建bucket