JDK解構 - Java中的引用和動態代理的實現

Alchemist發表於2017-07-03

我們知道,動態代理(這裡指JDK的動態代理)與靜態代理的區別在於,其真實的代理類是動態生成的。但具體是怎麼生成,生成的代理類包含了哪些內容,以什麼形式存在,它為什麼一定要以介面為基礎?

如果去看動態代理的原始碼(java.lang.reflect.Proxy),會發現其原理很簡單(真正二進位制類檔案的生成是在本地方法中完成,原始碼中沒有),但其中用到了一個緩衝類java.lang.reflect.WeakCache<ClassLoader, Class<?>[], Class<?>>,這個類用到了弱引用來構建。

在JDK的3個特殊引用中,弱引用是使用範圍最廣的,它的特性也最清晰,相對而言,其他兩種邏輯稍顯晦澀,原始碼中的註釋也語焉不詳。本文將簡單介紹幾種引用的行為特徵,然後分析一下弱引用的一些實際應用場景,其中包含了動態代理中的實現。本文將包含以下內容:

  1. JDK中的引用型別
  2. 不同引用型別對GC行為的影響
  3. 引用型別的實現
  4. ThreadLocal對弱引用的使用
  5. 動態代理對弱引用的實現
  6. 虛引用如何導致記憶體洩漏

JDK中「引用(Reference)」的型別

Java的所有執行邏輯都是基於引用的,其形態類似於不可變的指標,所以在Java中會有一些很繞的概念,比如說, Java中函式的傳參是值傳遞,但這裡所說的值,其實是引用的值,所以你可以通過一個函式的引數來修改其物件的值。另一方面,Java中還存在一些基本資料型別,它們沒有引用,傳遞的是真實的值,所以不能在函式內部修改引數的值。

關於引用,Java中有這樣幾種:

1. 強引用

所有物件的引用預設為強引用,普通程式碼中,賦值語句之間傳遞的都是強引用,如果一個物件可以被某個執行緒(活著的,下同)通過強引用訪問到,則稱之為強可達的(Strongly Reachable)。

強可達的物件不會被GC回收。

