Java 內(nèi)部類有坑,100 % 內(nèi)存泄露!
1
簡(jiǎn)介
說(shuō)明
本文介紹 Java 內(nèi)部類持有外部類導(dǎo)致內(nèi)存泄露的原因以及其解決方案。
為什么內(nèi)部類持有外部類會(huì)導(dǎo)致內(nèi)存泄露?
非靜態(tài)內(nèi)部類會(huì)持有外部類,如果有地方引用了這個(gè)非靜態(tài)內(nèi)部類,會(huì)導(dǎo)致外部類也被引用,垃圾回收時(shí)無(wú)法回收這個(gè)外部類(即使外部類已經(jīng)沒有其他地方在使用了)。
解決方案
-
不要讓其他的地方持有這個(gè)非靜態(tài)內(nèi)部類的引用,直接在這個(gè)非靜態(tài)內(nèi)部類執(zhí)行業(yè)務(wù)。
-
將非靜態(tài)內(nèi)部類改為靜態(tài)內(nèi)部類。內(nèi)部類改為靜態(tài)的之后,它所引用的對(duì)象或?qū)傩砸脖仨毷庆o態(tài)的,所以靜態(tài)內(nèi)部類無(wú)法獲得外部對(duì)象的引用,只能從 JVM 的 Method Area(方法區(qū))獲取到static類型的引用。
2
為什么要持有外部類
Java 語(yǔ)言中,非靜態(tài)內(nèi)部類的主要作用有兩個(gè):
-
當(dāng)內(nèi)部類只在外部類中使用時(shí),匿名內(nèi)部類可以讓外部不知道它的存在,從而減少了代碼的維護(hù)工作。
-
當(dāng)內(nèi)部類持有外部類時(shí),它就可以直接使用外部類中的變量了,這樣可以很方便的完成調(diào)用,如下代碼所示:
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);
????}
}
但是,靜態(tài)內(nèi)部類就無(wú)法持有外部類和其非靜態(tài)字段了。
比如下邊這樣就會(huì)報(bào)錯(cuò):
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);
????}
}
報(bào)錯(cuò):

3
實(shí)例:持有外部類
代碼
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);
????}
}
斷點(diǎn)調(diào)試
可以看到:內(nèi)部類持有外部類的對(duì)象的引用,是以“this$0”這個(gè)字段來(lái)保存的。

4
實(shí)例:不持有外部類
?
代碼
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);
????}
}
斷點(diǎn)調(diào)試
可以發(fā)現(xiàn):內(nèi)部類不再持有外部類了。

5
實(shí)例:內(nèi)存泄露
?
簡(jiǎn)介
若內(nèi)部類持有外部類的引用,對(duì)內(nèi)部類的使用很多時(shí),會(huì)導(dǎo)致外部類數(shù)目很多。此時(shí),就算是外部類的數(shù)據(jù)沒有被用到,外部類的數(shù)據(jù)所占空間也不會(huì)被釋放。
本處在外部類存放大量的數(shù)據(jù)來(lái)模擬。
代碼
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++);
????????}
????}
}
測(cè)試
可以看到:運(yùn)行了八千多次的時(shí)候就內(nèi)存溢出了。

我換了一臺(tái) mac 電腦,4000 多就內(nèi)存溢出了。
6
不會(huì)內(nèi)存泄露的方案
?
簡(jiǎn)介
內(nèi)部類改為靜態(tài)的之后,它所引用的對(duì)象或?qū)傩砸脖仨毷庆o態(tài)的,所以靜態(tài)內(nèi)部類無(wú)法獲得外部對(duì)象的引用,只能從 JVM 的 Method Area(方法區(qū))獲取到 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++);
????????}
????}
}
測(cè)試
可以發(fā)現(xiàn):循環(huán)了四十多萬(wàn)次都沒有內(nèi)存溢出。

來(lái)源:knife.blog.csdn.net/article/details/121108201
