java對象的引用包括強引用,軟引用,弱引用,虛引用四種
強引用
強引用是最常用的引用,我們在代碼中處處可見:
String str = "hello world";
Map<String, String> map = new HashMap<>();
int[] arr = new int[10];
上面的str、map、arr都是強引用。一個對象,只要有強引用與它關聯,那么JVM就不會回收它。即使是在內存不足的情況下,JVM寧愿拋出OutOfMemory的異常也不會回收它。
public void function() {
Object object = new Object();
Object[] array = new Object[9999];
}
比如上面的方法,當運行到Object[] array = new Object[9999];的時候,如果內存不夠了,JVM就好拋出OutOfMemory的異常,而不會回收object的內存。所以一個強引用的內存肯定是有效的,所以java并不像c++,需要擔心野指針的問題。
當然,當退出function之后,object和array就都已經不存在了,所以它們所指向的內存就可以被回收了。
當一個對象使用完,不會再被用到的時候,我們可以將所有指向它的強引用都賦為null。這樣JVM會在合適的時機去回收它。
軟引用
軟引用所管理的對象在內存不足的時候,如果沒有其他強引用與之管理,就會被回收。用SoftReference來表示軟引用,使用方法如下:
public class DemoActivity extends AppCompatActivity {
SoftReference<String> mStr = new SoftReference<>(new String("hello world"));
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
}
public void onClick(View view) {
Log.d("SoftReference", "mStr = " + mStr.get());
}
}
注意的是這里不能直接new SoftReference<>("hello world");因為JVM字符串常量池機制的存在,會導致字符串常量的內存很難被垃圾回收。
str.get()就可以獲取到管理的對象,如果對象已經被回收就會返回null。
我們可以用Android Studio的Monitors強制gc,釋放內存,然后這個時候就能看到它返回的是null了。
值得注意的是“SoftReference所管理的對象被回收”并不代表SoftReference的內存被回收, SoftReference此時依然是一個可以使用的對象,但它已經沒有使用價值了。我們需要在合適的時候將SoftReference賦值為null,釋放掉它所占用的內存,避免大量無用的SoftReference存在導致內存泄漏。
SoftReference也可以和ReferenceQueue一起使用。構造SoftReference的時候將ReferenceQueue傳入SoftReference的構造方法。當SoftReference所管理的對象被回收的時候SoftReference就會被放到ReferenceQueue中。
public class DemoActivity extends AppCompatActivity {
ReferenceQueue<String> mReferenceQueue = new ReferenceQueue<>();
SoftReference<String> mStr = new SoftReference<>(new String("hello world"), mReferenceQueue);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
}
public void onClick(View view) {
if (mStr.get() == null) {
Log.d("SoftReference", "mStr = " + mStr);
mStr = null;
SoftReference<String> ref;
do {
ref = (SoftReference<String>) mReferenceQueue.poll();
Log.d("SoftReference", "ref = " + ref);
} while (ref != null);
}
}
}
軟引用的特性使得它很適合用來實現數據緩存,如圖片緩存,網頁緩存等。在內存緊張的時候如果沒有其他的強引用關聯,即該資源僅僅是放在緩存中而沒有被使用,就會被釋放。而當內存不緊張的時候,即使沒有其他強引用與之關聯,JVM的垃圾回收機制也是不會回收軟引用所管理的資源的。
當需要使用的時候判斷獲取的是不是null,如果是的話證明之前內存被回收了,直接重新加載就好了。
弱引用
弱引用所管理的對象在JVM進行垃圾回收的時候,只要沒有其他強引用與之關聯。不管內存是否充足,都會被回收。它用WeakReference來表示。
弱引用可以用在回調函數中防止內存泄漏。我們來看個典型的例子,也是一個很多人都會犯的錯誤:
public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
}
// 可能會引入內存泄漏
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
不知道大家有沒有看過我之前寫的一篇關于Handler的博客,如果沒有沒有看過,而對Handler又不太熟悉的同學可以去看一下。
我們知道Handler是和Looper還有MessageQueue一起工作的。當安卓應用啟動之后,系統會在主線程創建一個Looper和MessageQueue,它們的生命周期貫穿整個應用的生命周期。
Handler在發送Message的時候會將Message傳到MessageQueue里面去,而Message里面保存著Handler的引用。這樣的話如果Message還沒有被處理,Handler也不會被回收。
而這里的Handler是DemoActivity的一個內部類。在java中,非晶體內部匿名類會持有外部類的一個隱式引用,這樣就有可能導致外部類無法被垃圾回收。
如果我們代碼中這樣寫:
public class DemoActivity extends AppCompatActivity {
private static final int MSG_DO_SOMETHING = 1;
// 可能會引入內存泄漏
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
// 延遲十分鐘發送消息
mHandler.sendEmptyMessageDelayed(MSG_DO_SOMETHING, 1000 * 60 * 10);
}
}
即使退出了DemoActivity,在消息沒有被處理之前, DemoActivity的內存也是不會被回收的。
那要怎樣解決它呢?
我們可以使用靜態內部類加虛引用的方式:
public class DemoActivity extends AppCompatActivity {
private static final int MSG_DO_SOMETHING = 1;
// 可能會引入內存泄漏
private static class InnerHandler extends Handler {
private final WeakReference<DemoActivity> mActivity;
InnerHandler(DemoActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
DemoActivity activity = mActivity.get();
if (activity != null) {
...
}
}
}
private final Handler mHandler = new InnerHandler(this);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
// 延遲十分鐘發送消息
mHandler.sendEmptyMessageDelayed(MSG_DO_SOMETHING, 1000 * 60 * 10);
}
}
由于靜態內部類不持有外部類的引用,所以現在只有虛引用關聯了DemoActivity,在垃圾回收的時候,不管是否內存不足,DemoActivity都會被回收。
十分鐘之后當handleMessage方法被調用的時候,用WeakReference的get方法獲取DemoActivity,如果返回null的話證明DemoActivity已經被回收,就不應該再做什么處理了。
WeakReference同樣可以在構造方法中傳入ReferenceQueue。如果它所管理的對象被JVM回收,這個WeakReference就會被加入到ReferenceQueue。
虛引用
虛引用或者叫做幽靈引用在java中用PhantomReference表示。它和前面的應用都不一樣,它不影響對象的生命周期,當一個對象只有虛引用與之關聯的時候,就和沒有任何引用一樣。
而且它必須與ReferenceQueue一起使用,它只有一個構造函數:
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
而且它的get方法永遠返回null:
public T get() {
return null;
}
如果PhantomReference管理的對象只有PhantomReference與之關聯,系統就會在這個時候或者一段時間之后將PhantomReference放到ReferenceQueue中。而不用等到垃圾回收的時候(參考PhantomReference的文檔):
If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.
這篇文章對虛引用做了一個詳細的介紹,其中對這一點他是這樣解釋的:
當一個虛引用被認為是一定會被垃圾回收器回收的時候,這個虛引用才會被注冊到引用隊列,而不會像軟引用和弱引用必須等到垃圾回收器回收之后才會被注冊到引用隊列
對這個虛引用我其實理解的還不是很深入,查了很多的資料對它的講解也是很模糊的。按我的理解,它應該就是單純的用來判斷一個對象是否被回收了。如果是,就進行一些清理操作。