Java引用概述
StrongReference(強引用) 不存在這個類 默認實現
Java.lang.ref提供了與 Java垃圾回收器密切相關的引用類。SoftReference(軟引用),WeakReference(弱引用),PhantomReference(虛引用)。這四種引用的強度按照上面的順序依次減弱.
一、強引用(StrongReference)
-就是指在程序代碼中普遍存在的,類似Object obj = new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。 我們一般都是使用強引用來對對象進行引用。如:
String tag = new String("T");
此處的 tag 引用就稱之為強引用。而強引用有以下特征:
- 強引用可以直接訪問目標對象。
- 強引用所指向的對象在任何時候都不會被系統回收。
- 強引用可能導致內存泄漏
只有顯式地設置o為null,或超出對象的生命周期范圍,則gc認為該對象不存在引用,這時就可以回收這個對象。具體什么時候收集這要取決于gc的算法。
二、軟引用(SoftReference)
是用來描述一些還有用但并非必須的對象。對于軟引用關聯著的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
對于軟引用關聯著的對象,如果內存充足,則垃圾回收器不會回收該對象,如果內存不夠了,就會回收這些對象的內存。在 JDK 1.2 之后,提供了 SoftReference 類來實現軟引用。軟引用可用來實現內存敏感的高速緩存。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
demo
public class SoftRefTest {
private static ReferenceQueue<MyObject> softQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) softQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
// -Xmx5M -XX:+PrintGCDetails
public static void main(String[] args) {
MyObject object = new MyObject();
SoftReference<MyObject> softRef = new SoftReference<>(object, softQueue);
new Thread(new CheckRefQueue()).start();
object = null; //刪除強引用
System.gc();
System.out.println("After GC: Soft Get= " + softRef.get());
System.out.println("分配大塊內存");
byte[] b = new byte[5 * 1024 * 928];
System.out.println("After new byte[]:Soft Get= " + softRef.get());
System.gc();
}
}
out:
[GC (Allocation Failure) [PSYoungGen: 1024K->481K(1536K)] 1024K->513K(5632K), 0.0038919 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (System.gc()) [PSYoungGen: 682K->481K(1536K)] 714K->537K(5632K), 0.0035611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 481K->0K(1536K)] [ParOldGen: 56K->498K(4096K)] 537K->498K(5632K), [Metaspace: 2717K->2717K(1056768K)], 0.0068592 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
After GC: Soft Get= I am MyObject
分配大塊內存
[GC (Allocation Failure) [PSYoungGen: 40K->32K(1536K)] 539K->530K(5632K), 0.0006437 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32K->64K(1536K)] 530K->562K(5632K), 0.0004163 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(1536K)] [ParOldGen: 498K->488K(4096K)] 562K->488K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0070447 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 488K->488K(5632K), 0.0006157 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[PSYoungGen: 0K->0K(1536K)] [ParOldGen: 488K->476K(4096K)] 488K->476K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0057286 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
MyObject's finalize called
Object for SoftReference is null
Heap
PSYoungGen total 1536K, used 61K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe0f740,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 476K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 11% used [0x00000007bfa00000,0x00000007bfa77338,0x00000007bfe00000)
Metaspace used 2749K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 294K, capacity 386K, committed 512K, reserved 1048576K
at com.gxgeek.javabasic.ref.SoftRefTest.main(SoftRefTest.java:55)
構造MyObject對象,并將其賦值給object變量,構成強引用。然后使用SoftReference構造這個MyObject對象的軟引用softRef,并注冊到softQueue引用隊列。當softRef被回收時,會被加入softQueue隊列。設置obj=null,刪除這個強引用,因此,系統內對MyObject對象的引用只剩下軟引用。此時,顯示調用GC,通過軟引用的get()方法,取得MyObject對象的引用,發現對象并未被回收,這說明GC在內存充足的情況下,不會回收軟引用對象。
接著,請求一塊大的堆空間,這個操作會使系統堆內存使用緊張,從而產生新一輪的GC。在這次GC后,softRef.get()不再返回MyObject對象,而是返回null,說明在系統內存緊張的情況下,軟引用被回收。軟引用被回收時,會被加入注冊的引用隊列。
二、弱引用(SoftReference)
WeakReference 是弱于 SoftReference 的引用類型。弱引用的特性和基本與軟引用相似,區別就在于弱引用所指向的對象只要進行系統垃圾回收,不管內存使用情況如何,永遠對其進行回收(get() 方法返回 null)。
弱引用有以下特征:
- 弱引用使用 get()方法取得對象的強引用從而訪問目標對象。
- 一旦系統內存回收,無論內存是否緊張,弱引用指向的對象都會被回收。
- 弱引用也可以避免 Heap 內存不足所導致的異常。
public class WeakRefTest {
private static ReferenceQueue<MyObject> weakQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) weakQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("刪除的弱引用為:" + obj + " but獲取弱引用的對象obj.get()=" + obj.get());
}
}
}
public static void main(String[] args) {
MyObject object = new MyObject();
Reference<MyObject> weakRef = new WeakReference<>(object, weakQueue);
System.out.println("創建的弱引用為:" + weakRef);
new Thread(new CheckRefQueue()).start();
object = null;
System.out.println("Before GC: Weak Get= " + weakRef.get());
System.gc();
System.out.println("After GC: Weak Get= " + weakRef.get());
}
}
out:
創建的弱引用為:java.lang.ref.WeakReference@2503dbd3
Before GC: Weak Get= I am MyObject
After GC: Weak Get= null
MyObject's finalize called
刪除的弱引用為:java.lang.ref.WeakReference@2503dbd3 but獲取弱引用的對象obj.get()=null
可以看到,在GC之前,弱引用對象并未被垃圾回收器發現,因此通過 weakRef.get()可以獲取對應的對象引用。但是只要進行垃圾回收,弱引用一旦被發現,便會立即被回收,并加入注冊引用隊列中。此時再試圖通過weakRef.get()獲取對象的引用就會失敗。
Java 虛引用
public class PhantomRefTest {
private static ReferenceQueue<MyObject> phanQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) phanQueue.remove();
System.out.println("刪除的虛引用為:" + obj + " but獲取虛引用的對象obj.get()=" + obj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
Reference<MyObject> phanRef = new PhantomReference<>(object, phanQueue);
System.out.println("創建的虛引用為:" + phanRef);
new Thread(new CheckRefQueue()).start();
object = null;
TimeUnit.SECONDS.sleep(1);
int i = 1;
while (true) {
System.out.println("第" + i++ + "次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
out:
創建的虛引用為:java.lang.ref.PhantomReference@2503dbd3
第1次gc
MyObject's finalize called
第2次gc
刪除的虛引用為:java.lang.ref.PhantomReference@2503dbd3 but獲取虛引用的對象obj.get()=null
PhantomReference是所有“弱引用”中最弱的引用類型。不同于軟引用和弱引用,虛引用無法通過 get() 方法來取得目標對象的強引用從而使用目標對象,觀察源碼可以發現 get() 被重寫為永遠返回 null。
那虛引用到底有什么作用?其實虛引用主要被用來 跟蹤對象被垃圾回收的狀態,通過查看引用隊列中是否包含對象所對應的虛引用來判斷它是否 即將被垃圾回收,從而采取行動。它并不被期待用來取得目標對象的引用,而目標對象被回收前,它的引用會被放入一個 ReferenceQueue 對象中,從而達到跟蹤對象垃圾回收的作用。
引用類型 | 取得目標對象方式 | 垃圾回收條件 | 是否可能內存泄漏 |
---|---|---|---|
強引用 | 直接調用 | 不回收 | 可能 |
軟引用 | 通過 get() 方法 | 視內存情況回收 | 不可能 |
弱引用 | 通過 get() 方法 | 永遠回收 | 不可能 |
虛引用 | 無法取得 | 不回收 | 可能 |
FinalReference 以及 Finzlizer
st=>start: Reference
e=>end: Finalizer
op=>operation: FinalReference
st->op->e
FinalReference 作為 java.lang.ref 里的一個不能被公開訪問的類,又起到了一個什么樣的作用呢?作為他的子類, Finalizer 又在垃圾回收機制里扮演了怎么樣的角色呢?
實際上,FinalReference 代表的正是 Java 中的強引用,如這樣的代碼 :
Bean bean = new Bean();
在虛擬機的實現過程中,實際采用了 FinalReference 類對其進行引用。而 Finalizer,除了作為一個實現類外,更是在虛擬機中實現一個 FinalizerThread,以使虛擬機能夠在所有的強引用被解除后實現內存清理。
讓我們來看看 Finalizer 是如何工作的。首先,通過聲明 FinalizerThread,并將該線程實例化,設置為守護線程后,加入系統線程中去。
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
在 GC 的過程中,當一個強引用被釋放,由系統垃圾收集器標記后的對象,會被加入 Finalizer 對象中的 ReferenceQueue 中去,并調用 Finalizer.runFinalizer() 來執行對象的 finalize 方法。
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
注意,標記處所調用的 invokeFinalizeMethod 為 native 方法,由于 finalize 方法在 Object 類中被聲明為 protected,這里必須采用 native 方法才能調用。隨后通過將本地強引用設置為空,以便使垃圾回收器清理內存。
可以看到,通過這樣的方法,Java 將四種引用對象類型:軟引用 (SoftReference),弱引用 (WeakReference),強引用 (FinalReference),虛引用 (PhantomReference) 平等地對待,并在垃圾回收器中進行統一調度和管理。
何時注冊(實例化FinalReference)
JVM在類加載的時候會遍歷當前類的所有方法,包括父類的方法,只要有一個參數為空且返回void的非空finalize方法就認為這個類在創建對象的時候需要進行注冊。
對象的創建其實是被拆分成多個步驟,注冊的時機可以在為對象分配好內存空間后,也可以在構造函數返回之前,這個點由-XX:-RegisterFinalizersAtInit控制,這個參數默認為true,即:在構造函數返回之前調用。注冊入口是Finalizer的register()方法。
GC回收問題
對象因為Finalizer的引用而變成了一個臨時的強引用,即使沒有其他的強引用,還是無法立即被回收;
對象至少經歷兩次GC才能被回收,因為只有在FinalizerThread執行完了f對象的finalize方法的情況下才有可能被下次GC回收,而有可能期間已經經歷過多次GC了,但是一直還沒執行對象的finalize方法;
CPU資源比較稀缺的情況下FinalizerThread線程有可能因為優先級比較低而延遲執行對象的finalize方法;
因為對象的finalize方法遲遲沒有執行,有可能會導致大部分f對象進入到old分代,此時容易引發old分代的GC,甚至Full GC,GC暫停時間明顯變長,甚至導致OOM;
對象的finalize方法被調用后,這個對象其實還并沒有被回收,雖然可能在不久的將來會被回收。
finalizer的生存周期
- 在創建對象時,如果對象override了finalize()方法,jvm會同時創建一個Finalizer對象
- 所有Finalizer對象組成了一個雙向鏈表
- 所有Finalizer對象都有一個名為queue的成員變量,指向的都是Finalizer類的靜態Queue。
- cms gc執行到mark階段的最后時,會把需要gc的對象加入到Reference的pending list中。
- 有一個專門的高級別線程Reference Handler處理pending list,把pending list中的對象取出來,放到這個對象所指的Reference Queue中,對于Finalizer對象來說,這個queue指向Finalizer類的靜態Queue。
- Finalizer類有一個專門的線程負責從queue中取對象,并且執行finalizer引用的對象的finalize函數。