Java物件強引用,軟引用,弱引用,虛引用
前言
總所周知, java不同於c/c++,它不需要程式設計師自已來管理記憶體(分配,釋放記憶體),java 會自己來管理記憶體,比如銷燬某些不再被使用的物件。這些操作都是在一個後臺執行緒默默進行(Garbage Collector Thread),也就是垃圾收集器執行緒,根據jvm實現的策略來釋放物件記憶體。但是程式編寫者卻無法控制這個後臺執行緒,無法讓它在你想要的時候開始釋放記憶體,銷燬物件,按照你的規定來銷燬那些物件,釋放記憶體,這些都是都jvm自己控制的。但是隨著 java.lang.ref這個包下的類的引進,程式設計師擁有了一點點控制你建立的物件何時釋放,銷燬的權利,當然只是一點點。那接下來就來看看這些類和java中的四種引用型別有何對應關係。先來看看這四種引用型別,因為jvm在進行垃圾收集的時候,需要判斷哪些物件需要銷燬,這時就與這幾種引用型別相關。
以下是這幾種引用型別:
- 強引用(Strong References)
強引用型別是我們平時寫程式碼的時候最常用的引用,而大部分人往往都會忽略這個概念,都成一種理所當然的事情了。
接下來看看下面這個簡單的例子:
public class Main { public static void main(String[] args) { //建立一個物件,new出來的物件都是分配在java堆中的 Sample sample = new Sample(); //sample這個引用就是強引用 sample = null; //將這個引用指向空指標, //那麼上面那個剛new來的物件就沒用任何其它有效的引用指向它了 //也就說該物件對於垃圾收集器是符合條件的 //因此在接下來某個時間點 GC進行收集動作的時候, 該物件將會被銷燬,記憶體被釋放 } } class Sample { }
也可以畫個簡單的圖理解一下:
- 軟引用(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); //將強引用指向空指標 那麼此時只有一個軟引用指向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);
//將強引用指向空指標 那麼此時只有一個軟引用指向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];
}
}
執行 輸出結果:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
null
null
Exception 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);
//將強引用指向空指標 那麼此時只有一個軟引用指向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];
}
}
執行 輸出結果:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
exa[email protected]
--- 軟引用物件被jvm回收了 ---- [email protected]
null
--- 回收物件 ---- null
null
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at example.Main$1.run(Main.java:47)
- 弱引用(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);
//將強引用指向空指標 那麼此時只有一個弱引用指向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];
}
}
執行 輸出結果:
[email protected]
[email protected]
--- 弱引用物件被jvm回收了 ---- [email protected]
--- 回收物件 ---- null
null
null
null
null
null
null
null
Exception 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
* null
.
*
* @return null
*/
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];
}
}
執行 結果:
null
null
— 虛幻引用物件被jvm回收了 ---- [email protected]
false
— 回收物件 ---- null
null
null
null
null
原文:https://blog.csdn.net/rodbate/article/details/72857447