1. 程式人生 > >jvm優化_day02

jvm優化_day02

垃圾回收介紹
1.什麼是垃圾回收?
    程式在執行的過程中,必然會向系統申請記憶體資源,而已經沒有用了的資源,如果不回收掉,最終就會導致記憶體溢位,因此需要垃圾回收
2.C/C++語言的垃圾回收
	在C/C++語言中,沒有自動垃圾回收機制,是通過new關鍵字申請記憶體資源,通過delete關鍵字釋放記憶體資源。如果,程式設計師在某些位置沒有寫delete進行釋放,那麼申請的物件將一直佔用記憶體資源,最終可能會導致記憶體溢位。
3.Java語言的垃圾回收
	在Java語言中,有了自動的垃圾回收機制,也就是我們熟悉的GC。
垃圾回收演算法
常見的垃圾回收演算法:
	* 引用計數法
	*
標記清除法 * 標記壓縮法 * 複製演算法 * 分代演算法 1.引用計數法的原理:new一個物件A的時候,gc會為該物件分配一個引用計數器;每當有一個其他的物件B持有物件A的引用的時候,物件A的引用計數器的數值+1;每當有一個其他的物件C放棄對物件A的引用的持有權,物件A的引用計數器的數值-1;最終,當執行gc的時候,gc會將所有的引用計數器數值為0(:沒有被任何物件引用)的物件回收 1.1 引用計數法的優缺點: 優點: - 實時性較高,無需等到記憶體不夠的時候,才開始回收,執行時根據物件的計數器是否為0,就可以直接回收。 - 在垃圾回收過程中,應用無需掛起。如果申請記憶體時,記憶體不足,則立刻報outofmember 錯誤。 -
區域性,更新物件的計數器時,只是影響到該物件,不會掃描全部物件。 缺點: - 每次物件被引用時,都需要去更新計數器,有一點時間開銷。 - 浪費CPU資源,即使記憶體夠用,仍然在執行時進行計數器的統計。 - 無法解決迴圈引用問題。(最大的缺點) 2.標記清除法的原理: 標記清除法分為2個階段,第一階段:標記階段,當執行gc時,引用中除了gc的執行緒,其他所有的執行緒全部暫停活動,gc從一個root根節點出發,遍歷找出所有被root節點直接或間接引用了的物件,並標記為1,其他沒有被root根節點引用到的物件標記為0;第二階段:清除階段,清除所有被標記為0的物件,然後重置標記為1的物件為0,喚醒其他的執行緒 2.1
標記清除法的優缺點: 優點: - 標記清除演算法解決了引用計數演算法中的迴圈引用的問題,沒有從root節點引用的物件都會被回收。 缺點: - 效率較低,標記和清除兩個動作都需要遍歷所有的物件,並且在GC時,需要停止應用程式,對於互動性要求比較高的應用而言這個體驗是非常差的。 - 通過標記清除演算法清理出來的記憶體,碎片化較為嚴重,因為被回收的物件可能存在於記憶體的各個角落,所以清理出來的記憶體是不連貫的。 2.2 GC時,停止其他執行緒的原因: 標記清除法執行GC時,需要遍歷操作,如果此時出現了物件的新建或者銷燬,將會影響GC的判斷;比如當我們遍歷list集合的時候,如果同時進行增刪操作,會發生併發修改異常 3.標記壓縮法的原理: 基本同標記清除法,當進入清除階段時,首先將存活的物件壓縮到記憶體中的一端,然後將非存活區的所有物件回收 3.1 標記壓縮法的優缺點: 優點: - 解決了標記清除法中造成的記憶體碎片化的問題 缺點: - 對物件的壓縮移動,影響了效能 4.複製演算法的原理: 將記憶體一分為2,這兩塊記憶體區域完全相同,通常稱為from區和to區;GC只對From區的物件進行回收;每次執行GC時,GC會將From區的存活物件copy到To區,然後將From區的資料全部回收;此時這一次的To區就成為了下一次GC的From區,而這一次的From去就成為下一次GC的To區 4.1 複製演算法的優缺點: 優點: - 在垃圾物件多的情況下,效率較高 - 清理後,記憶體無碎片 缺點: - 在垃圾物件少的情況下,不適用,如:老年代記憶體 - 分配的2塊記憶體空間,在同一個時刻,只能使用一半,記憶體使用率較低 5.分代演算法: 前面介紹了多種回收演算法,每一種演算法都有自己的優點也有缺點,誰都不能替代誰,所以根據垃圾回收物件的特點進行選擇,才是明智的選擇。 分代演算法其實就是這樣的,根據回收物件的特點進行選擇,在jvm中,年輕代適合使用複製演算法,老年代適合使用標記清除或標記壓縮演算法。
JVM垃圾回收演算法
	年輕代使用複製演算法,老年代使用標記清除或標記壓縮演算法
