對象被判定為垃圾的標準:
判定對象是否為垃圾的算法:
可達性分析算法遍歷引用鏈如圖:
可以作為GC Root的對象:
光有垃圾標記算法還不行,JVM還需要有垃圾回收算法來將這些標記為垃圾的對象給釋放回收掉。主要的回收算法有以下幾種:
1.標記 - 清除算法(Mark and Sweep):
缺點:由于標記 - 清除不需要進行對象的移動,并且僅對不可達的對象進行處理,因此使用該回收算法之后會產生大量不連續的內存碎片。而內存碎片過多可能會導致以后在程序運行過程中,需要分配內存給較大的對象時,無法找到足夠大的連續內存空間,從而不得不再次觸發垃圾回收工作,若依舊無法分配內存的話就會觸發內存溢出異常。
1.復制算法(Copying):
優點:解決內存碎片化問題,順序分配內存,簡單高效。該算法適用于對象存活率低的場景,所以普遍應用在新生代中,因為新生代里的對象存活率通常情況下只有10%左右
3.標記 - 整理算法(Compacting):
優點:避免了標記 - 清除算法所帶來的內存不連續性問題,以及不需要像復制算法那樣需要設置兩塊內存互換。該算法適用于對象存活率較高的場景,所以普遍應用在老年代中,因為老年代里對象存活率較高
4.分代收集算法(Generational Collector):
在JDK7及之前的JVM版本共有三個分代,即新生代、老年代和永久代(注意,永久代不存在于堆中,而是存在于方法區):
而JDK8及以后的版本只有新生代和老年代:
分代收集算法的GC分為兩種:
新生代用于盡可能快速地收集掉那些生命周期短的對象,新生代分為兩個區:
對象如何晉升到老年代:
常用的調優參數:
綜上,老年代用于存放生命周期較長的對象,老年代采用的是標記 - 整理算法。
Full GC和Major GC:
觸發Full GC的條件:
注:promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入老年代,而此時老年代空間不足造成的。
在了解垃圾收集器之前,我們需要知道一個概念“Stop-the-World”:
除此之外,我們需要知道什么是Safepoint:
JVM的運行模式:
各垃圾收集器之間的聯系,即可以搭配使用關系:
Serial收集器(啟動參數:-XX:+UseSerialGC,采用復制算法):
ParNew收集器(啟動參數:-XX:+UseParNewGC,采用復制算法):
Parallel Scavenge收集器(啟動參數:-XX:+UseParallelGC,采用復制算法):
Serial Old收集器(啟動參數:-XX:+UseSerialOldGC,采用標記 - 整理算法):
Parallel Old收集器(啟動參數:-XX:+UseParallelOldGC,采用標記 - 整理算法):
CMS收集器(啟動參數:-XX:+UseConcMarkSweepGC,采用標記 - 清除算法):
CMS收集器收集流程:
CMS收集器圖示:
G1收集器(啟動參數:-XX:+UseG1GC,采用復制 + 標記 - 整理算法):
1.Object的finalize()方法的作用是否與C++的析構函數作用相同:
示例代碼:
package com.example.demo.gc;
/**
* @author 01
* @date 2019-07-18
**/
public class Finalization {
public static Finalization finalization;
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
finalization = this;
}
public static void main(String[] args) {
Finalization f = new Finalization();
System.out.println("First print: " + f);
f = null;
System.gc();
System.out.println("Second print: " + f);
System.out.println(f.finalization);
}
}
執行結果:
從執行結果可以看到,Finalization對象被GC回收時finalize()方法會被調用,finalize()方法里將當前對象this賦值給了靜態屬性finalization實現了對象的“重生”,所以在GC之后依舊能打印到該對象的地址信息
注:finalize是個不太可控的方法因此并不常用,并且在JDK9+版本被標注為過時方法
2.Java中的強引用,軟引用,弱引用及虛引用有什么用:
所謂強引用,就是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還“活著”,垃圾收集器不會碰這種對象。對于一個普通的對象,如果沒有其他的引用關系,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為 null,就是可以被垃圾收集的了,當然具體回收時機還是要看垃圾收集策略??偨Y:
- 最普遍的引用,例:
Object obj = new Object();
- JVM寧可拋出OutOfMemoryError終止程序也不會回收具有強引用的對象
- 通過將對象設置為null來弱化引用,使其被回收
是一種相對強引用弱化一些的引用,可以讓對象豁免一些垃圾收集,只有當 JVM 認為內存不足時,才會去試圖回收軟引用指向的對象。JVM 會確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對象。軟引用通常用來實現內存敏感的緩存,如果還有空閑內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存??偨Y:
- 對象處在有用但非必須的狀態
- 只有當內存空間不足時,GC會回收該引用的對象內存
- 軟引用通常用來實現內存敏感的高速緩存
- 可以配合引用對象使用(ReferenceQueue)
弱引用并不能使對象豁免垃圾收集,僅僅是提供一種訪問在弱引用狀態下對象的途徑。這就可以用來構建一種沒有特定約束的關系,比如,維護一種非強制性的映射關系,如果試圖獲取時對象還在,就使用它,否則重現實例化。它同樣是很多緩存實現的選擇??偨Y:
- 用于描述非必須的對象,比軟引用更弱一些
- 發生GC時就會被回收掉,不過被回收的概率也不大,因為GC線程優先級比較低
- 適用于引用偶爾被使用且不影響垃圾收集的對象
- 可以配合引用對象使用(ReferenceQueue)
對于虛引用,你不能通過它訪問對象。虛引用僅僅是提供了一種確保對象被 finalize 以后,做某些事情的機制,比如,通常用來做所謂的 Post-Mortem 清理機制,例如 Java 平臺自身 Cleaner 機制等,也有人利用幻象引用監控對象的創建和銷毀??偨Y:
- 不會決定對象的生命周期
- 虛引用的對象任何時候都可能被垃圾收集器回收,就像是沒有引用的對象一樣
- 虛引用通常用來跟蹤對象被垃圾收集器回收的活動,起哨兵作用
- 與軟引用和弱引用不同的是,該引用必須與引用對列(ReferenceQueue)聯合使用
軟引用代碼示例:
// 強引用
String str = new String("abc");
// 轉換為軟引用
SoftReference<String> softReference = new SoftReference<>(str);
弱引用代碼示例:
String str = new String("abc");
// 弱引用
WeakReference<String> weakReference = new WeakReference<>(str);
虛引用代碼示例:
String str = new String("abc");
// 引用隊列
ReferenceQueue<String> queue = new ReferenceQueue<>();
// 轉換為虛引用
PhantomReference<String> phantomReference = new PhantomReference<>(str, queue);
// GC在回收一個對象時,如果發現該對象存在虛引用,那么在回收之前會先將該對象的虛引用添加到與該對象關聯的引用隊列中;程序代碼可以通過判斷引用隊列是否已加入虛引用來得知被引用的對象是否已經被回收
引用隊列(ReferenceQueue):
引用強度關系:
下面流程圖簡單總結了對象生命周期和不同可達性狀態,以及不同狀態可能的改變關系:
上圖的具體狀態,實際是 Java 定義的不同可達性級別(reachability level),在之前也說過判斷對象可達性,是 JVM 垃圾收集器決定如何處理對象的一部分考慮??蛇_性具體含義如下:
各引用包裝類的繼承關系圖:
下面我們來用一個例子演示引用包裝對象及引用隊列的使用,首先定義一個普通的類,并且實現finalize方法以便我們在測試時可以看到該對象是否被GC回收了:
package com.example.demo.gc;
/**
* @author 01
* @date 2019-07-18
**/
public class NormalObject {
public String name;
public NormalObject(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing obj: " + name);
}
}
然后定義一個WeakReference的子類,目的是擴展name屬性,以便我們在測試時能夠得知是哪個對象的引用對象:
package com.example.demo.gc;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* @author 01
* @date 2019-07-18
**/
public class NormalObjectWeakReference extends WeakReference<NormalObject> {
public String name;
public NormalObjectWeakReference(NormalObject referent, ReferenceQueue<NormalObject> queue) {
super(referent, queue);
this.name = referent.name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing NormalObjectWeakReference: " + name);
}
}
最后編寫一個測試類:
package com.example.demo.gc;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author 01
* @date 2019-07-18
**/
public class ReferenceQueueTests {
// 引用隊列
private static ReferenceQueue<NormalObject> queue = new ReferenceQueue<>();
/**
* 檢查引用隊列里有沒有引用對象,有的話則打印相關信息
*/
private static void checkQueue() {
Reference<? extends NormalObject> reference;
while ((reference = queue.poll()) != null) {
// 存在于引用隊列中的引用對象
System.out.println("In Queue: " + ((NormalObjectWeakReference) (reference)).name);
// 獲取引用的對象實例
System.out.println("reference object: " + reference.get());
}
}
public static void main(String[] args) throws InterruptedException {
List<WeakReference<NormalObject>> weakReferenceList = new ArrayList<>();
// 創建引用對象
for (int i = 0; i < 3; i++) {
NormalObject normalObject = new NormalObject("Weak-" + i);
NormalObjectWeakReference reference = new NormalObjectWeakReference(normalObject, queue);
weakReferenceList.add(reference);
System.out.println("Create weak: " + reference);
}
System.out.println("\nbefore gc ------");
checkQueue();
System.out.println("\ngc ing... ------");
System.gc();
// 讓線程休眠一會,以保gc能夠正常執行完畢
Thread.sleep(1000);
System.out.println("\nafter gc ------");
checkQueue();
}
}
運行結果:
可以看到在GC執行之前調用checkQueue方法沒有打印任何信息,因為此時引用隊列中沒有任何引用對象。而當GC執行之后,引用隊列中就被添加了與之相關聯的引用對象,所以就能夠打印出引用對象的相關信息
GC相關參考文章:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。