當前位置: 妍妍網 > 碼農

Java內部類有坑,100%記憶體泄露!

2024-03-15碼農

本文介紹 Java 內部類持有外部類導致記憶體泄露的原因以及其解決方案。

「為什麽內部類持有外部類會導致記憶體泄露?」

非靜態內部類會持有外部類,如果有地方參照了這個非靜態內部類,會導致外部類也被參照,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。

「解決方案」

  1. 不要讓其他的地方持有這個非靜態內部類的參照,直接在這個非靜態內部類執行業務。

  2. 將非靜態內部類改為靜態內部類。內部類改為靜態的之後,它所參照的物件或內容也必須是靜態的,所以靜態內部類無法獲得外部物件的參照,只能從 JVM 的 Method Area(方法區)獲取到static型別的參照。

來源:https://knife.blog.csdn.net/?type=blog



為什麽要持有外部類

Java 語言中,非靜態內部類的主要作用有兩個:

  1. 當內部類只在外部類中使用時,匿名內部類可以讓外部不知道它的存在,從而減少了程式碼的維護工作。

  2. 當內部類持有外部類時,它就可以直接使用外部類中的變量了,這樣可以很方便的完成呼叫,如下程式碼所示:

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

精品資料,超贊福利,免費領

微信掃碼/長按辨識 添加【技術交流群

群內每天分享精品學習資料

最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

👇👇

👇點選"閱讀原文",獲取更多資料(持續更新中