JVM垃圾回收演算法原理:
	在GC開始的時候,物件只會存在於Eden區和名為“From”的Survivor區,Survivor區“To”是空的;當執行GC的時候,GC會將Eden區中存活的物件copy到SurvivorTo區;同時,對SurvivorFrom區的存活物件進行篩選,當物件的年齡到達閥值,就將這個物件放到老年代,沒達到閥值的就放入SurvivorTo區;如果SurvivorTo區被填滿,就直接都放到老年代中,(可以通過-XX:MaxTenuringThreshold來設定閥值)

在這裡插入圖片描述

垃圾收集器
	垃圾收集器是對垃圾收集演算法的具體實現
		
1.CMS垃圾收集器: 
	CMS全稱 Concurrent Mark Sweep,是一款併發的、使用標記-清除演算法的垃圾回收器(導致記憶體碎片化嚴重),該回收器是針對老年代垃圾回收的,通過引數-XX:+UseConcMarkSweepGC進行設定。
* 初始化標記: 從Old區的roots節點出發,找出Old區的所有可達的物件,此過程會stw
* 併發標記: 從初始化標記的可達物件中,找出所有的存活物件;並且,在這段時間,如果有新的物件進入Old區,或者物件不再被roots節點引用,會被標記為dirty,此過程不會stw
* 預清理: 從被標記為dirty的物件中找出所有的roots可達的物件,此過程依然會產生dirty物件
* 重新標記: 從Olds區的roots節點和dirty表出發,再一次掃描出所有的存活的物件,確保所有可達存活物件被標記
* 併發清理: 清除那些沒有標記的物件並且回收空間
* 併發重置: 對標記的表和記錄dirty的表做清零。並且重置CMS收集器的其他資料結構,等待下一次垃圾回收。
1.1 設定啟動引數
-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m


2.G1垃圾收集器:
	G1垃圾收集器分為三種模式:Young GC, Mixed GC, Full GC;G1垃圾收集器最大的特點便是取消了物理上將記憶體劃分為年輕代,老年代區域,取而代之的是將記憶體分為了多個Region區域,由這些Region區組成各個邏輯上的年輕代和老年代;為了能在Young GC模式下快速找到roots,每個Region初始化時還會初始化一個Remembered Set,用於跟蹤物件的引用,Region區被初始化後預設又會被封為多個512kb的card,因此Remembered Set中便記錄某某region中的某某card引用了當前region區中的物件
	通常情況下,new出來的物件都會放到Eden區,但是如果一個物件所佔記憶體超過了分割槽容量的50%,就會被認為是一個大物件,會直接分配為老年代;但是有可能這個大物件只是一個臨時物件,因此G1垃圾收集器又有了一個Humongous區,專門用來儲存巨型物件
	
2.1 G1垃圾收集器的三種模式
* Young GC模式:
	當邏輯上的Eden區不夠用時觸發,G1垃圾收集器會用複製演算法將多個Eden區和Survivor From區的存活物件複製到一個新的Survivor To區,如果Survivor To區的記憶體不夠用,就將部分資料直接晉升為邏輯老年區
