簡介 說明 本文介紹 Java 內部類持有外部類導致記憶體泄露的原因以及其解決方案。 為什麼內部類持有外部類會導致記憶體泄露 非靜態內部類會持有外部類,如果有地方引用了這個非靜態內部類,會導致外部類也被引用,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。 解決方案 不要讓其他的地方 ...
簡介
說明
本文介紹 Java 內部類持有外部類導致記憶體泄露的原因以及其解決方案。
為什麼內部類持有外部類會導致記憶體泄露
非靜態內部類會持有外部類,如果有地方引用了這個非靜態內部類,會導致外部類也被引用,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。
解決方案
不要讓其他的地方持有這個非靜態內部類的引用,直接在這個非靜態內部類執行業務。
將非靜態內部類改為靜態內部類。內部類改為靜態的之後,它所引用的對象或屬性也必須是靜態的,所以靜態內部類無法獲得外部對象的引用,只能從 JVM 的 Method Area(方法區)獲取到static類型的引用。
推薦一個開源免費的 Spring Boot 實戰項目:
為什麼要持有外部類
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);
}
}
報錯:
實例:持有外部類
代碼
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”這個欄位來保存的。
實例:不持有外部類
推薦一個開源免費的 Spring Boot 實戰項目:
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<Object> list = new ArrayList<>();
int counter = 0;
while (true) {
list.add(new Outer(100000).createInner());
System.out.println(counter++);
}
}
}
測試
可以看到:運行了八千多次的時候就記憶體溢出了。
我換了一臺 mac 電腦,4000 多就記憶體溢出了。
不會記憶體泄露的方案
簡介
內部類改為靜態的之後,它所引用的對象或屬性也必須是靜態的,所以靜態內部類無法獲得外部對象的引用,只能從 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<Object> list = new ArrayList<>();
int counter = 0;
while (true) {
list.add(new Outer(100000).createInner());
System.out.println(counter++);
}
}
}
測試
可以發現:迴圈了四十多萬次都沒有記憶體溢出。
來源:blog.csdn.net/feiying0canglang/article/details/121108201
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!