Java匿名內(nèi)部類導(dǎo)致內(nèi)存泄露的原因與解決方案詳解
簡(jiǎn)介
說(shuō)明
本文用示例介紹匿名內(nèi)部類會(huì)導(dǎo)致內(nèi)存泄漏的原因及其解決方案。
相關(guān)網(wǎng)址
普通內(nèi)部類的內(nèi)存泄露:Java內(nèi)部類持有外部類導(dǎo)致內(nèi)存泄露--原因/解決方案
為什么要持有外部類
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;
import java.util.ArrayList;
import java.util.List;
public class Demo {
private static String name = "Tony";
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
add(name);
}};
System.out.println(list);
}
}實(shí)例:持有外部類
代碼
package org.example.a;
import java.util.ArrayList;
import java.util.List;
class Test{
public List<String> createList() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
}};
return list;
}
}
public class Demo {
public static void main(String[] args) {
System.out.println(new Test().createList());
}
}編譯查看class
命令:javac Demo.java
結(jié)果:

Idea查看Test$1.class(可以發(fā)現(xiàn):持有了一個(gè)外部類Test對(duì)象)
package org.example.a;
import java.util.ArrayList;
class Test$1 extends ArrayList<String> {
Test$1(Test var1) {
this.this$0 = var1;
this.add("a");
this.add("b");
}
}
Idea查看Test.class
package org.example.a;
import java.util.ArrayList;
import java.util.List;
class Test {
Test() {
}
public List<String> createList() {
ArrayList var1 = new ArrayList<String>() {
{
this.add("a");
this.add("b");
}
};
return var1;
}
}Idea查看Demo.class
package org.example.a;
public class Demo {
public Demo() {
}
public static void main(String[] var0) {
System.out.println((new Test()).createList());
}
}
查看字節(jié)碼
命令
javap -c Test$1.class
結(jié)果
Compiled from "Demo.java"
class org.example.a.Test$1 extends java.util.ArrayList<java.lang.String> {
final org.example.a.Test this$0;
org.example.a.Test$1(org.example.a.Test);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lorg/example/a/Test;
5: aload_0
6: invokespecial #2 // Method java/util/ArrayList."<init>":()V
9: aload_0
10: ldc #3 // String a
12: invokevirtual #4 // Method add:(Ljava/lang/Object;)Z
15: pop
16: aload_0
17: ldc #5 // String b
19: invokevirtual #4 // Method add:(Ljava/lang/Object;)Z
22: pop
23: return
}
分析
關(guān)鍵代碼的在 putfield 這一行,此行表示有一個(gè)對(duì) Test 的引用被存入到 this$0 中,也就是說(shuō)這個(gè)匿名內(nèi)部類持有了外部類的引用。
代碼驗(yàn)證
package org.example.a;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
class Test{
public List<String> createList() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
}};
return list;
}
}
public class Demo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
List<String> list = new Test().createList();
// 獲取一個(gè)類的所有字段
Field field = list.getClass().getDeclaredField("this$0");
// 設(shè)置允許方法私有的 private 修飾的變量
field.setAccessible(true);
System.out.println(field.get(list).getClass());
}
}打個(gè)斷點(diǎn)(注意:我這里是用Object模式(右鍵Variables里的this=> View as=> Object))
可見(jiàn):它是持有外部類Test的對(duì)象的。

執(zhí)行結(jié)果:
class org.example.a.Test
什么時(shí)候會(huì)內(nèi)存泄露
非靜態(tài)方法返回匿名內(nèi)部類的引用可能導(dǎo)致內(nèi)存泄露,例:
?class Test{
public List<String> createList() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
}};
return list;
}
}
跟上邊“普通內(nèi)部類” 一樣,若Test類里邊有比較大的對(duì)象,而這些大對(duì)象根本沒(méi)被用到,則會(huì)內(nèi)存泄露。
不會(huì)內(nèi)存泄漏的方案
方案1:不返回內(nèi)部類對(duì)象引用
業(yè)務(wù)直接處理,不返回內(nèi)部類對(duì)象引用
class Test{
public void createList() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
}};
System.out.println(list);
}
}
方案2:匿名內(nèi)部類改為靜態(tài)的
將匿名內(nèi)部類改為靜態(tài)的。此時(shí),內(nèi)部類不會(huì)持有外部類的對(duì)象的引用。
為什么這樣就不會(huì)內(nèi)存泄露了?
因?yàn)槟涿麅?nèi)部類是靜態(tài)的之后,它所引用的對(duì)象或?qū)傩砸脖仨毷庆o態(tài)的了,因此就可以直接從 JVM 的 Method Area(方法區(qū))獲取到引用而無(wú)需持久外部對(duì)象了。
代碼
package org.example.a;
import java.util.ArrayList;
import java.util.List;
class Test{
public static List<String> createList() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
}};
return list;
}
}
public class Demo {
public static void main(String[] args) {
System.out.println(Test.createList());
}
}執(zhí)行結(jié)果
[a, b]
編譯
命令:javac Demo.java
結(jié)果

Idea查看Test$1.class
package org.example.a;
import java.util.ArrayList;
final class Test$1 extends ArrayList<String> {
Test$1() {
this.add("a");
this.add("b");
}
}到此這篇關(guān)于Java匿名內(nèi)部類導(dǎo)致內(nèi)存泄露的原因與解決方案詳解的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄露內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)
本文主要介紹了Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Java JDK 動(dòng)態(tài)代理的使用方法示例
Java 動(dòng)態(tài)代理機(jī)制以巧妙的方式近乎完美地實(shí)踐了代理模式的設(shè)計(jì)理念。下面這篇文章主要給大家分享了關(guān)于Java JDK 動(dòng)態(tài)代理的使用方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-07-07
Java處理科學(xué)計(jì)數(shù)法數(shù)字方式
這篇文章主要介紹了Java處理科學(xué)計(jì)數(shù)法數(shù)字方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot集成quartz實(shí)現(xiàn)定時(shí)任務(wù)
這篇文章主要介紹了如何使用SpringBoot整合Quartz,并將定時(shí)任務(wù)寫入庫(kù)中(持久化存儲(chǔ)),還可以任意對(duì)定時(shí)任務(wù)進(jìn)行如刪除、暫停、恢復(fù)等操作,需要的可以了解下2023-09-09
Spring Cloud Sleuth整合zipkin過(guò)程解析
這篇文章主要介紹了Spring Cloud Sleuth整合zipkin過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Mybatis通過(guò)Spring完成代理類注入的流程分析
這篇文章主要介紹了Mybatis通過(guò)Spring完成代理類注入的流程分析,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
JDBC PreparedStatement Like參數(shù)報(bào)錯(cuò)解決方案
這篇文章主要介紹了JDBC PreparedStatement Like參數(shù)報(bào)錯(cuò)解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程詳細(xì)分析
這篇文章主要介紹了SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程,單點(diǎn)登錄英文全稱Single?Sign?On,簡(jiǎn)稱就是SSO。它的解釋是:在多個(gè)應(yīng)用系統(tǒng)中,只需要登錄一次,就可以訪問(wèn)其他相互信任的應(yīng)用系統(tǒng)2022-12-12