* Mixed GC模式:
	混合GC,不是Full GC,回收整個Young Region和部分Old Region,預設情況下,當Old Region的已用記憶體超過總記憶體的45%,觸發Mixed GC;主要分為兩階段,全域性併發標記(基本同CMS垃圾收集器,唯一區別是Mixed GC最終拷貝存活物件,而CMS是直接清理),拷貝存活物件
	
2.2 G1收集器相關引數:
- -XX:+UseG1GC
  - 使用 G1 垃圾收集器
- -XX:MaxGCPauseMillis
  - 設定期望達到的最大GC停頓時間指標(JVM會盡力實現,但不保證達到),預設值是 200 毫秒。
- -XX:G1HeapRegionSize=n
  - 設定的 G1 區域的大小。值是 2 的冪,範圍是 1 MB 到 32 MB 之間。目標是根據最小的 Java 堆大小劃分出約 2048 個區域。
  - 預設是堆記憶體的1/2000- -XX:ParallelGCThreads=n
  - 設定STW工作執行緒數的值。將n的值設定為邏輯處理器的數量。n 的值與邏輯處理器(CPU)的數量相同,最多為 8- -XX:ConcGCThreads=n
  - 設定並行標記的執行緒數。將 n 設定為並行垃圾回收執行緒數 (ParallelGCThreads)1/4 左右。
- -XX:InitiatingHeapOccupancyPercent=n
  - 設定觸發標記週期的 Java 堆佔用率閾值。預設佔用率是整個 Java 堆的 45%。
eg VM Options: XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -Xmx256m


2.3 G1收集器優化建議:
- 年輕代大小
  - 避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設定年輕代大小。
  - 固定年輕代的大小會覆蓋暫停時間目標。
- 暫停時間目標不要太過嚴苛
  - G1 GC 的吞吐量目標是 90% 的應用程式時間和 10%的垃圾回收時間(9:1)- 評估 G1 GC 的吞吐量時,暫停時間目標不要太嚴苛。目標太過嚴苛表示您願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量。
  
2.4 GC視覺化工具:
GC Easy 視覺化工具
GC Easy是一款線上的視覺化工具,易用、功能強大,網站:http://gceasy.io/

-XX:+PrintGC 輸出GC日誌
-XX:+PrintGCDetails 輸出GC的詳細日誌
-XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式)
-XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800-XX:+PrintHeapAtGC 在進行GC的前後打印出堆的資訊
-Xloggc:../logs/gc.log 日誌檔案的輸出路徑
eg:   -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:F://test//gc.log

在這裡插入圖片描述

迴圈引用程式碼
class TestA{
  public TestB b;
}

class TestB{
  public TestA a;
}

public class Main{
    public static void main(String[] args){
        A a = new A();
        B b = new B();
        a.b=b;
        b.a=a;
        a = null;
        b = null;
    }
}

說明:
	A a = new A(),在堆記憶體中申請了一塊記憶體空間A,並將地址值儲存到棧記憶體變數a中;B b = new B(),在堆記憶體中申請了一塊記憶體空間B,並將地址值儲存到棧記憶體變數b中;
	a.b=b,將物件b的引用儲存到堆記憶體的A區域中;b.a=a,將物件a的引用儲存到堆記憶體B中;
	a = null,b = null;對棧記憶體中的變數a,b賦值為null,此時棧記憶體不再持有堆記憶體中A,B區域的引用;正常來說,那麼它們的引用計數應該為0,但是它們的對記憶體中依然保持著各自的引用,也就是引用計數器的值始終的變化為:
0-->1-->2-->1
標記清除法原理圖

在這裡插入圖片描述

標記壓縮法原理圖

在這裡插入圖片描述

CMS垃圾回收器原理圖

在這裡插入圖片描述