Java匿名內(nèi)部類導(dǎo)致內(nèi)存泄露的原因與解決方案詳解
簡介
說明
本文用示例介紹匿名內(nèi)部類會導(dǎo)致內(nèi)存泄漏的原因及其解決方案。
相關(guān)網(wǎng)址
普通內(nèi)部類的內(nèi)存泄露:Java內(nèi)部類持有外部類導(dǎo)致內(nèi)存泄露--原因/解決方案
為什么要持有外部類
Java 語言中,非靜態(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對象)
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è)對 Test 的引用被存入到 this$0 中,也就是說這個(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))
可見:它是持有外部類Test的對象的。
執(zhí)行結(jié)果:
class org.example.a.Test
什么時(shí)候會內(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類里邊有比較大的對象,而這些大對象根本沒被用到,則會內(nèi)存泄露。
不會內(nèi)存泄漏的方案
方案1:不返回內(nèi)部類對象引用
業(yè)務(wù)直接處理,不返回內(nèi)部類對象引用
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)部類不會持有外部類的對象的引用。
為什么這樣就不會內(nèi)存泄露了?
因?yàn)槟涿麅?nèi)部類是靜態(tài)的之后,它所引用的對象或?qū)傩砸脖仨毷庆o態(tài)的了,因此就可以直接從 JVM 的 Method Area(方法區(qū))獲取到引用而無需持久外部對象了。
代碼
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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot調(diào)用第三方WebService接口的兩種方法
本文主要介紹了SpringBoot調(diào)用第三方WebService接口的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06詳解Java MyBatis 插入數(shù)據(jù)庫返回主鍵
這篇文章主要介紹了詳解Java MyBatis 插入數(shù)據(jù)庫返回主鍵,有興趣的可以了解一下。2017-01-01IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】
這篇文章主要介紹了IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot2使用WebFlux函數(shù)式編程的方法
這篇文章主要介紹了SpringBoot2使用WebFlux函數(shù)式編程的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08關(guān)于mybatis傳入?yún)?shù)一直為null的問題
這篇文章主要介紹了關(guān)于mybatis傳入?yún)?shù)一直為null的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07