理解Java四種引用類型——強(qiáng)軟弱虛

Java中引用分為4種類型,按照引用強(qiáng)度遞減分別是強(qiáng)引用惶傻、軟引用棍郎、弱引用、虛引用银室。
這里引用強(qiáng)度指的是被gc回收的存活傾向涂佃。

  • 強(qiáng)引用 只要存在、指向的對象就不會被gc回收蜈敢。
  • 軟引用 發(fā)生gc時(shí)若內(nèi)存不足辜荠,則會回收指向的對象。
  • 弱引用 只要發(fā)生gc抓狭、所指向的對象就會被回收伯病。
  • 虛引用 所指向的對象獲取不到、拿出來是null否过,因此也叫幽靈對象午笛。只起個(gè)標(biāo)識的作用。

先定義一個(gè)用來測試的對象:

public class SomeObject {
    
    @Override
    protected void finalize() throws Throwable{
        System.out.println("OneObject對象即將回收...");
    }
}

強(qiáng)引用

public class StrongReferenceTest {
    public static void main(String[] args) {
        SomeObject object = new SomeObject();
        System.out.println("SomeObject實(shí)例:" + object);
        System.gc(); //命令jvm嘗試一次fgc
        System.out.println("SomeObject實(shí)例:" + object);
        object = null; //使得上面的SomeObject對象失去gc root引用
        System.gc();
    }
}

輸出結(jié)果:

SomeObject實(shí)例:com.wangan.javaref.SomeObject@2a139a55
SomeObject實(shí)例:com.wangan.javaref.SomeObject@2a139a55
SomeObject即將被垃圾回收...

第一次手動fgc時(shí)苗桂,由于存在object = new SomeObject()這個(gè)強(qiáng)引用關(guān)系药磺,所以SomeObject對象沒有被回收,之后object=null相當(dāng)于使得object引用指向了null煤伟,這樣一來SomeObject對象就沒有g(shù)c root引用了癌佩,所以第二次fgc它被回收了木缝,被gc之前觸發(fā)了finalize()方法。

軟引用

/**
 * -Xms100m -Xmx100m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
 * */
public class SoftReferenceTest {
    private static final int M = 1024*1024;
    public static void main(String[] args) {
        SoftReference<SomeObject> softRef = new SoftReference<>(new SomeObject());
        SoftReference<byte[]> byteArraySoftRef = new SoftReference<>(new byte[50*M]);
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byte[]:" + byteArraySoftRef.get());
        System.gc();//手工gc
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byte[]:" + byteArraySoftRef.get());
        byte[] anotherByteArray = new byte[50*M]; //再來5M的byte數(shù)組驼卖,堆內(nèi)存空間不足氨肌,觸發(fā)gc
        System.out.println("softRef:" + softRef);
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byteArraySoftRef:" + byteArraySoftRef);
        System.out.println("byte[]:" + byteArraySoftRef.get());
    }
}

輸出

SomeObject:SomeObject@2a139a55
byte[]:[B@15db9742
SomeObject:SomeObject@2a139a55
byte[]:[B@15db9742
SomeObject即將被垃圾回收...
softRef:java.lang.ref.SoftReference@6d06d69c
SomeObject:null
byteArraySoftRef:java.lang.ref.SoftReference@7852e922
byte[]:null

我們首先固定住堆大小為100M,然后分配了一個(gè)對象和一個(gè)50M數(shù)組都是軟引用酌畜,然后System.gc()手工觸發(fā)gc,這時(shí)候由于堆內(nèi)存還夠用卿叽,軟引用指向的對象沒有被回收桥胞。之后我們再嘗試分配50M的byte數(shù)組,大數(shù)組直接進(jìn)入老年代考婴、老年代不足觸發(fā)gc贩虾,gc之后仍然堆內(nèi)存不足,所以觸發(fā)回收軟引用對象沥阱。
值得一提的是缎罢,觸發(fā)gc回收的是軟引用指向的對象,也就是那個(gè)SomeObject和byte[]考杉,而不是回收軟引用SoftReference這個(gè)實(shí)例本身策精,SoftReference是在所在方法體執(zhí)行完、對應(yīng)的棧幀被彈出之后由于失去gc root被回收崇棠。
gc日志

Java HotSpot(TM) 64-Bit Server VM (25.60-b23) for windows-amd64 JRE (1.8.0_60-b27), built on Aug  4 2015 11:06:27 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8303348k(4172232k free), swap 14332660k(7197788k free)
CommandLine flags: -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.638: [GC (System.gc()) [PSYoungGen: 2064K->800K(29696K)] 53264K->52008K(98304K), 0.0039739 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.642: [Full GC (System.gc()) [PSYoungGen: 800K->0K(29696K)] [ParOldGen: 51208K->51734K(68608K)] 52008K->51734K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0250932 secs] [Times: user=0.13 sys=0.02, real=0.03 secs] 
0.669: [GC (Allocation Failure) [PSYoungGen: 512K->0K(29696K)] 52246K->51734K(98304K), 0.0025944 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.672: [GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] 51734K->51734K(98304K), 0.0028220 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.675: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] [ParOldGen: 51734K->51734K(68608K)] 51734K->51734K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0081899 secs] [Times: user=0.06 sys=0.02, real=0.01 secs] 
0.683: [GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] 51734K->51734K(98304K), 0.0029494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.686: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] [ParOldGen: 51734K->523K(68608K)] 51734K->523K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0212280 secs] [Times: user=0.09 sys=0.00, real=0.02 secs] 
Heap
 PSYoungGen      total 29696K, used 1280K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
  eden space 25600K, 5% used [0x00000000fdf00000,0x00000000fe0401c0,0x00000000ff800000)
  from space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
  to   space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
 ParOldGen       total 68608K, used 51723K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
  object space 68608K, 75% used [0x00000000f9c00000,0x00000000fce82cd8,0x00000000fdf00000)
 Metaspace       used 2649K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

