Java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn)詳解
我們知道,動(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)制類文件的生成是在本地方法中完成,源代碼中沒(méi)有),但其中用到了一個(gè)緩沖類java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>>,這個(gè)類用到了弱引用來(lái)構(gòu)建。
在JDK的3個(gè)特殊引用中,弱引用是使用范圍最廣的,它的特性也最清晰,相對(duì)而言,其他兩種邏輯稍顯晦澀,源碼中的注釋也語(yǔ)焉不詳。本文將簡(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ì)有一些很繞的概念,比如說(shuō),Java中函數(shù)的傳參是值傳遞,但這里所說(shuō)的值,其實(shí)是引用的值,所以你可以通過(guò)一個(gè)函數(shù)的參數(shù)來(lái)修改其對(duì)象的值。另一方面,Java中還存在一些基本數(shù)據(jù)類型,它們沒(méi)有引用,傳遞的是真實(shí)的值,所以不能在函數(shù)內(nèi)部修改參數(shù)的值。
關(guān)于引用,Java中有這樣幾種:
1.強(qiáng)引用
所有對(duì)象的引用默認(rèn)為強(qiáng)引用,普通代碼中,賦值語(yǔ)句之間傳遞的都是強(qiáng)引用,如果一個(gè)對(duì)象可以被某個(gè)線程(活著的,下同)通過(guò)強(qiáng)引用訪問(wèn)到,則稱之為強(qiáng)可達(dá)的(StronglyReachable)。
強(qiáng)可達(dá)的對(duì)象不會(huì)被GC回收。
2.軟引用(SoftReference<T>)
當(dāng)一個(gè)對(duì)象不是強(qiáng)可達(dá)的,但可以被某個(gè)線程通過(guò)軟引用訪問(wèn)到,則稱之為軟可達(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í)被回收,可以參考Oracle的文檔。
3.弱引用(WeakReference<T>)
當(dāng)一個(gè)對(duì)象不是強(qiáng)可達(dá)的,也不是軟可達(dá)的,但可以被某個(gè)線程通過(guò)弱引用訪問(wèn)到,則稱之為弱可達(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ì)象,通過(guò)以上幾種可達(dá)性分析都不可達(dá),且已經(jīng)finalized,但有虛引用指向它,則它是虛可達(dá)的(PhantomReachable)。
虛引用是行為最詭異,使用方法最難的引用,后邊會(huì)講到。
虛引用不會(huì)影響GC,它的get()方法永遠(yuǎn)返回null,唯一的用處就是在GCfinalize一個(gè)對(duì)象之后,它會(huì)被放到指定的隊(duì)列中去。關(guān)于這個(gè)隊(duì)列會(huì)在下邊說(shuō)明。
不同GC行為背后的原理
JVMGC的可達(dá)性分析
JVM的GC通過(guò)可達(dá)性分析來(lái)判斷一個(gè)對(duì)象是否可被回收,其基本思路就是以GCRoots為起點(diǎn)遍歷鏈路上所有對(duì)象,當(dāng)一個(gè)對(duì)象和GCRoots之間沒(méi)有任何的直接或間接引用相連時(shí),就稱之為不可達(dá)對(duì)象,則證明此對(duì)象是不可用的。
而進(jìn)一步,Java中又定義了如上所述的4種不同的可達(dá)性,用來(lái)實(shí)現(xiàn)更精細(xì)的GC策略。
Finalaze和ReferenceQueue
對(duì)于普通的強(qiáng)引用對(duì)象,如果其變成不可達(dá)之后,通常GC會(huì)進(jìn)行Finalize(Finalize主要目的是讓用戶可以自定義釋放資源的過(guò)程,通常是釋放本地方法中使用的資源),然后將它的對(duì)象銷毀回收,但對(duì)于本文中討論的3種引用,還有可能在這個(gè)過(guò)程中做一些別的事情:
GC根據(jù)約定的規(guī)則來(lái)決定是否清除這些引用
這方面上一節(jié)已經(jīng)講過(guò)了,每個(gè)引用類型都有約定的處理規(guī)則。
如果它們注冊(cè)了引用隊(duì)列,在Finalize對(duì)象后,將引用的對(duì)象放入隊(duì)列。
主要用來(lái)使開(kāi)發(fā)者可以得到對(duì)象被銷毀的通知,當(dāng)然,如虛引用這樣的,其引用不會(huì)自動(dòng)被清除,所以它可以阻止其所引用的對(duì)象被回收。
引用(java.lang.ref.Reference<T>)對(duì)象的狀態(tài)
這里所說(shuō)的「引用對(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要按照特殊的邏輯來(lái)處理這個(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ì)后,或者沒(méi)有注冊(cè)隊(duì)列,其隊(duì)列是一個(gè)特殊的對(duì)象java.lang.ref.ReferenceQueue.NULL,表示這個(gè)對(duì)象已經(jīng)沒(méi)有用了。
幾種引用的實(shí)際應(yīng)用
日常開(kāi)發(fā)工作中,用到除強(qiáng)引用之外的引用的可能性很小,只有在處理一些性能敏感的邏輯時(shí),才需要考慮使用這些特殊的引用,下面就舉幾個(gè)相關(guān)的實(shí)際例子,分析其使用場(chǎng)景。
軟引用
弱引用的使用比較簡(jiǎn)單,如Guava中的LocalCache中就是用了SoftReference來(lái)做緩存。
弱引用
弱引用是使用的比較多的,從上文的描述可知:對(duì)于一個(gè)「目標(biāo)對(duì)象A」,如果還有強(qiáng)引用指向它,那么從一個(gè)弱引用就可以訪問(wèn)到A,一旦沒(méi)有強(qiáng)引用指向它,那么就可以認(rèn)為,從這個(gè)弱引用就訪問(wèn)不到A了(實(shí)際情況可能會(huì)有偏差)。
根據(jù)這個(gè)特點(diǎn),JDK中注釋說(shuō)到,弱引用通常用來(lái)做映射表(canonicalizingmapping),總結(jié)下來(lái)映射表有這樣2個(gè)特點(diǎn):
如果表中的Key(或者Value)還存在強(qiáng)引用,則可以通過(guò)Key訪問(wèn)到Value,反之則訪問(wèn)不到
換句話說(shuō),只要有原始的Key,就能訪問(wèn)到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
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ì)象還沒(méi)死,則對(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ī)制,其原理大致是要通過(guò)預(yù)先定義的接口(interface)來(lái)動(dòng)態(tài)的生成代理類,并將之代理到InvocationHandler的實(shí)例上去。JDK的動(dòng)態(tài)代理使用起來(lái)很簡(jiǎn)單,如下代碼2中所示:
//代碼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ù)處理消息、過(guò)濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等。代理類與委托類之間通常會(huì)存在關(guān)聯(lián)關(guān)系,一個(gè)代理類的對(duì)象與一個(gè)委托類的對(duì)象關(guān)聯(lián),代理類的對(duì)象本身并不真正實(shí)現(xiàn)服務(wù),而是通過(guò)調(diào)用委托類的對(duì)象的相關(guān)方法,來(lái)提供特定的服務(wù)。簡(jiǎn)單的說(shuō)就是,我們?cè)谠L問(wèn)實(shí)際對(duì)象時(shí),是通過(guò)代理對(duì)象來(lái)訪問(wèn)的,代理模式就是在訪問(wèn)實(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ì)象。
//代碼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");
}
// 通過(guò)緩存獲取代理類
return proxyClassCache.get(loader, interfaces);
}
生成動(dòng)態(tài)類的邏輯在方法java.lang.reflect.Proxy.ProxyClassFactory.apply(ClassLoader, Class<?>[]),代碼如下:
//代碼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ì)接口的可見(jiàn)性
//生成包名和修飾符
//生成類
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 )來(lái)緩存動(dòng)態(tài)生成的代理類,由于這個(gè)緩存對(duì)象是靜態(tài)的,也就是說(shuō)一旦Proxy類被加載,proxyClassCache 很可能永遠(yuǎn)不會(huì)被GC回收,然而它必須要保持對(duì)其中的ClassLoader和Class的引用,如果這里使用強(qiáng)引用,則它們也隨著proxyClassCache 永遠(yuǎn)不會(huì)被GC回收。
不再使用的類和類加載器如果無(wú)法被GC,其內(nèi)存泄漏的風(fēng)險(xiǎn)很大。所以WeakCache中設(shè)計(jì)為,「?jìng)魅氲念惣虞d器」和「生成的代理類」為弱引用。
類和類加載器是相互引用的,而類加載器的內(nèi)存泄漏可能會(huì)帶來(lái)很嚴(yán)重的問(wèn)題,有興趣可以去看這篇文章:Reloading Java Classes 201: How do ClassLoader leaks happen?
//代碼5
/**
* a cache of proxy classes
*/
//ClassLoader 用來(lái)加載預(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ì)被回收。
存在的問(wèn)題
然而,WeakCache的實(shí)現(xiàn)是有問(wè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。
不過(guò)這個(gè)問(wèn)題在JDK9中已經(jīng)不存在了。
關(guān)于虛引用的GC行為
在上一節(jié),并沒(méi)有列出虛引用的使用場(chǎng)景,因?yàn)樗氖褂脠?chǎng)景十分單一。PhantomReference設(shè)計(jì)的目的就是可以在對(duì)象被回收之前收到通知(通過(guò)注冊(cè)的隊(duì)列),所以它沒(méi)有不含注冊(cè)隊(duì)列的構(gòu)造器(只有publicPhantomReference(Treferent,ReferenceQueue<?superT>q),但你仍可以傳null進(jìn)去),但這種場(chǎng)景在JDK里并沒(méi)有出現(xiàn),也很少有開(kāi)發(fā)者使用它。
從PhantomReference類的源代碼可知,你永遠(yuǎn)無(wú)法通過(guò)它獲取到它引用的那個(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)存又不能被訪問(wèn)到,那么這塊內(nèi)存就泄漏了。
想要虛引用的「目標(biāo)對(duì)象」被回收,必須讓「引用對(duì)象」本身不可達(dá),或者顯式地清除虛引用。所以如果使用不當(dāng),很可能會(huì)造成內(nèi)存泄漏,這也是它使用范圍不廣的原因之一。
代碼6演示了這3種引用分別的GC行為:
//代碼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)存。
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。
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。
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行為和弱引用十分相似。
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ì)大家有所幫助。感興趣的朋友可以繼續(xù)啊參閱本站:
Java使用代理進(jìn)行網(wǎng)絡(luò)連接方法示例
Java設(shè)計(jì)模式之代理模式原理及實(shí)現(xiàn)代碼分享
如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持。
相關(guān)文章
JDK 7U15在 Windows x86平臺(tái)下的安裝方法
本文給大家分享的是如何在windows平臺(tái)下安裝JDK最新版的方法,十分的簡(jiǎn)單全面,有需要的小伙伴可以參考下2016-05-05
如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問(wèn)題
這篇文章主要介紹了如何保證RabbitMQ全鏈路數(shù)據(jù)100%不丟失問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
java并發(fā)訪問(wèn)重復(fù)請(qǐng)求過(guò)濾問(wèn)題
本篇文章給大家分享了關(guān)于java并發(fā)訪問(wèn)重復(fù)請(qǐng)求過(guò)濾的相關(guān)問(wèn)題以及解決方法,對(duì)此有需要的朋友參考學(xué)習(xí)下。2018-05-05
親手教你SpringBoot中的多數(shù)據(jù)源集成問(wèn)題
本文主要是介紹基于springboot的多數(shù)據(jù)源切換,輕量級(jí)的一種集成方案,對(duì)于小型的應(yīng)用可以采用這種方案,我之前在項(xiàng)目中用到是因?yàn)楹?jiǎn)單,便于擴(kuò)展以及優(yōu)化,對(duì)SpringBoot多數(shù)據(jù)源集成問(wèn)題感興趣的朋友一起看看吧2022-03-03
Java 獲取原始請(qǐng)求域名實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Java 獲取原始請(qǐng)求域名實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
詳解SpringBoot同時(shí)可以處理多少請(qǐng)求
在日常操作中,相信很多人在SpringBoot能同時(shí)處理多少請(qǐng)求問(wèn)題上存在疑惑,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-06-06

