天天看點

Java引用類型:強引用,軟引用,弱引用,虛引用

在Java中提供了4個級别的引用:強引用,軟引用,弱引用,虛引用。在這4個引用級别中,隻有強引用FinalReference類是包内可見,其他3中引用類型均為​

​public​

​,可以在應用程式中直接使用。

強引用

Java中的引用,有點像C++的指針,通過引用,可以對堆中的對象進行操作。

在我們的代碼生涯中,大部分使用的都是強引用,所謂強引入,都是形如​​

​Object o = new Object()​

​​的操作。

強引用具備一下特點:

  • 強引用可以直接通路目标對象
  • 強引用所指向的對象在任何時候不會被系統回收,JVM甯願抛出OOM異常,也不回收強引用所指向的對象
  • 強引用可能導緻記憶體洩漏

是以當我們在使用強引用建立對象時,如果下面不使用這個對象了,一定要顯式地使用​

​o = null​

​​操作來輔助垃圾回收器進行gc操作。一旦我們沒有進行置​

​null​

​​操作,就會造成記憶體洩漏,再使用MAT等工具進行複雜操作,浪費了大量的時間。

在jdk代碼中也有許多顯式置​​

​null​

​​的操作。對于​

​ArrayList​

​​,相信了解過java的都知道,​

​ArrayList​

​​底層使用的數組實作的,在我們進行​

​clear​

​操作時,就會對數組進行置null操作。

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }      

其實如果我們的對象​

​Object o = new Object()​

​​是在方法内建立的,那麼局部變量​

​o​

​​将被配置設定在棧上,而對象​

​Object​

​​執行個體被配置設定在堆上,局部變量​

​o​

​​指向​

​Object​

​​執行個體所在的對空間,通過​

​o​

​​可以操作該執行個體,那麼​

​o​

​​就是​

​Object​

​​的引用。這時候顯式置​

​null​

​​的作用不大,隻要在我們的方法退出,即該棧桢從Java虛拟機棧彈出時,​

​o​

​​指向​

​Object​

​​的引用就斷開了,此時​

​Object​

​在堆上配置設定的記憶體在GC時就能被回收。

軟引用

軟引用是除強引用外,最強的引用類型。可以通過​

​java.lang.ref.SoftReference​

​​使用軟引用,一個持有軟引用的對象,不會被JVM很快回收,JVM會根據目前堆的使用情況來判斷何時回收,當堆使用率臨近門檻值時,才會去回收軟引用對象。隻要有足夠的記憶體,軟引用便可能在記憶體中存活相當長一段時間。是以,軟引用可以用于實作對記憶體敏感的Cache。

在java doc中,軟引用是這樣描述的

虛拟機在抛出 OutOfMemoryError 之前會保證所有的軟引用對象已被清除。此外,沒有任何限制保證軟引用将在某個特定的時間點被清除,或者确定一組不同的軟引用對象被清除的順序。不過,虛拟機的具體實作會傾向于不清除最近建立或最近使用過的軟引用。

舉個例子

/**
 *
 * @author xiaosuda
 * @date 2018/10/23
 */
public class ReferenceTest {


    public static void main(String[] args) {

        //強引用
        MyObject object = new MyObject();
        //建立引用隊列
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        //建立軟引用
        SoftReference<MyObject> softRef = new SoftReference<>(object, queue);
        //檢查引用隊列,監控對象回收情況
        new Thread(new CheckRefQueue(queue)).start();
        //删除強引用
        object = null;
        //手動GC
        System.gc();
        System.out.println("After GC:Soft Get= " + softRef.get());
        System.out.println("配置設定大記憶體:" + Runtime.getRuntime().maxMemory());
        try {
            //配置設定大内容
              byte[] maxObject = new byte[(int) Runtime.getRuntime().maxMemory()];
        } catch (Throwable e) {
            System.out.println(e.getMessage());
        }
        System.out.println("After new byte[]:Soft Get= " + softRef.get());
    }

}

class CheckRefQueue implements Runnable {

    ReferenceQueue queue;

    public CheckRefQueue(ReferenceQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        Reference myObj = null;
        try {
            myObj = queue.remove();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (myObj != null) {
            System.out.println("Object for SoftReference is " + myObj.get());
        }
    }
}

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";
    }
}      

JVM參數為:-Xmx5m -XX:+PrintGCDetails -Xmn2m

執行結果如下:

[GC (System.gc()) [PSYoungGen: 891K->496K(1536K)] 891K->520K(5632K), 0.0015474 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->425K(4096K)] 520K->425K(5632K), [Metaspace: 2687K->2687K(1056768K)], 0.0050401 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
After GC:Soft Get= I'am MyObject
配置設定大記憶體:5767168
[GC (Allocation Failure) [PSYoungGen: 20K->64K(1536K)] 445K->489K(5632K), 0.0002611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 64K->96K(1536K)] 489K->521K(5632K), 0.0002294 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(1536K)] [ParOldGen: 425K->414K(4096K)] 521K->414K(5632K), [Metaspace: 2688K->2688K(1056768K)], 0.0037219 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 414K->414K(5632K), 0.0007248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 414K->402K(4096K)] 414K->402K(5632K), [Metaspace: 2688K->2688K(1056768K)], 0.0047190 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
異常:Java heap space
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
Heap
 PSYoungGen      total 1536K, used 81K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 1024K, 7% used [0x00000007bfe00000,0x00000007bfe14760,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 4096K, used 402K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
  object space 4096K, 9% used [0x00000007bfa00000,0x00000007bfa649d8,0x00000007bfe00000)
 Metaspace       used 2694K, capacity 4490K, committed 4864K, reserved 1056768K
  class space    used 290K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0
      
在這個例子中,首先構造​

​MyObject​

​​對象,并将其指派給​

​obj​

​​變量,構成強引用。然後使用弱引用構造這個​

​MyObject​

​​對象的軟引用,并注冊導​

​softQueue​

​​隊列裡面。當​

​softRef​

​​被回收時,會被​

​softQueue​

​​隊列,設定obj=null,删除這個強引用。是以,系統内對​

​MyObject​

​​對象的引用隻剩下軟引用。此時顯示調用​

​GC​

​​,通過軟引用的get方法,取得​

​myObject​

​​對象執行個體的強引用。法相對象未被回收。說明在​

​GC​

​​充足情況下不會回收軟引用對象。

接着申請一塊堆大小的的堆空間,并​​

​catch​

​​異常,從執行結果發現,在這次​

​GC​

​​後,​

​softRef.get()​

​​不再傳回​

​MyObject​

​​對象,而是傳回​

​null​

​​。說明,在系統記憶體緊張的情況下,軟引用被回收并且加入注冊的引用隊列

軟引用在我們的日常開發中使用的場景很多,比如商城中商品的資訊。某個商品可能會被多人通路,此時我們可以把該商品的資訊使用軟引用儲存。當系統記憶體足夠時,可以實作高速查找,當系統記憶體不足又會被回收,避免​​

​OOM​

​的風險。

TIPS: 盡管軟引用會在OOM之前被清理,但是,這并不表示full gc 會清理軟引用對象。在經過full gc後我們的軟引用對象都放入了old區,由于full gc的存在,程式大多數強框下并不會OOM。由于軟引用對象占據了老年代的空間,full gc将執行的更為頻繁。是以還是建議使用弱引用

當然了,上面的例子是​

​OOM​

​​之前回收軟引用。怎麼才能​

​full gc​

​就回收軟引用對象呢?

-XX:SoftRefLRUPolicyMSPerMB // FullGC 保留的 SoftReference 數量,參數值越大,GC 後保留的軟引用對象就越多。      

當我們設定這個參數值為0時,​

​full gc​

​就會回收我們的軟引用對象了

修改main方法内容

public static void main(String[] args) {

        //強引用
        MyObject object = new MyObject();
        //建立引用隊列
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        //建立軟引用
        SoftReference<MyObject> softRef = new SoftReference<>(object, queue);
        //檢查引用隊列,監控對象回收情況
        new Thread(new CheckRefQueue(queue)).start();
        //删除強引用
        object = null;
        //手動GC
        System.gc();
        System.out.println("After new byte[]:Soft Get= " + softRef.get());
    }      

JVM參數​

​-Xmx5m -XX:+PrintGCDetails -Xmn2m -XX:SoftRefLRUPolicyMSPerMB=0​

​ 執行結果

[GC (System.gc()) [PSYoungGen: 890K->496K(1536K)] 890K->520K(5632K), 0.0021185 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->413K(4096K)] 520K->413K(5632K), [Metaspace: 2685K->2685K(1056768K)], 0.0071067 secs] [Times: user=0.01 sys=0.01, real=0.01 secs] 
After new byte[]:Soft Get= null
Object for SoftReference is null
MyObject's finalize called
Heap
 PSYoungGen      total 1536K, used 71K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe11e68,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 4096K, used 413K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
  object space 4096K, 10% used [0x00000007bfa00000,0x00000007bfa67608,0x00000007bfe00000)
 Metaspace       used 2693K, capacity 4490K, committed 4864K, reserved 1056768K
  class space    used 290K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0
      

可以發現在手動GC後,軟引用對象已經被回收。此時的軟引用已經與弱引用效果一樣了。下面看弱引用

弱引用

弱引用時一種比軟引用較弱的引用類型。在系統GC時,隻要發現弱引用,不管系統對空間是否足夠,都會對對象進行回收。但是,由于垃圾回收器的線程通常優先級很低,是以,并不一定能很快地發現持有弱引用的對象。在這種情況下,弱引用對象可以存在較長時間。一旦一個弱引用對象被垃圾收集器回收,便會加入導一個注冊引用隊列中

修改軟引用例子中的main方法内容為

public static void main(String[] args) {

       //強引用
        MyObject object = new MyObject();
        //建立引用隊列
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        WeakReference<MyObject> weakRef = new WeakReference<>(object, queue);
        new Thread(new CheckRefQueue(queue)).start();
        object = null;
        System.out.println("After new byte[]:Soft Get= " + weakRef.get());
        System.gc();
        System.out.println("After new byte[]:Soft Get= " + weakRef.get());
   }      

​JVM​

​​參數:​

​-Xmx5m -XX:+PrintGCDetails -Xmn2m​

​ 執行結果如下

After new byte[]:Soft Get= I'am MyObject
[GC (System.gc()) [PSYoungGen: 891K->496K(1536K)] 891K->520K(5632K), 0.0016576 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->426K(4096K)] 520K->426K(5632K), [Metaspace: 2685K->2685K(1056768K)], 0.0124398 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
Heap
 PSYoungGen      total 1536K, used 71K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe11e60,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 4096K, used 426K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
  object space 4096K, 10% used [0x00000007bfa00000,0x00000007bfa6a988,0x00000007bfe00000)
 Metaspace       used 2692K, capacity 4490K, committed 4864K, reserved 1056768K
  class space    used 290K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0
      
通過結果可以看到,在GC之前,弱引用對象并未被垃圾回收器發現,是以通過​

​weakRef.get()​

​​方法可以獲得對應的強引用,但是隻要進行垃圾回收,弱引用對象一旦被發現,便會立刻被回收,并加入注冊的引用隊列中。此時,再通過​

​weakRef.get()​

​方法取得強引用就會失敗

虛引用

繼續閱讀