2. 軟引用(SoftReference

當一個物件不是強可達的,但可以被某個執行緒通過軟引用訪問到,則稱之為軟可達的(Softly Reachable)。

軟可達的物件的引用只有在記憶體不足時會被清除,使之可以被GC回收。在這一點上,JVM是不保證具體什麼時候清除軟引用,但可以保證在OOM之前會清除軟可達的物件。同時,JVM也不保證軟可達的物件的回收順序,但Oracle JDK的文件中提到,最近建立和最近使用的軟可達物件往往會最後被回收,與LRU類似。

3. 弱引用(WeakReference

當一個物件不是強可達的,也不是軟可達的,但可以被某個執行緒通過弱引用訪問到,則稱之為弱可達的(Weakly Reachable)。

弱引用是除了強引用之外,使用最廣泛的引用型別,它的特性也更簡單,當一個物件是弱可達時,JVM就會清除這個物件上的弱引用,隨後對這個物件進行回收動作。

4. 虛引用(PhantomReference

當一個物件,通過以上幾種可達性分析都不可達,且已經finalized,但有虛引用指向它,則它是虛可達的(Phantom Reachable)。

虛引用是行為最詭異,使用方法最難的引用,後邊會講到。

虛引用不會影響GC,它的get()方法永遠返回null,唯一的用處就是在GC finalize一個物件之後,它會被放到指定的佇列中去。關於這個佇列會在下邊說明。

不同GC行為背後的原理

JVM GC的可達性分析

JVM的GC通過可達性分析來判斷一個物件是否可被回收,其基本思路就是以GC Roots為起點遍歷鏈路上所有物件,當一個物件和GC Roots之間沒有任何的直接或間接引用相連時,就稱之為不可達物件,則證明此物件是不可用的。

而進一步,Java中又定義瞭如上所述的4種不同的可達性,用來實現更精細的GC策略。

Finalaze和ReferenceQueue

對於普通的強引用物件,如果其變成不可達之後,通常GC會進行Finalize(Finalize主要目的是讓使用者可以自定義釋放資源的過程,通常是釋放本地方法中使用的資源),然後將它的物件銷燬回收,但對於本文中討論的3種引用,還有可能在這個過程中做一些別的事情:

  1. GC根據約定的規則來決定是否清除這些引用

    這方面上一節已經講過了,每個引用型別都有約定的處理規則。

  2. 如果它們註冊了引用佇列,在Finalize物件後,將引用的物件放入佇列。

    主要用來使開發者可以得到物件被銷燬的通知,當然,如虛引用這樣的,其引用不會自動被清除,所以它可以阻止其所引用的物件被回收。

引用(java.lang.ref.Reference<T>)物件的狀態

這裡所說的「引用物件」指的是由類java.lang.ref.Reference<T>生產的物件,這個物件上保持了「需要特殊處理的」對「目標物件」的引用。

引用物件有4種狀態,根據它與其註冊的佇列的關係,分為以下4種:

  1. Active
    引用物件的初始狀態,表示GC要按照特殊的邏輯來處理這個物件,大致方法就是按照上一節提到的。

  2. Pending
    如果一個引用物件,其註冊了佇列,在入隊之前,會進入這個狀態。

  3. Enqueued

    一個引用物件入隊後,進入這個狀態。

  4. Inactive

一個引用物件出隊後,或者沒有註冊佇列,其佇列是一個特殊的物件java.lang.ref.ReferenceQueue.NULL,表示這個物件已經沒有用了。

幾種引用的實際應用

日常開發工作中,用到除強引用之外的引用的可能性很小,只有在處理一些效能敏感的邏輯時,才需要考慮使用這些特殊的引用,下面就舉幾個相關的實際例子,分析其使用場景。

軟引用

弱引用的使用比較簡單,如Guava中的LocalCache中就是用了SoftReference來做快取。

弱引用

弱引用是使用的比較多的,從上文的描述可知:對於一個「目標物件A」,如果還有強引用指向它,那麼從一個弱引用就可以訪問到A,一旦沒有強引用指向它,那麼就可以認為,從這個弱引用就訪問不到A了(實際情況可能會有偏差)。

根據這個特點,JDK中註釋說到,弱引用通常用來做對映表( canonicalizing mapping),總結下來對映表有這樣2個特點:

  1. 如果表中的Key(或者Value)還存在強引用,則可以通過Key訪問到Value,反之則訪問不到
    換句話說,只要有原始的Key,就能訪問到Value。

  2. 對映表本身不會影響其中Key或者Value的GC

在JDK中有很多個地方使用了它的這個特點,下面是2個具有代表性的例項。

1. ThreadLocal

ThreadLocal的原理比較簡單,執行緒中保持了一個以ThreadLocal為Key的ThreadLocal.ThreadLocalMap物件threadLocals,其中的Entry如程式碼1中所示:

//程式碼1
static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;
    //其保持了對作為Key的ThreadLocal物件的弱引用
    Entry(ThreadLocal> k, Object v) {
        super(k);
        value = v;
    }
}複製程式碼

其引用關係如下圖所示:

ThreadLocal中的引用關係
ThreadLocal中的引用關係

從上圖可以看出,當引用2被清除之後(ThreadLocal物件不再使用),如果引用4為強引用,則不論引用1是否還存在,只要Thread物件還沒死,則物件1和物件2永遠不會被釋放。

2. 動態代理

動態代理是Java世界一個十分重要的特性,對於需要做AOP的業務邏輯十分重要。JDK本身提供了基於反射的動態代理機制,其原理大致是要通過預先定義的介面(interface)來動態的生成代理類,並將之代理到InvocationHandler的例項上去。JDK的動態代理使用起來很簡單,如下程式碼2中所示:

//程式碼2
package me.lk;

import java.lang.reflect.*;

public class TestProxy {
    /**
     * 兩個預定義的需要被代理的介面
     */
    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();
    }
}複製程式碼
動態代理的實現原理

其實現原理其實也很簡單,就是在方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)中動態生成一個「實現了interfaces中所有介面」並「繼承於Proxy」的代理類,並生成相應的物件。

//程式碼3
public static Object newProxyInstance(ClassLoader loader,
                                          Class>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class>[] intfs = interfaces.clone();
        //驗證真實呼叫者的許可權
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //查詢或生成代理類
        Class> cl = getProxyClass0(loader, intfs);

        //驗證呼叫者對代理類的許可權,並生成物件
        。。。省略程式碼
    }

    private static Class> getProxyClass0(ClassLoader loader,
                                       Class>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 通過快取獲取代理類
        return proxyClassCache.get(loader, interfaces);
    }複製程式碼

生成動態類的邏輯在方法java.lang.reflect.Proxy.ProxyClassFactory.apply(ClassLoader, Class<?>[]),程式碼如下:

//程式碼4
@Override
public Class> apply(ClassLoader loader, Class>[] interfaces) {

    Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    //驗證介面,驗證介面是否重複,驗證loader對介面的可見性

    //生成包名和修飾符

    //生成類
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        /*
         * 生成失敗
         */
        throw new IllegalArgumentException(e.toString());
    }
}複製程式碼
動態代理中的快取策略

為了更高效的使用動態代理,Proxy類中採用了快取策略(程式碼3中的proxyClassCache )來快取動態生成的代理類,由於這個快取物件是靜態的,也就是說一旦Proxy類被載入,proxyClassCache 很可能永遠不會被GC回收,然而它必須要保持對其中的ClassLoader和Class的引用,如果這裡使用強引用,則它們也隨著proxyClassCache 永遠不會被GC回收。

不再使用的類和類載入器如果無法被GC,其記憶體洩漏的風險很大。所以WeakCache中設計為,「傳入的類載入器」和「生成的代理類」為弱引用。

類和類載入器是相互引用的,而類載入器的記憶體洩漏可能會帶來很嚴重的問題,有興趣可以去看這篇文章:Reloading Java Classes 201: How do ClassLoader leaks happen?

//程式碼5
/**
 * a cache of proxy classes
 */
//ClassLoader    用來載入預定義介面(interface)和生成代理類的類載入器
//Class>[]     預定義介面(interface)
//Class>       生成的代理類
private static final WeakCache[], 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 extends WeakReference 

/**
 * A {@link Value} that weakly references the referent.
 */
private static final class CacheValue
    extends WeakReference implements Value複製程式碼

從程式碼5中可以看出,WeakCache物件中保持了對ClassLoader(包裝為CacheKey)和代理類(包裝為CacheValue)的弱引用,所以當此類載入器和代理類不再被強引用時,它們就會被回收。

存在的問題

然而,WeakCache的實現是有問題的,在java.lang.reflect.WeakCache.reverseMapjava.lang.reflect.WeakCache.valueFactory中的狀態在極限情況下可能會出現不同步,導致一個代理類被呼叫java.lang.reflect.Proxy.isProxyClass(Class<?>)的返回值不正確。具體可以參考Race Condition in java.lang.reflect.WeakCache

不過這個問題在JDK9中已經不存在了。

關於虛引用的GC行為

在上一節,並沒有列出虛引用的使用場景,因為它的使用場景十分單一。PhantomReference設計的目的就是可以在物件被回收之前收到通知(通過註冊的佇列),所以它沒有不含註冊佇列的構造器(只有public PhantomReference(T referent, ReferenceQueue<? super T> q),但你仍可以傳null進去),但這種場景在JDK裡並沒有出現,也很少有開發者使用它。

從PhantomReference類的原始碼可知,你永遠無法通過它獲取到它引用的那個物件(其get()方法永遠返回null),但是它又可以阻止其引用的目標物件被GC回收。從上文可知,通常一個不可達(強不可達、軟不可達、弱不可達)的物件會被finalize,然後被回收。但如果它在被回收前,GC發現它仍然是虛可達,那麼它就不會回收這塊記憶體,而這塊記憶體又不能被訪問到,那麼這塊記憶體就洩漏了。

想要虛引用的「目標物件」被回收,必須讓「引用物件」本身不可達,或者顯式地清除虛引用。所以如果使用不當,很可能會造成記憶體洩漏,這也是它使用範圍不廣的原因之一。

程式碼6演示了這3種引用分別的GC行為:

//程式碼6
private static List> phantomRefs = new ArrayList<>();
private static List> weaks = new ArrayList<>();
private static List> softs = new ArrayList<>();

public static void testPhantomRefLeakOOM() {
    while(true) {
        //生成一個佔用10M的記憶體的物件
        Byte[] bytes = new Byte[1024 * 1024 * 10];
        //使用軟引用儲存
//      softs.add(new SoftReference(bytes));
        //使用虛引用儲存
        PhantomReference pf = new PhantomReference(bytes, null);
        //使用弱引用儲存
//      weaks.add((new WeakReference(bytes)));
        phantomRefs.add(pf);
        //顯式清除引用
//      pf.clear();
        //建議GC
        System.gc();
    }
}複製程式碼

以上程式碼展示了4種影響GC的行為,分別是:

1. 使用軟引用的GC行為

GC日誌如下,可以看到,當系統記憶體不夠的時候(OOM之前),軟引用會被清除,引發GC,釋放記憶體。

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日誌如下,從中可以看到,弱引用所引用的目標物件,時時刻刻都在被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日誌如下,可以看到,不顯式清除的虛引用會阻止GC回收記憶體,最終導致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. 使用虛引用,顯式清除

顯式清除的虛引用,不會影響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]複製程式碼

相關文章