軟引用的特性比較適合用于構(gòu)建進(jìn)程內(nèi)緩存咽袜。

弱引用

public class WeakReferenceTest {
    public static void main(String[] args) {
        WeakReference<SomeObject> weakRef = new WeakReference<>(new SomeObject());
        System.out.println("weakRef: " + weakRef);
        System.out.println("SomeObject: " + weakRef.get());
        System.gc();
        System.out.println("weakRef: " + weakRef);
        System.out.println("SomeObject: " + weakRef.get());
    }
}

輸出

weakRef: java.lang.ref.WeakReference@2a139a55
SomeObject: SomeObject@15db9742
weakRef: java.lang.ref.WeakReference@2a139a55
SomeObject: null
SomeObject即將被垃圾回收...

內(nèi)存足夠的情況下發(fā)生gc(這里我們是用手工System.gc,實(shí)際情況下更可能是年輕代分配對象空間不夠觸發(fā)的ygc)枕稀,這時(shí)候弱引用指向的對象也會被回收询刹。也就是弱引用不管內(nèi)存是否足夠都會在下一次gc中被回收。
ThreadLocal中有關(guān)于弱引用WeakReference的應(yīng)用萎坷。

虛引用

我們無法獲取虛引用所指向的對象凹联,虛引用一般被用來釋放堆外內(nèi)存
因此它所指向的一般是堆外內(nèi)存。
jvm無法直接管理和回收堆外內(nèi)存哆档,因此在gc的時(shí)候如果要順帶觸發(fā)堆外內(nèi)存的回收的話蔽挠,必須找到個(gè)辦法能知道這時(shí)候要回收哪塊堆外內(nèi)存。
比如某個(gè)方法內(nèi)創(chuàng)建了一個(gè)虛引用臨時(shí)變量虐呻,指向堆外內(nèi)存象泵,隨著線程棧的彈出,理論上我們是要回收這個(gè)內(nèi)存的斟叼,因?yàn)闆]有root引用了偶惠。
這時(shí)候就將這個(gè)虛引用放入創(chuàng)建它時(shí)候指定的那個(gè)引用隊(duì)列,做個(gè)記錄朗涩。之后再從引用隊(duì)列里找到這個(gè)引用來釋放相應(yīng)的直接內(nèi)存忽孽。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

/**
 * 我們無法獲取虛引用所指向的對象,虛引用一般被用來釋放堆外內(nèi)存
 * 因此它所指向的一般是堆外內(nèi)存。
 * jvm無法直接管理和回收堆外內(nèi)存兄一,因此在gc的時(shí)候如果要順帶觸發(fā)堆外內(nèi)存的回收的話厘线,必須找到個(gè)辦法能知道這時(shí)候要回收哪塊堆外內(nèi)存。
 * 比如某個(gè)方法內(nèi)創(chuàng)建了一個(gè)虛引用臨時(shí)變量出革,指向堆外內(nèi)存造壮,隨著線程棧的彈出,理論上我們是要回收這個(gè)內(nèi)存的骂束,因?yàn)闆]有root引用了耳璧。
 * 這時(shí)候就將這個(gè)虛引用放入創(chuàng)建它時(shí)候指定的那個(gè)引用隊(duì)列,做個(gè)記錄展箱。之后再從引用隊(duì)列里找到這個(gè)引用來釋放相應(yīng)的直接內(nèi)存旨枯。
 * */
