本系列文章內容:jvm內存模型;javaGC機制,以及不同種GC算法關係和區別;java引用類型概念,四種引用類型的區別範圍;finalize方法介紹和FinalizeReference工作機制。
這篇文章是很早之前就想寫的,當時是因爲在android內存工具查看內存使用時發現FinalReference這個引用沒接觸過,想寫一篇關於FinalReference的文章,但是看着看着覺得跟GC機制有很大的聯繫,於是我將這些內容放到一起寫成幾篇文章了,以下都是筆者的個人見解,如有不用意見可以一同討論!
爲什麼要設置不同種引用類型?
java不同於C/C++,不是通過手動free函數釋放對象,而是通過GC機制將對象釋放工作交給GC線程去處理,所以我們需要知道讓jvm知道我們希望哪些對象在什麼時候釋放,哪些對象還不能釋放。這時候給不同類型對象設置不同的引用類型來達到這個效果。
爲了滿足在程序中對生命週期需求不同的對象,通過設置不同種引用類型,使內存利用率儘可能達到最大。
四種不同引用類型
java中有四種引用類型:強引用、軟引用、弱引用、虛引用(或者叫幽靈引用)
當對象被強引用類型引用時,對象不會被gc釋放。
當對象被軟引用類型引用,且沒有被強引用類型引用時,對象在jvm內存足夠時gc不會被釋放,在內存不足時gc會將其釋放。
當對象被弱引用類型引用,且沒有被強引用類型引用時,無論jvm內存是否充足,對象都會被釋放。
當對象僅被虛引用類型引用時,對象類似於沒有引用,隨時會被釋放。
從引用類型強度說:強引用>軟引用>弱引用>虛引用。
強引用:
強引用是接觸最多的,平時寫一個A a = new A();就是將jvm棧中的引用內存a指向堆中新創建的A對象,這個就是強引用類型。
A a1 = new A();//新創建的A對象被強引用引用
其他三種引用都是通過包裝對象的方式實現的,其對應的類分別SoftReference、WeakReference、PhantomReference。
軟引用:
public static void main(String[] args) throws InterruptedException, NoSuchFieldException {
ArrayList<SoftReference<A>> softReferences = new ArrayList<>();
for (int i = 0; i < 10; i++) {
softReferences.add(new SoftReference<A>(new A()));
System.gc();
}
for (SoftReference<A> softReference : softReferences) {
System.out.println(softReference.get());
}
}
static class A {
protected byte[] b;
public A() {
b = new byte[1024 * 1024 * 256];
}
}
爲了模擬內存不足的情況,我在A對象創建時內部創建一個很大的數組,然後連續創建多個A對象,並用SoftReference引用起來。結果如下:
null
null
null
null
null
ClassTest$A@63947c6b
ClassTest$A@2b193f2d
ClassTest$A@355da254
ClassTest$A@4dc63996
ClassTest$A@d716361
Process finished with exit code 0
最開始創建的幾個A對象已經被釋放。
弱引用:
現在將所有的SoftReference換成WeakReference,結果如下:
public static void main(String[] args) throws InterruptedException, NoSuchFieldException {
A a;
ArrayList<WeakReference<A>> weakReferences = new ArrayList<>();
for (int i = 0; i < 10; i++) {
a = new A();//這裏加了個引用哦
weakReferences.add(new WeakReference<A>(a));
System.gc();
}
for (WeakReference<A> weakReference : weakReferences) {
System.out.println(weakReference.get());
}
}
static class A {
protected byte[] b;
public A() {
b = new byte[1024 * 1024 * 2];
}
}
我將代碼做了一點修改,讓引用a指向每次新創建的A對象,這樣就保證每次新創建的A對象有強引用指向,就不會被釋放掉,結果如下:
null
null
null
null
null
null
null
null
null
ClassTest$A@63947c6b
Process finished with exit code 0
可以看到在沒有強引用指向的時候虛引用指向的對象全部被釋放掉了。
虛引用:
虛引用和其他兩個引用不同的地方在於構造方法,除了傳入綁定的對象之外,還要傳入一個引用隊列。而且我們無法通過PhantomReference.get方法獲取這個對象實例,在PhantomReference的get方法中複寫實現就是返回null。
public T get() {
return null;
}
PhantomReference<A> aPhantomReference = new PhantomReference<>(new A(), new ReferenceQueue<>());
要注意的是,虛引用必須和引用隊列關聯使用,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會把這個虛引用加入到與之 關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。(引用自空谷幽瀾的博客https://www.cnblogs.com/huajiezh/p/5835618.html)
簡單來說,我們不指望通過虛引用操作實例,而是通過虛引用以及相關聯的引用隊列獲取一個對象被回收的通知。