我們知道,動(dòng)態(tài)代理(這里指jdk的動(dòng)態(tài)代理)與靜態(tài)代理的區(qū)別在于,其真實(shí)的代理類是動(dòng)態(tài)生成的。但具體是怎么生成,生成的代理類包含了哪些內(nèi)容,以什么形式存在,它為什么一定要以接口為基礎(chǔ)?
如果去看動(dòng)態(tài)代理的源代碼(java.lang.reflect.proxy),會(huì)發(fā)現(xiàn)其原理很簡(jiǎn)單(真正二進(jìn)制類文件的生成是在本地方法中完成,源代碼中沒有),但其中用到了一個(gè)緩沖類java.lang.reflect.weakcache<classloader,class<?>[],class<?>>,這個(gè)類用到了弱引用來構(gòu)建。
在jdk的3個(gè)特殊引用中,弱引用是使用范圍最廣的,它的特性也最清晰,相對(duì)而言,其他兩種邏輯稍顯晦澀,源碼中的注釋也語焉不詳。本文將簡(jiǎn)單介紹幾種引用的行為特征,然后分析一下弱引用的一些實(shí)際應(yīng)用場(chǎng)景,其中包含了動(dòng)態(tài)代理中的實(shí)現(xiàn)。本文將包含以下內(nèi)容:
jdk中的引用類型
不同引用類型對(duì)gc行為的影響
引用類型的實(shí)現(xiàn)
threadlocal對(duì)弱引用的使用
動(dòng)態(tài)代理對(duì)弱引用的實(shí)現(xiàn)
虛引用如何導(dǎo)致內(nèi)存泄漏
jdk中「引用(reference)」的類型
java的所有運(yùn)行邏輯都是基于引用的,其形態(tài)類似于不可變的指針,所以在java中會(huì)有一些很繞的概念,比如說,java中函數(shù)的傳參是值傳遞,但這里所說的值,其實(shí)是引用的值,所以你可以通過一個(gè)函數(shù)的參數(shù)來修改其對(duì)象的值。另一方面,java中還存在一些基本數(shù)據(jù)類型,它們沒有引用,傳遞的是真實(shí)的值,所以不能在函數(shù)內(nèi)部修改參數(shù)的值。
關(guān)于引用,java中有這樣幾種:
1.強(qiáng)引用
所有對(duì)象的引用默認(rèn)為強(qiáng)引用,普通代碼中,賦值語句之間傳遞的都是強(qiáng)引用,如果一個(gè)對(duì)象可以被某個(gè)線程(活著的,下同)通過強(qiáng)引用訪問到,則稱之為強(qiáng)可達(dá)的(stronglyreachable)。
強(qiáng)可達(dá)的對(duì)象不會(huì)被gc回收。
2.軟引用(softreference<t>)
當(dāng)一個(gè)對(duì)象不是強(qiáng)可達(dá)的,但可以被某個(gè)線程通過軟引用訪問到,則稱之為軟可達(dá)的(softlyreachable)。
軟可達(dá)的對(duì)象的引用只有在內(nèi)存不足時(shí)會(huì)被清除,使之可以被gc回收。在這一點(diǎn)上,jvm是不保證具體什么時(shí)候清除軟引用,但可以保證在oom之前會(huì)清除軟可達(dá)的對(duì)象。同時(shí),jvm也不保證軟可達(dá)的對(duì)象的回收順序,但oraclejdk的文檔中提到,最近創(chuàng)建和最近使用的軟可達(dá)對(duì)象往往會(huì)最后被回收,與lru類似。
關(guān)于軟可達(dá)的對(duì)象何時(shí)被回收,可以參考o(jì)racle的文檔。
3.弱引用(weakreference<t>)
當(dāng)一個(gè)對(duì)象不是強(qiáng)可達(dá)的,也不是軟可達(dá)的,但可以被某個(gè)線程通過弱引用訪問到,則稱之為弱可達(dá)的(weaklyreachable)。
弱引用是除了強(qiáng)引用之外,使用最廣泛的引用類型,它的特性也更簡(jiǎn)單,當(dāng)一個(gè)對(duì)象是弱可達(dá)時(shí),jvm就會(huì)清除這個(gè)對(duì)象上的弱引用,隨后對(duì)這個(gè)對(duì)象進(jìn)行回收動(dòng)作。
4.虛引用(phantomreference<t>)
當(dāng)一個(gè)對(duì)象,通過以上幾種可達(dá)性分析都不可達(dá),且已經(jīng)finalized,但有虛引用指向它,則它是虛可達(dá)的(phantomreachable)。
虛引用是行為最詭異,使用方法最難的引用,后邊會(huì)講到。
虛引用不會(huì)影響gc,它的get()方法永遠(yuǎn)返回null,唯一的用處就是在gcfinalize一個(gè)對(duì)象之后,它會(huì)被放到指定的隊(duì)列中去。關(guān)于這個(gè)隊(duì)列會(huì)在下邊說明。
不同gc行為背后的原理
jvmgc的可達(dá)性分析
jvm的gc通過可達(dá)性分析來判斷一個(gè)對(duì)象是否可被回收,其基本思路就是以gcroots為起點(diǎn)遍歷鏈路上所有對(duì)象,當(dāng)一個(gè)對(duì)象和gcroots之間沒有任何的直接或間接引用相連時(shí),就稱之為不可達(dá)對(duì)象,則證明此對(duì)象是不可用的。
而進(jìn)一步,java中又定義了如上所述的4種不同的可達(dá)性,用來實(shí)現(xiàn)更精細(xì)的gc策略。
finalaze和referencequeue
對(duì)于普通的強(qiáng)引用對(duì)象,如果其變成不可達(dá)之后,通常gc會(huì)進(jìn)行finalize(finalize主要目的是讓用戶可以自定義釋放資源的過程,通常是釋放本地方法中使用的資源),然后將它的對(duì)象銷毀回收,但對(duì)于本文中討論的3種引用,還有可能在這個(gè)過程中做一些別的事情:
gc根據(jù)約定的規(guī)則來決定是否清除這些引用
這方面上一節(jié)已經(jīng)講過了,每個(gè)引用類型都有約定的處理規(guī)則。
如果它們注冊(cè)了引用隊(duì)列,在finalize對(duì)象后,將引用的對(duì)象放入隊(duì)列。
主要用來使開發(fā)者可以得到對(duì)象被銷毀的通知,當(dāng)然,如虛引用這樣的,其引用不會(huì)自動(dòng)被清除,所以它可以阻止其所引用的對(duì)象被回收。
引用(java.lang.ref.reference<t>)對(duì)象的狀態(tài)
這里所說的「引用對(duì)象」指的是由類java.lang.ref.reference<t>生產(chǎn)的對(duì)象,這個(gè)對(duì)象上保持了「需要特殊處理的」對(duì)「目標(biāo)對(duì)象」的引用。
引用對(duì)象有4種狀態(tài),根據(jù)它與其注冊(cè)的隊(duì)列的關(guān)系,分為以下4種:
active
引用對(duì)象的初始狀態(tài),表示gc要按照特殊的邏輯來處理這個(gè)對(duì)象,大致方法就是按照上一節(jié)提到的。
pending
如果一個(gè)引用對(duì)象,其注冊(cè)了隊(duì)列,在入隊(duì)之前,會(huì)進(jìn)入這個(gè)狀態(tài)。
enqueued
一個(gè)引用對(duì)象入隊(duì)后,進(jìn)入這個(gè)狀態(tài)。
inactive
一個(gè)引用對(duì)象出隊(duì)后,或者沒有注冊(cè)隊(duì)列,其隊(duì)列是一個(gè)特殊的對(duì)象java.lang.ref.referencequeue.null,表示這個(gè)對(duì)象已經(jīng)沒有用了。
幾種引用的實(shí)際應(yīng)用
日常開發(fā)工作中,用到除強(qiáng)引用之外的引用的可能性很小,只有在處理一些性能敏感的邏輯時(shí),才需要考慮使用這些特殊的引用,下面就舉幾個(gè)相關(guān)的實(shí)際例子,分析其使用場(chǎng)景。
軟引用
弱引用的使用比較簡(jiǎn)單,如guava中的localcache中就是用了softreference來做緩存。
弱引用
弱引用是使用的比較多的,從上文的描述可知:對(duì)于一個(gè)「目標(biāo)對(duì)象a」,如果還有強(qiáng)引用指向它,那么從一個(gè)弱引用就可以訪問到a,一旦沒有強(qiáng)引用指向它,那么就可以認(rèn)為,從這個(gè)弱引用就訪問不到a了(實(shí)際情況可能會(huì)有偏差)。
根據(jù)這個(gè)特點(diǎn),jdk中注釋說到,弱引用通常用來做映射表(canonicalizingmapping),總結(jié)下來映射表有這樣2個(gè)特點(diǎn):
如果表中的key(或者value)還存在強(qiáng)引用,則可以通過key訪問到value,反之則訪問不到
換句話說,只要有原始的key,就能訪問到value。
映射表本身不會(huì)影響其中key或者value的gc
在jdk中有很多個(gè)地方使用了它的這個(gè)特點(diǎn),下面是2個(gè)具有代表性的實(shí)例。
1.threadlocal
threadlocal的原理比較簡(jiǎn)單,線程中保持了一個(gè)以threadlocal為key的threadlocal.threadlocalmap對(duì)象threadlocals,其中的entry如代碼1中所示:
1
2
3
4
5
6
7
8
9
10
|
//代碼1 static class entry extends weakreference<threadlocal<?>> { /** the value associated with this threadlocal. */ object value; //其保持了對(duì)作為key的threadlocal對(duì)象的弱引用 entry(threadlocal<?> k, object v) { super (k); value = v; } } |
其引用關(guān)系如下圖所示:
threadlocal中的引用關(guān)系
從上圖可以看出,當(dāng)引用2被清除之后(threadlocal對(duì)象不再使用),如果引用4為強(qiáng)引用,則不論引用1是否還存在,只要thread對(duì)象還沒死,則對(duì)象1和對(duì)象2永遠(yuǎn)不會(huì)被釋放。
2.動(dòng)態(tài)代理
動(dòng)態(tài)代理是java世界一個(gè)十分重要的特性,對(duì)于需要做aop的業(yè)務(wù)邏輯十分重要。jdk本身提供了基于反射的動(dòng)態(tài)代理機(jī)制,其原理大致是要通過預(yù)先定義的接口(interface)來動(dòng)態(tài)的生成代理類,并將之代理到invocationhandler的實(shí)例上去。jdk的動(dòng)態(tài)代理使用起來很簡(jiǎn)單,如下代碼2中所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
//代碼2 package me.lk; import java.lang.reflect.*; public class testproxy { /** * 兩個(gè)預(yù)定義的需要被代理的接口 */ public static interface proxiedinterface { void proxiedmethod(); } public static interface proxiedinterface2 { void proxiedmethod2(); } /** * 真正的處理邏輯 */ public static class invohandler implements invocationhandler { @override public object invoke(object proxy, method method, object[] args) throws throwable { system.out.println( "in proxy:" + method.getname()); //其他邏輯 system.out.println( "in proxy end" ); return null ; } } public static void main(string[] args) { invohandler ih = new invohandler(); proxiedinterface proxy = (proxiedinterface) proxy.newproxyinstance(testproxy. class .getclassloader(), new class []{proxiedinterface. class , proxiedinterface2. class }, ih); proxy.proxiedmethod(); proxiedinterface2 p = (proxiedinterface2) proxy; p.proxiedmethod2(); } } |
動(dòng)態(tài)代理的實(shí)現(xiàn)原理
關(guān)于java中的動(dòng)態(tài)代理,我們首先需要了解的是一種常用的設(shè)計(jì)模式--代理模式,而對(duì)于代理,根據(jù)創(chuàng)建代理類的時(shí)間點(diǎn),又可以分為靜態(tài)代理和動(dòng)態(tài)代理。
代理模式是常用的java設(shè)計(jì)模式,他的特征是代理類與委托類有同樣的接口,代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等。代理類與委托類之間通常會(huì)存在關(guān)聯(lián)關(guān)系,一個(gè)代理類的對(duì)象與一個(gè)委托類的對(duì)象關(guān)聯(lián),代理類的對(duì)象本身并不真正實(shí)現(xiàn)服務(wù),而是通過調(diào)用委托類的對(duì)象的相關(guān)方法,來提供特定的服務(wù)。簡(jiǎn)單的說就是,我們?cè)谠L問實(shí)際對(duì)象時(shí),是通過代理對(duì)象來訪問的,代理模式就是在訪問實(shí)際對(duì)象時(shí)引入一定程度的間接性,因?yàn)檫@種間接性,可以附加多種用途。
參閱:java設(shè)計(jì)模式之代理模式原理及實(shí)現(xiàn)代碼分享
其實(shí)現(xiàn)原理其實(shí)也很簡(jiǎn)單,就是在方法proxy.newproxyinstance(classloader loader, class<?>[] interfaces, invocationhandler h)中動(dòng)態(tài)生成一個(gè)「實(shí)現(xiàn)了interfaces中所有接口」并「繼承于proxy」的代理類,并生成相應(yīng)的對(duì)象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//代碼3 public static object newproxyinstance(classloader loader, class <?>[] interfaces, invocationhandler h) throws illegalargumentexception { objects.requirenonnull(h); final class <?>[] intfs = interfaces.clone(); //驗(yàn)證真實(shí)調(diào)用者的權(quán)限 final securitymanager sm = system.getsecuritymanager(); if (sm != null ) { checkproxyaccess(reflection.getcallerclass(), loader, intfs); } //查詢或生成代理類 class <?> cl = getproxyclass0(loader, intfs); //驗(yàn)證調(diào)用者對(duì)代理類的權(quán)限,并生成對(duì)象 。。。省略代碼 } private static class <?> getproxyclass0(classloader loader, class <?>... interfaces) { if (interfaces.length > 65535 ) { throw new illegalargumentexception( "interface limit exceeded" ); } // 通過緩存獲取代理類 return proxyclasscache.get(loader, interfaces); } |
生成動(dòng)態(tài)類的邏輯在方法java.lang.reflect.proxy.proxyclassfactory.apply(classloader, class<?>[]),代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//代碼4 @override public class <?> apply(classloader loader, class <?>[] interfaces) { map< class <?>, boolean > interfaceset = new identityhashmap<>(interfaces.length); //驗(yàn)證接口,驗(yàn)證接口是否重復(fù),驗(yàn)證loader對(duì)接口的可見性 //生成包名和修飾符 //生成類 byte [] proxyclassfile = proxygenerator.generateproxyclass( proxyname, interfaces, accessflags); try { return defineclass0(loader, proxyname, proxyclassfile, 0 , proxyclassfile.length); } catch (classformaterror e) { /* * 生成失敗 */ throw new illegalargumentexception(e.tostring()); } } |
動(dòng)態(tài)代理中的緩存策略
為了更高效的使用動(dòng)態(tài)代理,proxy類中采用了緩存策略(代碼3中的proxyclasscache )來緩存動(dòng)態(tài)生成的代理類,由于這個(gè)緩存對(duì)象是靜態(tài)的,也就是說一旦proxy類被加載,proxyclasscache 很可能永遠(yuǎn)不會(huì)被gc回收,然而它必須要保持對(duì)其中的classloader和class的引用,如果這里使用強(qiáng)引用,則它們也隨著proxyclasscache 永遠(yuǎn)不會(huì)被gc回收。
不再使用的類和類加載器如果無法被gc,其內(nèi)存泄漏的風(fēng)險(xiǎn)很大。所以weakcache中設(shè)計(jì)為,「?jìng)魅氲念惣虞d器」和「生成的代理類」為弱引用。
類和類加載器是相互引用的,而類加載器的內(nèi)存泄漏可能會(huì)帶來很嚴(yán)重的問題,有興趣可以去看這篇文章:reloading java classes 201: how do classloader leaks happen?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//代碼5 /** * a cache of proxy classes */ //classloader 用來加載預(yù)定義接口(interface)和生成代理類的類加載器 //class<?>[] 預(yù)定義接口(interface) //class<?> 生成的代理類 private static final weakcache<classloader, class <?>[], class <?>> proxyclasscache = new weakcache<>( new keyfactory(), new proxyclassfactory()); /** * cachekey containing a weakly referenced {@code key}. it registers * itself with the {@code refqueue} so that it can be used to expunge * the entry when the {@link weakreference} is cleared. */ private static final class cachekey<k> extends weakreference<k> /** * a {@link value} that weakly references the referent. */ private static final class cachevalue<v> extends weakreference<v> implements value<v> |
從代碼5中可以看出,weakcache對(duì)象中保持了對(duì)classloader(包裝為cachekey)和代理類(包裝為cachevalue)的弱引用,所以當(dāng)此類加載器和代理類不再被強(qiáng)引用時(shí),它們就會(huì)被回收。
存在的問題
然而,weakcache的實(shí)現(xiàn)是有問題的,在java.lang.reflect.weakcache.reversemap和java.lang.reflect.weakcache.valuefactory中的狀態(tài)在極限情況下可能會(huì)出現(xiàn)不同步,導(dǎo)致一個(gè)代理類被調(diào)用java.lang.reflect.proxy.isproxyclass(class<?>)的返回值不正確。具體可以參考raceconditioninjava.lang.reflect.weakcache。
不過這個(gè)問題在jdk9中已經(jīng)不存在了。
關(guān)于虛引用的gc行為
在上一節(jié),并沒有列出虛引用的使用場(chǎng)景,因?yàn)樗氖褂脠?chǎng)景十分單一。phantomreference設(shè)計(jì)的目的就是可以在對(duì)象被回收之前收到通知(通過注冊(cè)的隊(duì)列),所以它沒有不含注冊(cè)隊(duì)列的構(gòu)造器(只有publicphantomreference(treferent,referencequeue<?supert>q),但你仍可以傳null進(jìn)去),但這種場(chǎng)景在jdk里并沒有出現(xiàn),也很少有開發(fā)者使用它。
從phantomreference類的源代碼可知,你永遠(yuǎn)無法通過它獲取到它引用的那個(gè)對(duì)象(其get()方法永遠(yuǎn)返回null),但是它又可以阻止其引用的目標(biāo)對(duì)象被gc回收。從上文可知,通常一個(gè)不可達(dá)(強(qiáng)不可達(dá)、軟不可達(dá)、弱不可達(dá))的對(duì)象會(huì)被finalize,然后被回收。但如果它在被回收前,gc發(fā)現(xiàn)它仍然是虛可達(dá),那么它就不會(huì)回收這塊內(nèi)存,而這塊內(nèi)存又不能被訪問到,那么這塊內(nèi)存就泄漏了。
想要虛引用的「目標(biāo)對(duì)象」被回收,必須讓「引用對(duì)象」本身不可達(dá),或者顯式地清除虛引用。所以如果使用不當(dāng),很可能會(huì)造成內(nèi)存泄漏,這也是它使用范圍不廣的原因之一。
代碼6演示了這3種引用分別的gc行為:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//代碼6 private static list<phantomreference<object>> phantomrefs = new arraylist<>(); private static list<weakreference<object>> weaks = new arraylist<>(); private static list<softreference<object>> softs = new arraylist<>(); public static void testphantomrefleakoom() { while ( true ) { //生成一個(gè)占用10m的內(nèi)存的對(duì)象 byte [] bytes = new byte [ 1024 * 1024 * 10 ]; //使用軟引用存儲(chǔ) // softs.add(new softreference<object>(bytes)); //使用虛引用存儲(chǔ) phantomreference<object> pf = new phantomreference<object>(bytes, null ); //使用弱引用存儲(chǔ) // weaks.add((new weakreference<object>(bytes))); phantomrefs.add(pf); //顯式清除引用 // pf.clear(); //建議gc system.gc(); } } |
以上代碼展示了4種影響gc的行為,分別是:
1. 使用軟引用的gc行為
gc日志如下,可以看到,當(dāng)系統(tǒng)內(nèi)存不夠的時(shí)候(oom之前),軟引用會(huì)被清除,引發(fā)gc,釋放內(nèi)存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
2017 - 07 -03t12: 36 : 22.995 + 0800 : [full gc (system.gc()) [psyounggen: 40971k->40960k(76288k)] [paroldgen: 492061k->492061k(506880k)] 533033k->533022k(583168k), [metaspace: 2727k->2727k(1056768k)], 0.0610620 secs] [times: user= 0.23 sys= 0.00 , real= 0.06 secs] 2017 - 07 -03t12: 36 : 24.391 + 0800 : [full gc (system.gc()) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 1065502k->1065502k(1087488k)] 1106462k->1106462k(1163776k), [metaspace: 2017 - 07 -03t12: 36 : 32.291 + 0800 : [full gc (system.gc()) [psyounggen: 40962k->40962k(76288k)] [paroldgen: 2581022k->2581022k(2621952k)] 2621985k->2621985k(2698240k), [metaspace: 2727k->2727k(1056768k)], 0.3106258 secs] [times: user= 2.31 sys= 0.00 , real= 0.31 secs] 2017 - 07 -03t12: 36 : 32.610 + 0800 : [gc (system.gc()) [psyounggen: 40962k->128k(76288k)] 2662945k->2663070k(2739712k), 0.6298054 secs] [times: user= 4.63 sys= 0.00 , real= 0.63 secs] 2017 - 07 -03t12: 36 : 33.240 + 0800 : [full gc (system.gc()) [psyounggen: 128k->0k(76288k)] [paroldgen: 2662942k->2662945k(2663424k)] 2663070k->2662945k(2739712k), [metaspace: 2727k->2727k(1056768k)], 0.2898513 secs] [times: user= 2.25 sys= 0.00 , real= 0.29 secs] 2017 - 07 -03t12: 36 : 34.096 + 0800 : [full gc (system.gc()) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744865k->2744865k(2746368k)] 2785825k->2785825k(2822656k), [metaspace: 2727k->2727k(1056768k)], 0.3282086 secs] [times: user= 2.47 sys= 0.00 , real= 0.33 secs] 2017 - 07 -03t12: 36 : 34.425 + 0800 : [full gc (ergonomics) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744865k->2744865k(2777088k)] 2785825k->2785825k(2853376k), [metaspace: 2727k->2727k(1056768k)], 0.3061587 secs] [times: user= 2.32 sys= 0.00 , real= 0.31 secs] 2017 - 07 -03t12: 36 : 34.731 + 0800 : [full gc (allocation failure) [psyounggen: 40960k->0k(76288k)] [paroldgen: 2744865k->531k(225280k)] 2785825k->531k(301568k), [metaspace: 2727k->2727k(1056768k)], 0.1559132 secs] [times: user= 0.02 sys= 0.14 , real= 0.16 secs] 2017 - 07 -03t12: 36 : 34.890 + 0800 : [gc (system.gc()) [psyounggen: 40960k->32k(76288k)] 41491k->82483k(301568k), 0.0304114 secs] [times: user= 0.14 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 36 : 34.920 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82451k->41491k(225280k)] 82483k->41491k(301568k), [metaspace: 2727k->2727k(1056768k)], 0.0179676 secs] [times: user= 0.05 sys= 0.00 , real= 0.02 secs] 2017 - 07 -03t12: 36 : 34.941 + 0800 : [gc (system.gc()) [psyounggen: 41649k->32k(76288k)] 83140k->123443k(301568k), 0.0323917 secs] [times: user= 0.11 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 36 : 34.973 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 123411k->82451k(225280k)] 123443k->82451k(301568k), [metaspace: 2727k->2727k(1056768k)], 0.0424672 secs] [times: user= 0.20 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 36 : 35.414 + 0800 : [full gc (system.gc()) [psyounggen: 41011k->40960k(76288k)] [paroldgen: 287252k->287252k(308224k)] 328264k->328212k(384512k), [metaspace: 2727k->2727k(1056768k)], 0.0520262 secs] [times: user= 0.33 sys= 0.00 , real= 0.05 secs] 2017 - 07 -03t12: 36 : 48.569 + 0800 : [full gc (ergonomics) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744854k->2744854k(2777088k)] 2785815k->2785815k(2853376k), [metaspace: 2727k->2727k(1056768k)], 0.3476025 secs] [times: user= 2.45 sys= 0.02 , real= 0.35 secs] 2017 - 07 -03t12: 36 : 48.916 + 0800 : [full gc (allocation failure) [psyounggen: 40960k->0k(76288k)] [paroldgen: 2744854k->534k(444928k)] 2785815k->534k(521216k), [metaspace: 2727k->2727k(1056768k)], 0.1644360 secs] [times: user= 0.02 sys= 0.16 , real= 0.17 secs] 2017 - 07 -03t12: 36 : 49.084 + 0800 : [gc (system.gc()) [psyounggen: 40960k->32k(76288k)] 41494k->82486k(521216k), 0.0444057 secs] [times: user= 0.22 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 36 : 49.128 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82454k->41494k(444928k)] 82486k->41494k(521216k), [metaspace: 2727k->2727k(1056768k)], 0.0288512 secs] [times: user= 0.11 sys= 0.00 , real= 0.03 secs] |
2. 使用弱引用
gc日志如下,從中可以看到,弱引用所引用的目標(biāo)對(duì)象,時(shí)時(shí)刻刻都在被gc。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
2017 - 07 -03t12: 32 : 55.214 + 0800 : [gc (system.gc()) [psyounggen: 43581k->728k(76288k)] 43581k->41696k(251392k), 0.0354037 secs] [times: user= 0.20 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 32 : 55.252 + 0800 : [full gc (system.gc()) [psyounggen: 728k->0k(76288k)] [paroldgen: 40968k->41502k(175104k)] 41696k->41502k(251392k), [metaspace: 2726k->2726k(1056768k)], 0.0258447 secs] [times: user= 0.08 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 32 : 55.533 + 0800 : [full gc (system.gc()) [psyounggen: 41309k->40960k(76288k)] [paroldgen: 164381k->164381k(175104k)] 205690k->205341k(251392k), [metaspace: 2726k->2726k(1056768k)], 0.0389489 secs] [times: user= 0.25 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 32 : 57.413 + 0800 : [full gc (system.gc()) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 1024541k->1024541k(1046016k)] 1065502k->1065502k(1122304k), [metaspace: 2726k->2726k(1056768k)], 0.1263574 secs] [times: user= 0.94 sys= 0.00 , real= 0.13 secs] 2017 - 07 -03t12: 33 : 05.364 + 0800 : [full gc (system.gc()) [psyounggen: 40962k->40962k(76288k)] [paroldgen: 2581022k->2581022k(2621952k)] 2621984k->2621984k(2698240k), [metaspace: 2726k->2726k(1056768k)], 0.2474419 secs] [times: user= 1.69 sys= 0.00 , real= 0.25 secs] 2017 - 07 -03t12: 33 : 07.447 + 0800 : [full gc (ergonomics) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744864k->2744864k(2777088k)] 2785824k->2785824k(2853376k), [metaspace: 2726k->2726k(1056768k)], 0.2825105 secs] [times: user= 1.79 sys= 0.00 , real= 0.28 secs] 2017 - 07 -03t12: 33 : 07.729 + 0800 : [full gc (allocation failure) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744864k->2744851k(2777088k)] 2785824k->2785812k(2853376k), [metaspace: 2726k->2726k(1056768k)], 0.8902204 secs]exception in thread "main" java.lang.outofmemoryerror: java heap space at me.lk.testreference.testphantomrefleakoom(testreference.java: 109 ) at me.lk.testreference.main(testreference.java: 50 ) [times: user= 3.79 sys= 0.00 , real= 0.89 secs] heap psyounggen total 76288k, used 43025k [ 0x000000076b400000 , 0x0000000770900000 , 0x00000007c0000000 ) eden space 65536k, 65 % used [ 0x000000076b400000 , 0x000000076de04408 , 0x000000076f400000 ) from space 10752k, 0 % used [ 0x000000076f400000 , 0x000000076f400000 , 0x000000076fe80000 ) to space 10752k, 0 % used [ 0x000000076fe80000 , 0x000000076fe80000 , 0x0000000770900000 ) paroldgen total 2777088k, used 2744851k [ 0x00000006c1c00000 , 0x000000076b400000 , 0x000000076b400000 ) object space 2777088k, 98 % used [ 0x00000006c1c00000 , 0x0000000769484fb8 , 0x000000076b400000 ) metaspace used 2757k, capacity 4490k, committed 4864k, reserved 1056768k class space used 310k, capacity 386k, committed 512k, reserved 1048576k |
3. 使用虛引用,不顯式清除
gc日志如下,可以看到,不顯式清除的虛引用會(huì)阻止gc回收內(nèi)存,最終導(dǎo)致oom。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
2017 - 07 -03t12: 32 : 55.214 + 0800 : [gc (system.gc()) [psyounggen: 43581k->728k(76288k)] 43581k->41696k(251392k), 0.0354037 secs] [times: user= 0.20 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 32 : 55.252 + 0800 : [full gc (system.gc()) [psyounggen: 728k->0k(76288k)] [paroldgen: 40968k->41502k(175104k)] 41696k->41502k(251392k), [metaspace: 2726k->2726k(1056768k)], 0.0258447 secs] [times: user= 0.08 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 32 : 55.533 + 0800 : [full gc (system.gc()) [psyounggen: 41309k->40960k(76288k)] [paroldgen: 164381k->164381k(175104k)] 205690k->205341k(251392k), [metaspace: 2726k->2726k(1056768k)], 0.0389489 secs] [times: user= 0.25 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 32 : 57.413 + 0800 : [full gc (system.gc()) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 1024541k->1024541k(1046016k)] 1065502k->1065502k(1122304k), [metaspace: 2726k->2726k(1056768k)], 0.1263574 secs] [times: user= 0.94 sys= 0.00 , real= 0.13 secs] 2017 - 07 -03t12: 33 : 05.364 + 0800 : [full gc (system.gc()) [psyounggen: 40962k->40962k(76288k)] [paroldgen: 2581022k->2581022k(2621952k)] 2621984k->2621984k(2698240k), [metaspace: 2726k->2726k(1056768k)], 0.2474419 secs] [times: user= 1.69 sys= 0.00 , real= 0.25 secs] 2017 - 07 -03t12: 33 : 07.447 + 0800 : [full gc (ergonomics) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744864k->2744864k(2777088k)] 2785824k->2785824k(2853376k), [metaspace: 2726k->2726k(1056768k)], 0.2825105 secs] [times: user= 1.79 sys= 0.00 , real= 0.28 secs] 2017 - 07 -03t12: 33 : 07.729 + 0800 : [full gc (allocation failure) [psyounggen: 40960k->40960k(76288k)] [paroldgen: 2744864k->2744851k(2777088k)] 2785824k->2785812k(2853376k), [metaspace: 2726k->2726k(1056768k)], 0.8902204 secs]exception in thread "main" java.lang.outofmemoryerror: java heap space at me.lk.testreference.testphantomrefleakoom(testreference.java: 109 ) at me.lk.testreference.main(testreference.java: 50 ) [times: user= 3.79 sys= 0.00 , real= 0.89 secs] heap psyounggen total 76288k, used 43025k [ 0x000000076b400000 , 0x0000000770900000 , 0x00000007c0000000 ) eden space 65536k, 65 % used [ 0x000000076b400000 , 0x000000076de04408 , 0x000000076f400000 ) from space 10752k, 0 % used [ 0x000000076f400000 , 0x000000076f400000 , 0x000000076fe80000 ) to space 10752k, 0 % used [ 0x000000076fe80000 , 0x000000076fe80000 , 0x0000000770900000 ) paroldgen total 2777088k, used 2744851k [ 0x00000006c1c00000 , 0x000000076b400000 , 0x000000076b400000 ) object space 2777088k, 98 % used [ 0x00000006c1c00000 , 0x0000000769484fb8 , 0x000000076b400000 ) metaspace used 2757k, capacity 4490k, committed 4864k, reserved 1056768k class space used 310k, capacity 386k, committed 512k, reserved 1048576k |
4. 使用虛引用,顯式清除
顯式清除的虛引用,不會(huì)影響gc,其gc行為和弱引用十分相似。
1
2
3
4
5
6
7
8
9
10
11
12
|
2017 - 07 -03t12: 45 : 14.774 + 0800 : [gc (system.gc()) [psyounggen: 43581k->696k(76288k)] 43581k->41664k(251392k), 0.0458469 secs] [times: user= 0.17 sys= 0.00 , real= 0.05 secs] 2017 - 07 -03t12: 45 : 14.820 + 0800 : [full gc (system.gc()) [psyounggen: 696k->0k(76288k)] [paroldgen: 40968k->41502k(175104k)] 41664k->41502k(251392k), [metaspace: 2726k->2726k(1056768k)], 0.0198788 secs] [times: user= 0.08 sys= 0.00 , real= 0.02 secs] 2017 - 07 -03t12: 45 : 14.842 + 0800 : [gc (system.gc()) [psyounggen: 42231k->32k(76288k)] 83734k->82495k(251392k), 0.0367363 secs] [times: user= 0.22 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 45 : 14.879 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82463k->41501k(175104k)] 82495k->41501k(251392k), [metaspace: 2727k->2727k(1056768k)], 0.0198085 secs] [times: user= 0.08 sys= 0.00 , real= 0.02 secs] 2017 - 07 -03t12: 45 : 14.901 + 0800 : [gc (system.gc()) [psyounggen: 41786k->32k(76288k)] 83287k->82493k(251392k), 0.0327529 secs] [times: user= 0.19 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 45 : 14.934 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82461k->41501k(175104k)] 82493k->41501k(251392k), [metaspace: 2727k->2727k(1056768k)], 0.0283782 secs] [times: user= 0.17 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 45 : 14.964 + 0800 : [gc (system.gc()) [psyounggen: 41497k->32k(76288k)] 82998k->82493k(251392k), 0.0336216 secs] [times: user= 0.20 sys= 0.00 , real= 0.03 secs] 2017 - 07 -03t12: 45 : 14.998 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82461k->41501k(175104k)] 82493k->41501k(251392k), [metaspace: 2727k->2727k(1056768k)], 0.0211702 secs] [times: user= 0.13 sys= 0.00 , real= 0.02 secs] 2017 - 07 -03t12: 45 : 15.021 + 0800 : [gc (system.gc()) [psyounggen: 41309k->32k(76288k)] 82810k->82493k(251392k), 0.0445368 secs] [times: user= 0.30 sys= 0.00 , real= 0.05 secs] 2017 - 07 -03t12: 45 : 15.066 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82461k->41501k(175104k)] 82493k->41501k(251392k), [metaspace: 2727k->2727k(1056768k)], 0.0219968 secs] [times: user= 0.11 sys= 0.00 , real= 0.02 secs] 2017 - 07 -03t12: 45 : 15.090 + 0800 : [gc (system.gc()) [psyounggen: 41186k->32k(76288k)] 82688k->82493k(251392k), 0.0436528 secs] [times: user= 0.36 sys= 0.00 , real= 0.04 secs] 2017 - 07 -03t12: 45 : 15.133 + 0800 : [full gc (system.gc()) [psyounggen: 32k->0k(76288k)] [paroldgen: 82461k->41501k(175104k)] 82493k->41501k(251392k), [metaspace: 2727k->2727k(1056768k)], 0.0219814 secs] [times: user= 0.11 sys= 0.00 , real= 0.02 secs] |
總結(jié)
以上就是本文關(guān)于java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn)詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持。
原文鏈接:http://www.jianshu.com/p/77dfeccac85d