public class PhantomReferenceTest {
    private static final ReferenceQueue<SomeObject> queue = new ReferenceQueue<>();
    
    public static void main(String[] args) {
        /**
         * SomeObject oneObject = new SomeObject();
         * PhantomReference<SomeObject> oneObjectRef = new PhantomReference<SomeObject>(oneObject, queue);
         * 這么搞的話底下System.gc()無法回收SomeObject對象,因?yàn)檫@里用oneObject指向了這個(gè)對象混驰,
         * 且后者是個(gè)強(qiáng)引用攀隔,線程沒執(zhí)行完,棧幀沒從線程棧里彈出來栖榨、線程棧也沒銷毀昆汹。
         * 所以不回收。
         * */
        //SomeObject oneObject = new SomeObject(); //創(chuàng)建一個(gè)SomeObject對象;
        PhantomReference<SomeObject> oneObjectRef = new PhantomReference<SomeObject>(new SomeObject(), queue);//創(chuàng)建這個(gè)對象的虛引用
        System.out.println("虛引用oneObjectRef的地址:" + oneObjectRef);
        System.out.println("虛引用oneObjectRef關(guān)聯(lián)的SomeObject對象的地址:" + oneObjectRef.get());
        
        while(true) {
            System.gc(); //命令jvm嘗試一次fgc治泥,上邊的new OneObject()沒有強(qiáng)引用筹煮,要被回收了
            Reference<? extends SomeObject> ref = queue.poll();
            if(null!=ref) {
                System.out.println("gc過后,在引用隊(duì)列中找到了虛引用居夹,一般來說其所指向的是直接內(nèi)存败潦,無法gc,需要做特殊處理准脂。" + ref);
                break;
            }
        }
    }
}
package com.wangan.direct;
import java.lang.reflect.Field;

import sun.misc.Unsafe;
public class UnsafeUtil {
    
    /**
     * 返回Unsafe實(shí)例
     * */
    public static Unsafe getUnsafeInstance() throws Exception{
        //return Unsafe.getUnsafe();
        Field unsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeInstance.setAccessible(true);
        return (Unsafe)unsafeInstance.get(Unsafe.class);
    }
}

參考文章:
https://blog.csdn.net/u011291072/article/details/106315905
https://blog.csdn.net/aitangyong/article/details/39455229

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末劫扒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狸膏,更是在濱河造成了極大的恐慌沟饥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湾戳,死亡現(xiàn)場離奇詭異贤旷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)砾脑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門幼驶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人韧衣,你說我怎么就攤上這事盅藻」荷#” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵氏淑,是天一觀的道長勃蜘。 經(jīng)常有香客問我,道長假残,這世上最難降的妖魔是什么缭贡? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮守问,結(jié)果婚禮上匀归,老公的妹妹穿的比我還像新娘。我一直安慰自己耗帕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布袱贮。 她就那樣靜靜地躺著仿便,像睡著了一般。 火紅的嫁衣襯著肌膚如雪攒巍。 梳的紋絲不亂的頭發(fā)上嗽仪,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音柒莉,去河邊找鬼闻坚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兢孝,可吹牛的內(nèi)容都是我干的窿凤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼跨蟹,長吁一口氣:“原來是場噩夢啊……” “哼雳殊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起窗轩,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤夯秃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后痢艺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仓洼,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年堤舒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了色建。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡植酥,死狀恐怖镀岛,靈堂內(nèi)的尸體忽然破棺而出弦牡,到底是詐尸還是另有隱情,我是刑警寧澤漂羊,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布驾锰,位于F島的核電站,受9級特大地震影響走越,放射性物質(zhì)發(fā)生泄漏椭豫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一旨指、第九天 我趴在偏房一處隱蔽的房頂上張望赏酥。 院中可真熱鬧,春花似錦谆构、人聲如沸裸扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呵晨。三九已至,卻和暖如春熬尺,著一層夾襖步出監(jiān)牢的瞬間摸屠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工粱哼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留季二,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓揭措,卻偏偏與公主長得像胯舷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子蜂筹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容