1. 程式人生 > 實用技巧 >2、帶著問題學習JVM_物件存活_垃圾回收的內容_理論_收集演算法

2、帶著問題學習JVM_物件存活_垃圾回收的內容_理論_收集演算法

  帶著問題學習:深入理解Java虛擬機器 JVM高階特性與最佳實踐第3版,本篇博文從為什麼要學習垃圾回收?如何判斷物件是否為垃圾?垃圾回收都發生在哪些地方?從不同維度立學習垃圾回收理論?當前的垃圾收集演算法有哪些?垃圾收集器有哪些?及垃圾收集器優劣的衡量標準是什麼?為了避免本篇篇幅過長後兩個問題將在下一篇進行學習。
1、記憶體動態分配與記憶體回收技術已經相當成熟,為什麼我們還要去了解垃圾收集和記憶體分配?
1)當需要排查各種記憶體溢位、記憶體洩漏問題時,當垃圾收整合為系統達到更高併發量的瓶頸時,我們就必須對這些“自動化”的技術實施必要的監控和調節。
2)執行時資料區域中的執行緒私有區域的記憶體分配和回收都具備確定性,隨執行緒而生隨執行緒而亡,不需要過多的關注垃圾回收問題。
3)Java堆和方法區這兩個區域則有著很顯著的不確定性只有處於執行期間,我們才能知道程式究竟會建立哪些物件,建立多少個物件,這部分記憶體的分配和回收是動態的。垃圾收集器所關注的正是這部分記憶體該如何管理

2、如何判斷物件是否存活?

1)引用計數演算法
2)可達性分析演算法
這個演算法的基本思路就是通過一系列稱為“GC Roots”的根物件作為起始節點集,從這些節點開始,根據引用關係向下搜尋,搜尋過程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個物件到GC Roots間沒有任何引用鏈相連,或者用圖論的話來說就是從GC Roots到這個物件不可達時,則證明此物件是不可能再被使用的。

固定可作為GC Roots的物件包括以下六種:
a.虛擬機器棧(棧幀中的本地變量表)中引用的物件,譬如各個執行緒被呼叫的方法堆疊中使用到的引數、區域性變數、臨時變數等
b.本地方法棧中JNI引用的物件
c.方法區中類靜態屬性引用的物件、常量引用的物件
d.虛擬機器內部的引用(如基本資料型別對應的Class物件、一些常駐的異常物件、系統類載入器)
e.所有被同步鎖(synchronized關鍵字)持有的物件
f.反映Java虛擬機器內部情況的JMXBean、JVMTI中註冊的回撥、原生代碼快取等

3、引用的含義及分類:

1)在JDK 1.2版之前,如果reference型別的資料中儲存的數值代表的是另外一塊記憶體的起始地址,就稱該reference資料是代表某塊記憶體、某個物件的引用。
2)在JDK 1.2版之後,Java對引用的概念進行了擴充,將引用分為強引用(Strongly Re-ference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。
a.強引用:
是在程式程式碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關係。無論任何情況下,只要強引用關係還存在,垃圾收集器就永遠不會回
收掉被引用的物件。

b.軟引用:
是用來描述一些還有用,但非必須的物件。只被軟引用關聯著的物件,在系統將要發生記憶體溢位異常前,會把這些物件列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。
在JDK 1.2版之後提供了SoftReference類來實現軟引用。

c.弱引用:
也是用來描述那些非必須物件,但是它的強度比軟引用更弱一些,被弱引用關聯的物件只能生存到下一次垃圾收集發生為止。當垃圾收集器開始工作,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。
在JDK 1.2版之後提供了WeakReference類來實現弱引用。

d.虛引用:
也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關係。一個物件是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的只是為了能
在這個物件被收集器回收時收到一個系統通知。在JDK 1.2版之後提供了PhantomReference類來實現虛引用

4、談談方法區中的垃圾回收內容:

《Java虛擬機器規範》中提出不要求虛擬機器在方法區中實現垃圾收集,事實上也確實有未實現或未能完整實現方法區型別解除安裝的收集器存在(如JDK 11時期的ZGC收集器就不支援類解除安裝)
方法區中的垃圾回收:主要包含兩部分內容廢棄的常量和不再使用的型別。
1)廢棄常量如何回收:(以常量池中的java 字串為例)
已經沒有任何字串物件引用常量池中的“java”常量,且虛擬機器中也沒有其他地方引用這個字面量。如果在這時發生記憶體回收,而且垃圾收集器判斷確有必要的話,這個“java”常量就將會被系統清理出常量池。常量池中其他類(介面)、方法、欄位的符號引用也與此類似。
2)不再被使用的類如何回收:(至少需要滿足一下三個條件)
a.該類所有的例項都已經被回收,也就是Java堆中不存在該類及其任何派生子類的例項。
b.載入該類的類載入器已經被回收,這個條件除非是經過精心設計的可替換類載入器的場景,如OSGi、JSP的重載入等,否則通常是很難達成的。
c.該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
規範規定對滿足上述三個條件的無用類是允許進行回收,而不是沒有引用了就必然會回收。關於是否要對型別進行回收,HotSpot虛擬機器提供了-Xnoclassgc引數進行控制,還可以使用-verbose:class、-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading檢視類載入和解除安裝資訊。
在大量使用反射、動態代理、CGLib等位元組碼框架,動態生成JSP以及OSGi這類頻繁自定義類載入器的場景中,通常都需要Java虛擬機器具備型別解除安裝的能力,以保證不會對方法區造成過大的記憶體壓力。

