程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java引用類型

Java引用類型

編輯:JAVA綜合教程

Java引用類型


博主最近在整理Java集合框架時,在整理到WeakHashMap的時候,覺得有必要先闡述一下Java的引用類型,故此先整理的這篇文章,希望各位多提提意見。
閒話不多說,直接進入主題。Java中提供了4個級別的引用:強應用、軟引用、弱引用和虛引用。這四個引用定義在java.lang.ref的包下。
這裡寫圖片描述


強引用( Final Reference)

就是指在程序代碼中普遍存在的,類似Object obj = new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
強引用具備一下三個個特點:

1. 強引用可以直接訪問目標對象;
2. 強引用鎖指向的對象在任何時候都不會被系統回收。JVM寧願拋出OOM異常也不回收強引用所指向的對象;
3. 強應用可能導致內存洩露;

整個FinalReference類的定義如下(有些API中並沒有加入FinalReference類的說明,只能看源碼了):

package java.lang.ref;
/* Final references, used to implement finalization */
class FinalReference extends Reference {
    public FinalReference(T referent, ReferenceQueue q) {
        super(referent, q);
    }
}

從類定義中可以看出,只有一個構造函數,根據所給的對象的應用和應用隊列構造一個強引用。


軟引用(Soft Reference)

是用來描述一些還有用但並非必須的對象。對於軟引用關聯著的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
對於軟引用關聯著的對象,如果內存充足,則垃圾回收器不會回收該對象,如果內存不夠了,就會回收這些對象的內存。在 JDK 1.2 之後,提供了 SoftReference 類來實現軟引用。軟引用可用來實現內存敏感的高速緩存。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
案例1:

package collections.ref;

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

public class SoftRefTest
{
    private static ReferenceQueue softQueue = new ReferenceQueue<>();

    public static class MyObject{

        @Override
        protected void finalize() throws Throwable
        {
            super.finalize();
            System.out.println("MyObject's finalize called");
        }

        @Override
        public String toString()
        {
            return "I am MyObject";
        }
    }

    public static class CheckRefQueue implements Runnable
    {
        Reference obj = null;
        @Override
        public void run()
        {
            try
            {
                obj = (Reference)softQueue.remove();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            if(obj != null)
            {
                System.out.println("Object for SoftReference is "+obj.get());
            }
        }
    }

