來源:https://knife.blog.csdn.net/article/details/124946774
今天給大家分享一種,Java內部類使用不當導致的內存泄露問題,最終導致內存溢出!希望能夠幫助到大家!
簡介
「說明」
本文介紹 Java 內部類持有外部類導致內存泄露的原因以及其解決方案。
「爲什麼內部類持有外部類會導致內存泄露?」
非靜態內部類會持有外部類,如果有地方引用了這個非靜態內部類,會導致外部類也被引用,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。
「解決方案」
- 不要讓其他的地方持有這個非靜態內部類的引用,直接在這個非靜態內部類執行業務。
- 將非靜態內部類改爲靜態內部類。內部類改爲靜態的之後,它所引用的對象或屬性也必須是靜態的,所以靜態內部類無法獲得外部對象的引用,只能從 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);
}
}
但是,靜態內部類就無法持有外部類和其非靜態字段了。另外,最新 Java 面試題整理:https://www.javastack.cn/mst/
比如下邊這樣就會報錯
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”這個字段來保存的。
實例:不持有外部類
「代碼」
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);
}
}
更多 Java 教程及示例:https://github.com/javastacks/javastack
「斷點調試」
可以發現:內部類不再持有外部類了。
實例:內存泄露
「簡介」
若內部類持有外部類的引用,對內部類的使用很多時,會導致外部類數目很多。此時,就算是外部類的數據沒有被用到,外部類的數據所佔空間也不會被釋放。
本處在外部類存放大量的數據來模擬。
「代碼」
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++);
}
}
}
「測試」
可以發現:循環了四十多萬次都沒有內存溢出。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這纔是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!