5、你知道的垃圾收集理論有哪些?
1)從如何判定物件消亡的角度出發,垃圾收集演算法的分類
a.“引用計數式垃圾收集”(Reference Counting GC)被稱為“直接垃圾收集”,其在主流的Java虛擬機器中均未涉及。
b.“追蹤式垃圾收集”(Tracing GC)也被稱為“間接垃圾收集”

2)分代收集理論
分代收集是建立在以下兩個理論基礎之上的
a.弱分代假說(Weak Generational Hypothesis):絕大多數物件都是朝生夕滅的。
b.強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的物件就越難以消亡。
c.跨代引用假說(Intergenerational Reference Hypothesis):跨代引用相對於同代引用來說僅佔極少數。這其實是可根據前兩條假說邏輯推理得出的隱含推論:存在互相引用關係的兩個物件,是應該傾向於同時生存或者同時消亡的。如果某個新生代物件存在跨代引用引用某個老年代的物件,由於老年代物件難以消亡,該引用會使得新生代物件在收集時同樣得以存活,進而在年齡增長之後晉升到老年代中,這時跨代引用也隨即被消除了。

問題1:談談什麼是跨代引用?
所謂跨代引用就是老年代的物件引用了新生代的物件,或者新生代的物件引用了老年代的物件。那對於這種情況我們的GC在進行掃描的時候不可能直接把我們的整個堆都掃描完,那這樣效率也太低了。所以這時候就需要開闢了一小塊空間,維護這種引用,而不必讓GC掃描整個堆區域,而這種小空間就叫做記憶集。

問題2:談談什麼是記憶集?
記憶集也叫rememberSet,垃圾收集器在新生代中建立了記憶集這樣的資料結構,用來避免把整個老年代加入到GC ROOTS的掃描範圍中。對於記憶集來說,我們可以理解為他是一個抽象類,那麼具體實現它的方法將由子類去完成。
依據跨代引用假說,我們就不應再為了少量的跨代引用去掃描整個老年代,也不必浪費空間專門記錄每一個物件是否存在及存在哪些跨代引用,只需在新生代上建立一個全域性的資料結構(該結構被稱為“記憶集”,Remembered Set),這個結構把老年代劃分成若干小塊,標識出老年代的哪一塊記憶體會存在跨代引用。此後當發生MinorGC時,只有包含了跨代引用的小塊記憶體裡的物件才會被加入到GCRoots進行掃描。雖然這種方法需要在物件改變引用關係(如將自己或者某個屬性賦值)時維護記錄資料的正確性,會增加一些執行時的開銷,但比起收集時掃描整個老年代來說仍然是划算的。
公司專案組大佬對記憶集的理解:記憶集是非收集區域指向收集區域的指標的集合的資料結構抽象

問題3:實現記憶集的方式有那些?包含以下三種:
a.字長精度
b.物件精度
c.卡精度(卡表) 卡表(Card Table)是一種對記憶集的具體實現。主要定義了記憶集的記錄精度、與堆記憶體的對映關係等。卡表中的每一個元素都對應著一塊特定大小的記憶體塊,這個記憶體塊我們稱之為卡頁(card page),當存在跨帶引用的時候,它會將卡頁標記為dirty,那麼JVM對於卡頁的維護也是通過寫屏障的方式。

3)不同分代垃圾收集的概念劃分
a.部分收集(Partial GC):指目標不是完整收集整個Java堆的垃圾收集,部分收集分為以下三種收集:
新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集
老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行為。
混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行為。
b.整堆收集(Full GC): 收集整個Java堆和方法區的垃圾收集


