本文已同步至我的博客 liaosi's blog
在Java中是由JVM負責內存的分配和回收,這是它的優勢(簡化編程者的工做,不須要像C語言那樣去手動操做內存),但同時也是它的缺點(不夠靈活,垃圾回收對於編程者來講是不可控的)。java
在JDK1.2之前,若是一個對象不被任何變量引用,則程序沒法再次使用這個對象,這個對象最終會被GC(GabageCollection:垃圾回收)。可是若是以後可能還會用到這個對象,就只能去新建一個了,這其實就下降了JVM性能,沒有達到最大的優化策略。git
所以,從JDK1.2開始,提供了四種類型的引用:強引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)和虛引用(PhantomReference)。主要有兩個目的:github
什麼是 GC(GabageCollection)?
GC一般是運行在一個獨立的、優先級比較低的線程中,實時監測並釋放「無效」的內存。算法
什麼是「無效"的內存單元?
通常GC採用引用計數法來判斷一個內存單元(一個變量)是不是無效的內存。
引用計數法(引用計數法只是GC中一種經常使用的方法,還會用到年代方法等)是指一個變量或一塊內存當前被引用的次數,若是引用次數爲0,則表示這個變量或這塊內存未被引用,所以GC「有可能」去釋放它 ,爲何說有可能?首先GC運行在一個獨立的、優先級比較低的線程中,其次GC回收的具體工做也是比較複雜的,好比說須要釋放大量內存的時候,而CPU資源又相對緊張,GC可能會選擇性地釋放一些內存資源,具體回收方法取決於GC內部的算法。編程
強引用是最廣泛的引用,若是一個對象具備強引用,垃圾回收器不會回收該對象,當內存空間不足時,JVM 寧願拋出 OutOfMemoryError
異常;只有當這個對象沒有被引用時,纔有可能會被回收。瀏覽器
package com.lzumetal.jvmtest; import java.util.ArrayList; import java.util.List; public class StrongReferenceTest { static class BigObject { private Byte[] bytes = new Byte[1024 * 1024]; } public static void main(String[] args) { List<BigObject> list = new ArrayList<>(); while (true) { BigObject obj = new BigObject(); list.add(obj); } } }
BigObject obj = new BigObject()
建立的這個對象時就是強引用,上面的main方法最終將拋出OOM異常:緩存
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.lzumetal.jvm.StrongReferenceTest$BigObject.<init>(StrongReferenceTest.java:9) at com.lzumetal.jvm.StrongReferenceTest.main(StrongReferenceTest.java:16)
若是一個對象只具備軟引用,則安全
軟引用是用來描述一些有用但並非必需的對象,適合用來實現緩存(好比瀏覽器的‘後退’按鈕使用的緩存),內存空間充足的時候將數據緩存在內存中,若是空間不足了就將其回收掉。jvm
軟引用在Java中用java.lang.ref.SoftReference類來表示。爲了方便測試,在下面這個示例中我設置了JVM的內存爲8M,在IDEA的Run——>EditConfigiratons中設置參數:-Xms8m -Xmx8m -XX:+PrintGCDetails
代碼:性能
package com.lzumetal.jvmtest; import java.lang.ref.SoftReference; public class SoftReferenceTest { static class Person { private String name; private Byte[] bytes = new Byte[1024 * 1024]; public Person(String name) { this.name = name; } } public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); SoftReference<Person> softReference = new SoftReference<>(person); person = null; //去掉強引用,new Person("張三")的這個對象就只有軟引用了 System.gc(); Thread.sleep(1000); System.err.println("軟引用的對象 ------->" + softReference.get()); } }
運行main方法,控制檯輸出:
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->748K(7680K), 0.0118019 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] [GC (System.gc()) [PSYoungGen: 1005K->496K(2048K)] 5346K->4868K(7680K), 0.0025626 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 496K->0K(2048K)] [ParOldGen: 4372K->4773K(5632K)] 4868K->4773K(7680K), [Metaspace: 3466K->3466K(1056768K)], 0.0083134 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 軟引用的對象 ------->com.lzumetal.jvmtest.SoftReferenceTest$Person@6d6f6e28 Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4773K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca9498,0x00000000ffd80000) Metaspace used 3474K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K
雖然調用System.gc()
後JVM並不必定會馬上進行GC操做,但從上面這段輸出能夠看到JVM確實進行了GC,可是軟引用的對象並無被回收掉,說明如今內存空間還足夠,JVM暫時還不會回收軟引用的對象。
把main方法改爲以下:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); SoftReference<Person> softReference = new SoftReference<>(person); person = null;//去掉強引用,new Person("張三")的這個對象就只有軟引用了 Person anotherPerson = new Person("李四"); Thread.sleep(1000); System.err.println("軟引用的對象 ------->" + softReference.get()); }
由於這裏JVM內存只有8M,沒有足夠的空間同時保留兩個Person對象(我已經測試過了:new兩個強引用的Person對象就會報OOM),因此當我再new Person("李四")
時,也是會觸發JVM的GC的,同時由於前面的new Person("張三")
只有軟引用了,它會被回收掉。
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->664K(7680K), 0.0009884 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1006K->504K(2048K)] 5262K->4848K(7680K), 0.0077414 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 504K->504K(2048K)] 4848K->4872K(7680K), 0.0017661 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 504K->0K(2048K)] [ParOldGen: 4368K->4773K(5632K)] 4872K->4773K(7680K), [Metaspace: 3465K->3465K(1056768K)], 0.0201011 secs] [Times: user=0.08 sys=0.00, real=0.02 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4773K->4773K(7680K), 0.0039905 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4773K->659K(5632K)] 4773K->659K(7680K), [Metaspace: 3465K->3465K(1056768K)], 0.0103549 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 軟引用的對象 ------->null Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4755K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca4c80,0x00000000ffd80000) Metaspace used 3473K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K
SoftReference對象是用來保存軟引用,但它同時也是一個Java對象。因此,當軟可及對象被回收以後,雖然這個SoftReference對象的get()方法返回null,但SoftReference對象自己並非null,而此時這個SoftReference對象已經再也不具備存在的價值,須要一個適當的清除機制,避免大量SoftReference對象帶來的內存泄漏。
在java.lang.ref包裏還提供了ReferenceQueue。若是在建立SoftReference對象的時候,使用了一個ReferenceQueue對象做爲參數提供給SoftReference的構造方法,如:
Person person = new Person("張三"); ReferenceQueue<Person> queue = new ReferenceQueue<>(); SoftReference<Person> softReference = new SoftReference<Person>(person, queue);
在SoftReference所軟引用的Person對象被垃圾回收時,JVM會先將softReference對象添加到ReferenceQueue這個隊列中。當咱們調用ReferenceQueue的poll()方法,若是這個隊列中不是空隊列,那麼將返回並移除前面添加的那個Reference對象。
仍是上面的那個例子,測試代碼:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); ReferenceQueue<Person> queue = new ReferenceQueue<>(); SoftReference<Person> softReference = new SoftReference<Person>(person, queue); person = null;//去掉強引用,new Person("張三")的這個對象就只有軟引用了 Person anotherPerson = new Person("李四"); Thread.sleep(1000); System.err.println("軟引用的對象 ------->" + softReference.get()); Reference softPollRef = queue.poll(); if (softPollRef != null) { System.err.println("SoftReference對象中保存的軟引用對象已經被GC,準備清理SoftReference對象"); //清理softReference } }
控制檯輸出:
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->728K(7680K), 0.0022378 secs] [Times: user=0.03 sys=0.05, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1036K->504K(2048K)] 5356K->4840K(7680K), 0.0027540 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 504K->504K(2048K)] 4840K->4840K(7680K), 0.0048557 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [PSYoungGen: 504K->0K(2048K)] [ParOldGen: 4336K->4774K(5632K)] 4840K->4774K(7680K), [Metaspace: 3468K->3468K(1056768K)], 0.0087802 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4774K->4774K(7680K), 0.0005462 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4774K->659K(5632K)] 4774K->659K(7680K), [Metaspace: 3468K->3468K(1056768K)], 0.0104794 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 軟引用的對象 ------->null SoftReference對象中保存的軟引用對象已經被GC,準備清理SoftReference對象 Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4755K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca4d70,0x00000000ffd80000) Metaspace used 3476K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K
弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期,它只能生存到下一次垃圾收集發生以前。當垃圾回收器掃描到只具備弱引用的對象時,不管當前內存空間是否足夠,都會回收它。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
弱引用也能夠和一個引用隊列(ReferenceQueue)聯合使用。
使用場景:一個對象只是偶爾使用,但願在使用時能隨時獲取,但也不想影響對該對象的垃圾收集,則能夠考慮使用弱引用來指向該對象。
參考上面的代碼示例,測試弱引用:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); ReferenceQueue<Person> queue = new ReferenceQueue<>(); WeakReference<Person> weakReference = new WeakReference<Person>(person, queue); person = null;//去掉強引用,new Person("張三")的這個對象就只有軟引用了 System.gc(); Thread.sleep(1000); System.err.println("弱引用的對象 ------->" + weakReference.get()); Reference weakPollRef = queue.poll(); //poll()方法是有延遲的 if (weakPollRef != null) { System.err.println("WeakReference對象中保存的弱引用對象已經被GC,下一步須要清理該Reference對象"); //清理softReference } else { System.err.println("WeakReference對象中保存的軟引用對象尚未被GC,或者被GC了可是得到對列中的引用對象出現延遲"); } }
與其餘三種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。
虛引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。
Object object = new Object(); ReferenceQueue queue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (object, queue);
程序能夠經過判斷引用隊列中是 否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序若是發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。
在實際程序設計中通常不多使用弱引用與虛引用,使用軟引用的狀況較多,這是由於軟引用能夠加速JVM對垃圾內存的回收速度,能夠維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生。
本文代碼已上傳至個人GitHub