前言
總所周知, java不同於c/c++,它不需要程式員自已來管理記憶體(分配,釋放記憶體),java 會自己來管理記憶體,比如銷毀某些不再被使用的對象。這些操作都是在一個後台線程默默進行(Garbage Collector Thread),也就是垃圾收集器線程,根據jvm實現的策略來釋放對象記憶體。但是程式編寫者卻無法控制這個後台線程,無法讓它在你想要的時候開始釋放記憶體,銷毀對象,按照你的規定來銷毀那些對象,釋放記憶體,這些都是都jvm自己控制的。但是隨著 java.lang.ref這個包下的類的引進,程式員擁有了一點點控制你建立的對象何時釋放,銷毀的權利,當然只是一點點。那接下來就來看看這些類和java中的四種參考型別有何對應關係。先來看看這四種參考型別,因為jvm在進行垃圾收集的時候,需要判斷哪些對象需要銷毀,這時就與這幾種參考型別相關。
以下是這幾種參考型別: 1. 強引用(Strong References)
強參考型別是我們平時寫代碼的時候最常用的引用,而大部分人往往都會忽略這個概念,都成一種理所當然的事情了。
接下來看看下面這個簡單的例子:
public class Main { public static void main(String[] args) { //建立一個對象,new出來的對象都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 sample = null; //將這個引用指向null 指標, //那麼上面那個剛new來的對象就沒用任何其它有效引用指向它了 //也就說該對象對於垃圾收集器是合格 //因此在接下來某個時間點 GC進行收集動作的時候, 該對象將會被銷毀,記憶體被釋放 }}class Sample {}
也可以畫個簡單的圖理解一下:
2. 軟引用(Soft References)
軟引用在java.lang.ref包中有與之對應的類java.lang.ref.SoftReference。
重點: 被弱引用指向的對象不會被垃圾收集器收集(即使該對象沒有強引用指向它),除非jvm使用記憶體不夠了,才會對這類對象進行銷毀,釋放記憶體。舉個簡單的例子:
public class Main { public static void main(String[] args) { //建立一個對象,new出來的對象都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 //建立一個軟引用指向這個對象 那麼此時就有兩個引用指向Sample對象 SoftReference<Sample> softRef = new SoftReference<Sample>(sample); //將強引用指向null 指標 那麼此時只有一個軟引用指向Sample對象 //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的 //那麼這個軟引用在哪呢。 可以跟一下java.lang.Reference的源碼 //private T referent; 這個才是軟引用, 只被jvm使用 sample = null; //可以重新獲得Sample對象,並用一個強引用指向它 sample = softRef.get(); }}class Sample {}
有興趣可以去看看Reference的源碼。
現在是不是想到了軟引用的一個使用情境,它相比與強引用可以避免OOM。
現在可以簡單的測試下 當jvm記憶體不足的情況下,軟引用的回收情況。
為了更快的看到結果,我限制了jvm的最大堆記憶體 -Xmx100m 為100m
public class Main { private static final List<Object> TEST_DATA = new LinkedList<>(); public static void main(String[] args) throws InterruptedException { //建立一個對象,new出來的對象都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 //建立一個軟引用指向這個對象 那麼此時就有兩個引用指向Sample對象 SoftReference<Sample> softRef = new SoftReference<Sample>(sample); //將強引用指向null 指標 那麼此時只有一個軟引用指向Sample對象 //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的 //那麼這個軟引用在哪呢。 可以跟一下java.lang.Reference的源碼 //private T referent; 這個才是軟引用, 只被jvm使用 sample = null; //可以重新獲得Sample對象,並用一個強引用指向它 //sample = softRef.get(); new Thread(){ @Override public void run() { while (true) { System.out.println(softRef.get()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } TEST_DATA.add(new byte[1024 * 1024 * 5]); } } }.start(); Thread.currentThread().join(); }}class Sample { private final byte[] data; public Sample() { data = new byte[1024 * 1024 * 10]; }}
運行 輸出結果:
example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216nullnullException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at example.Main$1.run(Main.java:42)
可以看到當jvm記憶體耗盡的時候,會將弱引用的對象進行回收 上面的例子的10m,且剛好還可以分配兩次 5m一次 ,輸出了兩次null也證明了這一點
當然我們在建立軟引用時,還可以傳入ReferenceQueue,這個隊列有啥用呢。 當jvm回收某個軟引用對象之後會將該SoftReference對象(例子中的softRef對象)添加進這個隊列,因此我們就知道這個對象啥時候被回收了,可以做一些我們想做的操作。
public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock;}Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}
還是舉個簡單的例子
public class Main { private static final List<Object> TEST_DATA = new LinkedList<>(); private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { //建立一個對象,new出來的對象都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 //建立一個軟引用指向這個對象 那麼此時就有兩個引用指向Sample對象 SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE); //將強引用指向null 指標 那麼此時只有一個軟引用指向Sample對象 //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的 //那麼這個軟引用在哪呢。 可以跟一下java.lang.Reference的源碼 //private T referent; 這個才是軟引用, 只被jvm使用 sample = null; //可以重新獲得Sample對象,並用一個強引用指向它 //sample = softRef.get(); new Thread(){ @Override public void run() { while (true) { System.out.println(softRef.get()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } TEST_DATA.add(new byte[1024 * 1024 * 5]); } } }.start(); new Thread(){ @Override public void run() { while (true) { Reference<? extends Sample> poll = QUEUE.poll(); if (poll != null) { System.out.println("--- 軟引用對象被jvm回收了 ---- " + poll); System.out.println("--- 回收對象 ---- " + poll.get()); } } } }.start(); try { Thread.currentThread().join(); } catch (InterruptedException e) { System.exit(1); } }}class Sample { private final byte[] data; public Sample() { data = new byte[1024 * 1024 * 10]; }}
運行 輸出結果:
example.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142c--- 軟引用對象被jvm回收了 ---- java.lang.ref.SoftReference@306d3b64null--- 回收對象 ---- nullnullException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at example.Main$1.run(Main.java:47)
3. 弱引用(Weak References)
弱引用會被jvm忽略,也就說在GC進行垃圾收集的時候,如果一個對象只有弱引用指向它,那麼和沒有引用指向它是一樣的效果,jvm都會對它就行果斷的銷毀,釋放記憶體。其實這個特性是很有用的,jdk也提供了java.util.WeakHashMap這麼一個key為弱引用的Map。比如某個資來源物件你要釋放(比如 db connection), 但是如果被其它map作為key強引用了,就無法釋放,被jvm收集。
來個簡單的例子:
public class Main { private static final List<Object> TEST_DATA = new LinkedList<>(); private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { //建立一個對象,new出來的對象都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 //建立一個弱引用指向這個對象 那麼此時就有兩個引用指向Sample對象 //SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE); WeakReference<Sample> weakRef = new WeakReference<Sample>(sample, QUEUE); //將強引用指向null 指標 那麼此時只有一個弱引用指向Sample對象 //注意:softRef這個引用也是強引用,它是指向SoftReference這個對象的 //那麼這個弱引用在哪呢。 可以跟一下java.lang.Reference的源碼 //private T referent; 這個才是弱引用, 只被jvm使用 sample = null; //可以重新獲得Sample對象,並用一個強引用指向它 //sample = softRef.get(); new Thread(){ @Override public void run() { while (true) { System.out.println(weakRef.get()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } TEST_DATA.add(new byte[1024 * 1024 * 5]); } } }.start(); new Thread(){ @Override public void run() { while (true) { Reference<? extends Sample> poll = QUEUE.poll(); if (poll != null) { System.out.println("--- 弱引用對象被jvm回收了 ---- " + poll); System.out.println("--- 回收對象 ---- " + poll.get()); } } } }.start(); try { Thread.currentThread().join(); } catch (InterruptedException e) { System.exit(1); } }}class Sample { private final byte[] data; public Sample() { data = new byte[1024 * 1024 * 10]; }}
運行 輸出結果:
example.Sample@234c0f50example.Sample@234c0f50--- 弱引用對象被jvm回收了 ---- java.lang.ref.WeakReference@16fea746--- 回收對象 ---- nullnullnullnullnullnullnullnullException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at example.Main$1.run(Main.java:47)
從結果中可以清晰的看到弱引用對象並不需要在jvm耗盡記憶體的情況下才進行回收, 是可以隨時回收的。 4. 虛幻引用(Phantom References)
虛幻應用和弱引用的回收機制差不多,都是可以被隨時回收的。但是不同的地方是,它的構造方法必須強制傳入ReferenceQueue,因為在jvm回收前(重點: 對,就是回收前,軟引用和弱引用都是回收後),會將PhantomReference對象加入ReferenceQueue中; 還有一點就是PhantomReference.get()方法永遠返回空,不管對象有沒有被回收。
源碼: java.lang.ref.PhantomReference /** * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * <code>null</code>. * * @return <code>null</code> */ public T get() { return null; }
來個例子看看:
public class Main { private static final List<Object> TEST_DATA = new LinkedList<>(); private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { Sample sample = new Sample(); PhantomReference<Sample> phantomRef = new PhantomReference<>(sample, QUEUE); sample = null; new Thread(){ @Override public void run() { while (true) { System.out.println(phantomRef.get()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } TEST_DATA.add(new byte[1024 * 1024 * 5]); } } }.start(); new Thread(){ @Override public void run() { while (true) { Reference<? extends Sample> poll = QUEUE.poll(); if (poll != null) { System.out.println("--- 虛幻引用對象被jvm回收了 ---- " + poll); System.out.println(poll.isEnqueued()); System.out.println("--- 回收對象 ---- " + poll.get()); } } } }.start(); try { Thread.currentThread().join(); } catch (InterruptedException e) { System.exit(1); } }}class Sample { private final byte[] data; public Sample() { data = new byte[1024 * 1024 * 10]; }}
運行 結果:
nullnull--- 虛幻引用對象被jvm回收了 ---- java.lang.ref.PhantomReference@40788638false--- 回收對象 ---- nullnullnullnullnull