6、垃圾收集演算法:
1)標記-清除演算法(Mark-Sweep)是最基礎的收集演算法,是因為後續的收集演算法大多都是以標記-清除演算法為基礎,對其缺點進行改進而得到的。
問題1:標記-清除演算法如何實現的?
分為“標記”和“清除”兩個階段實現
a.首先通過可達性分析標記出所有需要回收的物件;
b.在標記完成後,統一回收掉所有被標記的物件,也可以反過來,標記存活的物件,統一回收所有未被標記的物件;

問題2:標記-清除演算法的缺點?
a.執行效率不穩定,如果Java堆中包含大量物件,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過程的執行效率都隨物件數量增長而降低;
b.記憶體空間的碎片化問題,標記、清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致當以後在程式執行過程中需要分配較大物件時無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

2)標記-複製演算法/半區複製演算法(Semispace Copying):簡稱為複製演算法,為了解決標記-清除演算法面對大量可回收物件時執行效率低的問題。
問題1:標記-複製演算法如何實現的?
它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。

問題2:標記-複製演算法的缺點?
a.如果記憶體中多數物件都是存活的,這種演算法將會產生大量的記憶體間複製的開銷
b.將可用記憶體縮小為了原來的一半,空間浪費未免太多了一點;

問題2:標記-複製演算法的使用?
現在的商用Java虛擬機器大多都優先採用了這種收集演算法去回收新生代。Andrew Appel針對具備“朝生夕滅”特點的物件(98%物件朝夕滅),提出了一種更優化的半區複製分代策略,現在稱為“Appel式回收”。HotSpot虛擬機器的Serial、ParNew等新生代收集器均採用了這種策略來設計新生代的記憶體佈局。Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配記憶體只使用Eden和其中一塊Survivor。發生垃圾收集時,將Eden和Survivor中仍然存活的物件一次性複製到另外一塊Survivor空間上,然後直接清理掉Eden和已用過的那塊Survivor空間

3)標記-整理演算法(Mark-Compact):是讓所有存活的物件都向記憶體空間一端移動,然後直接清理掉邊界以外的記憶體。
問題1:標記-整理演算法的缺點:
a.移動使的記憶體回收時會更復雜
b.如果移動存活物件,尤其是在老年代這種每次回收都有大量物件存活區域,移動存活物件並更新所有引用這些物件的地方將會是一種極為負重的操作,而且這種物件移動操作必須全程暫停使用者應用程式才能進行,可能會導致“Stop The World”的停頓現象

問題2:站在標記-整理演算法的角度進一步理解標記-清除演算法的缺點:
如果跟標記-清除演算法那樣完全不考慮移動和整理存活物件的話,彌散於堆中的存活物件導致的空間碎片化問題就只能依賴更為複雜的記憶體分配器和記憶體訪問器來解決。譬如通過“分割槽空閒分配連結串列”來解決記憶體分配問題(計算機硬碟儲存大檔案就不要求物理連續的磁碟空間,能夠在碎片化的硬碟上儲存和訪問就是通過硬碟分割槽表實現的)。記憶體的訪問是使用者程式最頻繁的操作,甚至都沒有之一,假如在這個環節上增加了額外的負擔,勢必會直接影響應用程式的吞吐量。

備註:針對以上三種垃圾收集演算法的思考?
思考1:基於移動物件使的記憶體回收時更復雜,不移動則記憶體分配時會更復雜,那麼我們可以從以下停頓時間和吞吐量兩個角度來分析:
a.從垃圾收集的停頓時間來看,不移動物件停頓時間會更短,甚至可以不需要停頓
b.但是從整個程式的吞吐量來看,移動物件會更划算。吞吐量的實質是賦值器(“使用者程式”或“使用者執行緒”代替)與收集器的效率總和。不移動物件會使得收集器的效率提升一些,但因記憶體分配和訪問相比垃圾收集頻率要高得多,這部分的耗時增加,總吞吐量仍然是下降的。

思考2:不同收集器使用的垃圾回收演算法應用例項?
a.HotSpot虛擬機器裡面關注吞吐量的Parallel Scavenge收集器是基於標記-整理演算法的。
b.關注延遲的CMS收集器則是基於標記-清除演算法的
c.還有一種“和稀泥式”解決方案可以不在記憶體分配和訪問上增加太大額外負擔,做法是讓虛擬機器平時多數時間都採用標記-清除演算法,暫時容忍記憶體碎片的存在,直到記憶體空間的碎片化程度已經大到影響物件分配時,再採用標記-整理演算法收集一次,以獲得規整的記憶體空間。基於標記-清除演算法的CMS收集器面臨空間碎片過多時採用的就是這種處理辦法。

參看書籍:深入理解Java虛擬機器:JVM高階特性與最佳實踐第3版.pdf,如有需要歡迎留言!