    public static void main(String[] args)
    {
        MyObject object = new MyObject();
        SoftReference softRef = new SoftReference<>(object,softQueue);
        new Thread(new CheckRefQueue()).start();

        object = null;    //刪除強引用
        System.gc();
        System.out.println("After GC: Soft Get= "+softRef.get());
        System.out.println("分配大塊內存");
        byte[] b = new byte[5*1024*928];
        System.out.println("After new byte[]:Soft Get= "+softRef.get());
        System.gc();
    }
}

運行參數1:

-Xmx5M

運行結果1:

After GC: Soft Get= I am MyObject
分配大塊內存
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null

運行參數2:

-Xmx5M -XX:PrintGCDetails

運行結果2:

[GC [PSYoungGen: 680K->504K(2560K)] 680K->512K(6144K), 0.0040658 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 8K->482K(3584K)] 512K->482K(6144K) [PSPermGen: 2491K->2490K(21504K)], 0.0188479 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
After GC: Soft Get= I am MyObject
分配大塊內存
[GC [PSYoungGen: 123K->64K(2560K)] 605K->546K(7680K), 0.0004285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [PSYoungGen: 64K->64K(2560K)] 546K->546K(7680K), 0.0003019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 64K->0K(2560K)] [ParOldGen: 482K->482K(4608K)] 546K->482K(7168K) [PSPermGen: 2493K->2493K(21504K)], 0.0094748 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(2560K)] 482K->482K(7680K), 0.0003759 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 482K->472K(5120K)] 482K->472K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0101017 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
[GC [PSYoungGen: 122K->32K(2560K)] 5235K->5144K(7680K), 0.0004806 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5144K->5112K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0136270 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 20K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 1% used [0x00000000ffd00000,0x00000000ffd05250,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 5120K, used 5112K [0x00000000ff800000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 5120K, 99% used [0x00000000ff800000,0x00000000ffcfe188,0x00000000ffd00000)
 PSPermGen       total 21504K, used 2500K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000)
  object space 21504K, 11% used [0x00000000fa600000,0x00000000fa871190,0x00000000fbb00000)

加入 -XX:PrintGCDetails參數運行可以更形象的看到GC回收的細節。
這個案例1中,首先構造MyObject對象,並將其賦值給object變量,構成強引用。然後使用SoftReference構造這個MyObject對象的軟引用softRef,並注冊到softQueue引用隊列。當softRef被回收時,會被加入softQueue隊列。設置obj=null,刪除這個強引用,因此,系統內對MyObject對象的引用只剩下軟引用。此時,顯示調用GC,通過軟引用的get()方法,取得MyObject對象的引用,發現對象並未被回收,這說明GC在內存充足的情況下,不會回收軟引用對象。
接著,請求一塊大的堆空間5*1024*928,這個操作會使系統堆內存使用緊張,從而產生新一輪的GC。在這次GC後,softRef.get()不再返回MyObject對象,而是返回null,說明在系統內存緊張的情況下,軟引用被回收。軟引用被回收時,會被加入注冊的引用隊列。
如果將上面案例中的數組再改大點,比如5*1024*1024,就會拋出OOM異常:

After GC: Soft Get= I am MyObject
分配大塊內存
MyObject's finalize called
Object for SoftReference is null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at collections.ref.SoftRefTest.main(SoftRefTest.java:58)

軟引用主要應用於內存敏感的高速緩存,在android系統中經常使用到。一般情況下,Android應用會用到大量的默認圖片,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取文件需要硬件操作,速度較慢,會導致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內存中讀取。但是,由於圖片占用內存空間比較大,緩存很多圖片需要很多的內存,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟引用技術來避免這個問題發生。SoftReference可以解決oom的問題,每一個對象通過軟引用進行實例化,這個對象就以cache的形式保存起來,當再次調用這個對象時,那麼直接通過軟引用中的get()方法,就可以得到對象中中的資源數據,這樣就沒必要再次進行讀取了,直接從cache中就可以讀取得到,當內存將要發生OOM的時候,GC會迅速把所有的軟引用清除,防止oom發生。
案例2:

public class BitMapManager {
    private Map> imageCache = new HashMap>();

    //保存Bitmap的軟引用到HashMap
    public void saveBitmapToCache(String path) {
        // 強引用的Bitmap對象
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        // 軟引用的Bitmap對象
        SoftReference softBitmap = new SoftReference(bitmap);
        // 添加該對象到Map中使其緩存
        imageCache.put(path, softBitmap);
        // 使用完後手動將位圖對象置null
        bitmap = null;
    }

    public Bitmap getBitmapByPath(String path) {

        // 從緩存中取軟引用的Bitmap對象
        SoftReference softBitmap = imageCache.get(path);
        // 判斷是否存在軟引用
        if (softBitmap == null) {
            return null;
        }
        // 取出Bitmap對象,如果由於內存不足Bitmap被回收,將取得空
        Bitmap bitmap = softBitmap.get();
        return bitmap;
    }
}

弱引用(Weak Reference)

用來描述非必須的對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發送之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個注冊引用隊列中。
我們略微修改一下案例1的代碼,如下:

package collections.ref;

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

public class WeakRefTest
{
    private static ReferenceQueue weakQueue = new ReferenceQueue<>();

    public static class MyObject{

        @Override
        protected void finalize() throws Throwable
        {
            super.finalize();
            System.out.println("MyObject's finalize called");
        }

        @Override
        public String toString()
        {
            return "I am MyObject";
        }
    }

    public static class CheckRefQueue implements Runnable
    {
        Reference obj = null;
        @Override
        public void run()
        {
            try
            {
                obj = (Reference)weakQueue.remove();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            if(obj != null)
            {
                System.out.println("刪除的弱引用為:"+obj+"  but獲取弱引用的對象obj.get()="+obj.get());
            }
        }
    }

    public static void main(String[] args)
    {
        MyObject object = new MyObject();
        Reference weakRef = new WeakReference<>(object,weakQueue);
        System.out.println("創建的弱引用為:"+weakRef);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.out.println("Before GC: Weak Get= "+weakRef.get());
        System.gc();
        System.out.println("After GC: Weak Get= "+weakRef.get());
    }
}

不加參數運行結果:

創建的弱引用為:[email protected]
Before GC: Weak Get= I am MyObject
After GC: Weak Get= null
MyObject's finalize called
刪除的弱引用為:[email protected]  but獲取弱引用的對象obj.get()=null

可以看到,在GC之前,弱引用對象並未被垃圾回收器發現,因此通過 weakRef.get()可以獲取對應的對象引用。但是只要進行垃圾回收,弱引用一旦被發現,便會立即被回收,並加入注冊引用隊列中。此時再試圖通過weakRef.get()獲取對象的引用就會失敗。
弱引用的相關實際案例可以參考WeakHashMap,博主會在近期整理出相關文檔。等不及的小伙伴可以自行度娘之。

軟引用、弱引用都非常適合來保存那些可有可無的緩存數據。如果這麼做,當系統內存不足時,這些緩存數據會被回收,不會導致內存溢出。而當內存資源充足時,這些緩存數據又可以存在相當長的時間,從而起來加速系統的作用。


虛引用(Phantom Reference)

虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個持有虛引用的對象,和沒有引用幾乎是一樣的,隨時都有可能被垃圾回收器回收。當試圖通過虛引用的get()方法取得強引用時,總是會失敗。並且,虛引用必須和引用隊列一起使用,它的作用在於跟蹤垃圾回收過程。
虛引用中get方法的實現如下:

    public T get() {
        return null;
    }

可以看到永遠返回null.
我們再來修改一下案例1的代碼:

package collections.ref;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;

public class PhantomRefTest
{
    private static ReferenceQueue phanQueue = new ReferenceQueue<>();

    public static class MyObject{

        @Override
        protected void finalize() throws Throwable
        {
            super.finalize();
            System.out.println("MyObject's finalize called");
        }

        @Override
        public String toString()
        {
            return "I am MyObject";
        }
    }

    public static class CheckRefQueue implements Runnable
    {
        Reference obj = null;
        @Override
        public void run()
        {
            try
            {
                obj = (Reference)phanQueue.remove();
                System.out.println("刪除的虛引用為:"+obj+"  but獲取虛引用的對象obj.get()="+obj.get());
                System.exit(0);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        MyObject object = new MyObject();
        Reference phanRef = new PhantomReference<>(object,phanQueue);
        System.out.println("創建的虛引用為:"+phanRef);
        new Thread(new CheckRefQueue()).start();

        object = null;
        TimeUnit.SECONDS.sleep(1);
        int i =1;
        while(true)
        {
            System.out.println("第"+i+++"次gc");
            System.gc();
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

運行結果:

創建的虛引用為:[email protected]
第1次gc
MyObject's finalize called
第2次gc
刪除的虛引用為:[email protected]  but獲取虛引用的對象obj.get()=null

可以看到,再經過一次GC之後,系統找到了垃圾對象,並調用finalize()方法回收內存,但沒有立即加入回收隊列。第二次GC時,該對象真正被GC清楚,此時,加入虛引用隊列。
虛引用的最大作用在於跟蹤對象回收,清理被銷毀對象的相關資源。
通常當對象不被使用時,重載該對象的類的finalize方法可以回收對象的資源。但是如果使用不慎,會使得對象復活,譬如這麼編寫finalize方法:

public class Test{
    public static Test obj;

    @Override protected void finalize() throws Throwable{
        super.finalize();
        obj = this;
    }
}

對上面這個類Test中obj = new Test();然後obj=null;之後調用System.gc()企圖銷毀對象,但是很抱歉,不管你調用多少次System.gc()都沒有什麼用,除非你在下面的代碼中再就obj=null;這樣才能回收對象,這是因為JVM對某一個對象至多只執行一次被重寫的finalize方法。
上面的小片段說明重寫finalize的方法並不是很靠譜,可以使用虛引用來清理對象所占用的資源。
如下代碼所示:

    public class PhantomRefTest2
{
    private static ReferenceQueue phanQueue = new ReferenceQueue<>();
    private static Map,String> map = new HashMap<>();

    public static class MyObject{

        @Override
        protected void finalize() throws Throwable
        {
            super.finalize();
            System.out.println("MyObject's finalize called");
        }

        @Override
        public String toString()
        {
            return "I am MyObject";
        }
    }

    public static class CheckRefQueue implements Runnable
    {
        Reference obj = null;
        @Override
        public void run()
        {
            try
            {
                obj = (Reference)phanQueue.remove();
                Object value = map.get(obj);
                System.out.println("clean resource:"+value);
                map.remove(obj);

                System.out.println("刪除的虛引用為:"+obj+"  but獲取虛引用的對象obj.get()="+obj.get());
                System.exit(0);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        MyObject object = new MyObject();
        Reference phanRef = new PhantomReference<>(object,phanQueue);
        System.out.println("創建的虛引用為:"+phanRef);
        new Thread(new CheckRefQueue()).start();
        map.put(phanRef, "Some Resources");

        object = null;
        TimeUnit.SECONDS.sleep(1);
        int i =1;
        while(true)
        {
            System.out.println("第"+i+++"次gc");
            System.gc();
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

運行結果:

創建的虛引用為:[email protected]
第1次gc
MyObject's finalize called
第2次gc
clean resource:Some Resources
刪除的虛引用為:[email protected]  but獲取虛引用的對象obj.get()=null

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved