碎碎念
JDK
1.2之后對對象的引用進行了劃分,分別存在有強引用
,軟引用
,弱引用
,虛引用
- 強引用(Strong Reference)
強引用
是最普通的引用,如果一個對象是強引用
。那么垃圾回收器
絕對不會回收它。當內存空間不足時,java
虛擬機會拋出OutOfMemoryError
是程序異常終止,但是不會回收強引用
對象。
- 軟引用(Soft Reference)
一個對象如果是軟引用
的話,那么當程序內存不足的時候,垃圾回收器
會回收它。那內存空間不緊張的時候。它就一直存在。也就意味著我們是使用它。
- 弱引用(Weak Reference)
弱引用
相比于軟引用
它的聲明周期更短。當垃圾回收器
在工作的過程中,發現了弱引用
對象,不管當前內存是否緊張。都會將其回收。但是GC
的觸發不是特別的頻繁。所以不會那么快發現這個引用。
- 虛引用 (Phantom Reference)
虛引用
顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用
并不會決定對象的生命周期。
設計一個軟引用緩存集合
由于種種因素,我們需要緩存一些數據在內存中,假設我們現在需要緩存的數據是Person
類。( 注意Person
對象很大,像BitMap
一樣)
public class Person {
private String name;
private byte[] datas = new byte[1024*1024*100];
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
正常情況下,我們是直接使用如下的代碼
public class Main {
public static void main(String[] args) {
Map<Integer, Person> pp = new HashMap<>();
for (int i = 0; i < 100; i++) {
Person person = new Person("hello world");
pp.put(i, person);
}
for (int i = 0; i < 100; i++) {
System.out.println(pp.get(i));
}
}
}
但是一運行,你就會發現出現如下的錯誤
因為Person
的數據量太大了,導致JVM
出現了OOM
。那么有沒有辦法容納下這么多的數據,而不會出現OOM
呢? 嗯~ 沒有辦法。 因為JVM
內存是有限制的,你沒有辦法無節制的往內存中存放東西。但是我們可以換個角度,來思考。盡可能的利用JVM
內存呢?嗯~ 可以的,我們可以利用上面所說的軟引用
特性來設計一個緩存集合。
/***
* 軟引用應用設計集合
* @param <K> Key
* @param <V> Value
*/
public class SoftReferenceCache<K, V> {
private Map<K, SoftReference<V>> mCache = new HashMap<>();
/**
* 存放數據
*
* @param k 數據Key
* @param v 數據Value
*/
public void put(K k, V v) {
mCache.put(k, new SoftReference<V>(v));
}
/**
* 存放數據
*
* @param k 數據Key
* @return 返回數據Value
*/
public V get(K k) {
SoftReference<V> reference = mCache.get(k);
if (reference != null) {
return reference.get();
}
return null;
}
}
然后使用上面的 SoftReferenceCache 來進行存儲
public static void main(String[] args) {
SoftReferenceCache<Integer, Person> softReferenceCache = new SoftReferenceCache<>();
for (int i = 0; i < 100; i++) {
softReferenceCache.put(i, new Person("hello world" + i));
}
for (int i = 0; i < 100; i++) {
System.out.println(softReferenceCache.get(i));
}
}
運行后,你會發現不會出現OOM
。但是會存在有被GC
回收的對象。這個時候,你需要在自己的邏輯上增加三級緩存
。
如何知道一個對象被GC
回收呢?
我們想要知道一個對象什么時候會被GC
回收。就需要利用引用隊列ReferenceQueue
,GC
在準備回收一個對象時,如果發現它還僅有軟引用
(或弱引用
,或虛引用
)指向它,就會在回收該對象之前,把這個軟引用
(或弱引用
,或虛引用
)加入到與之關聯的引用隊列(ReferenceQueue)中,這樣我們就知道,對象被GC
回收了。
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Person> queue = new ReferenceQueue<>();
WeakReference<Person> cc = new WeakReference<>(new Person("hello"), queue);
System.gc();
Thread.sleep(2000);
Object object;
while ((object = queue.poll()) != null) {
System.out.println("GC回收了>>>>:" + object);
}
}
上述代碼,可以知道cc
什么時候被回收
Android 中的 Reference
- Android 不建議使用
軟引用
來做緩存,因為Android Runtime
無法感知,應該通過GC
來回收內存,還是增加APP
的棧內存。官方推薦我們是LruCache
- 當我們能夠了解一個對象的銷毀時機,就意味著我們能對一些本該銷毀的對象進行反應。 也就是
內存泄露
的檢測。LeakCanary
底層就是使用引用隊列
進行對Activity
、Fragment
進行檢測的。
相關連接
https://developer.android.com/reference/java/lang/ref/package-summary
https://github.com/square/leakcanary