本文介紹 Java 內部類持有外部類導致記憶體泄露的原因以及其解決方案。
「為什麽內部類持有外部類會導致記憶體泄露?」
非靜態內部類會持有外部類,如果有地方參照了這個非靜態內部類,會導致外部類也被參照,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。
「解決方案」
不要讓其他的地方持有這個非靜態內部類的參照,直接在這個非靜態內部類執行業務。
將非靜態內部類改為靜態內部類。內部類改為靜態的之後,它所參照的物件或內容也必須是靜態的,所以靜態內部類無法獲得外部物件的參照,只能從 JVM 的 Method Area(方法區)獲取到static型別的參照。
來源:https://knife.blog.csdn.net/?type=blog
為什麽要持有外部類
Java 語言中,非靜態內部類的主要作用有兩個:
當內部類只在外部類中使用時,匿名內部類可以讓外部不知道它的存在,從而減少了程式碼的維護工作。
當內部類持有外部類時,它就可以直接使用外部類中的變量了,這樣可以很方便的完成呼叫,如下程式碼所示:
package org.example.a;
classOuter{
private String outerName = "Tony";
classInner{
private String name;
publicInner(){
this.name = outerName;
}
}
Inner createInner(){
returnnew Inner();
}
}
public classDemo{
publicstaticvoidmain(String[] args){
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
但是,靜態內部類就無法持有外部類和其非靜態欄位了。比如下邊這樣就會報錯
package org.example.a;
classOuter{
private String outerName = "Tony";
static classInner{
private String name;
publicInner(){
this.name = outerName;
}
}
Inner createInner(){
returnnew Inner();
}
}
public classDemo{
publicstaticvoidmain(String[] args){
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
報錯:
例項:持有外部類
「程式碼」
package org.example.a;
classOuter{
classInner{
}
Inner createInner(){
returnnew Inner();
}
}
public classDemo{
publicstaticvoidmain(String[] args){
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
「斷點偵錯」
可以看到:內部類持有外部類的物件的參照,是以「this$0」這個欄位來保存的。
例項:不持有外部類
「程式碼」
package org.example.a;
classOuter{
static classInner{
}
Inner createInner(){
returnnew Inner();
}
}
public classDemo{
publicstaticvoidmain(String[] args){
Outer.Inner inner = new Outer().createInner();
System.out.println(inner);
}
}
「斷點偵錯」
可以發現:內部類不再持有外部類了。
例項:記憶體泄露
「簡介」
若內部類持有外部類的參照,對內部類的使用很多時,會導致外部類數目很多。此時,就算是外部類的數據沒有被用到,外部類的數據所占空間也不會被釋放。
本處在外部類存放大量的數據來模擬。
「程式碼」
package org.example.a;
import java.util.ArrayList;
import java.util.List;
classOuter{
privateint[] data;
publicOuter(int size){
this.data = newint[size];
}
classInnner{
}
Innner createInner(){
returnnew Innner();
}
}
public classDemo{
publicstaticvoidmain(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;
classOuter{
privateint[] data;
publicOuter(int size){
this.data = newint[size];
}
static classInner{
}
Inner createInner(){
returnnew Inner();
}
}
public classDemo{
publicstaticvoidmain(String[] args){
List<Object> list = new ArrayList<>();
int counter = 0;
while (true) {
list.add(new Outer(100000).createInner());
System.out.println(counter++);
}
}
}
「測試」
可以發現:迴圈了四十多萬次都沒有記憶體溢位。
以上,希望能對大家在使用內部類時會有所幫助。
>>
END
精品資料,超贊福利,免費領
微信掃碼/長按辨識 添加【技術交流群】
群內每天分享精品學習資料
最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎、並行、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、訊息佇列等多個型別),歡迎您的使用。
👇👇
👇點選"閱讀原文",獲取更多資料(持續更新中)