JVM的堆和棧以及GC演算法的介紹
JVM就是java虛擬機器,我們可以把它理解成一個作業系統,每個不同的平臺都有不同的JVM,比如linux系統和windows系統,就是因為這個原因所以java程式就有了一個很突出的特性就是 跨平臺性
其中JVM中的堆和棧這兩個東西以及它的垃圾回收機制是我們平時遇到得最多的,那麼下面就介紹一下JVM的堆疊以及GC。
- 1.JVM的堆疊
棧:在jvm中棧用來儲存一些物件的引用、區域性變數以及計算過程的中間資料,在方法退出後那麼這些變數也會被銷燬。它的儲存比堆快得多,只比CPU裡的暫存器慢
堆:用來儲存程式中的一些物件,比如你用new關鍵字建立的物件,它就會被儲存在堆記憶體中,但是這個物件在堆記憶體中的首地址會儲存在棧中。
棧記憶體在JVM中預設是1M,可以通過下面的引數進行設定
-Xss
最小堆記憶體在JVM中預設實體記憶體的64分之1,最大堆記憶體在JVM中預設實體記憶體4分之一,且建議最大堆記憶體不大於4G,並且設定-Xms=-Xmx避免每次GC後,調整堆的大小,減少系統記憶體分配開銷
-Xms
-Xmx
在jvm的堆記憶體中有三個區域:
1.年輕代:用於存放新產生的物件。
2.老年代:用於存放被長期引用的物件。
3.持久帶:用於存放Class,method元資訊。
如圖:
- 一.年輕代
年輕代中包含兩個區:Eden 和survivor,並且用於儲存新產生的物件,其中有兩個survivor區如圖:
可以使用引數配置年輕代的大小,如果配置它為100M那麼就相當於2*survivor+Eden = 100M
-Xmn
可以配置Eden 和survivor區的大小,這裡配置的是比值,jvm中預設為8,意思就是Eden區的記憶體比上survivor的記憶體等於8,如果年輕代的Xmn配置的100M,那麼Eden就會被分配80M記憶體,每個survivor分配10M記憶體
-XX:SurvivorRatio
還可以配置年輕代和老年代的比值,這裡需要注意:老年代的記憶體就是通過這個比值設定,jvm沒有給你直接設定老年代記憶體大小的引數;如果整個堆記憶體設為100M並且在這裡設定年輕代和老年代的比值為7,如果持久代佔用了10M,那麼100M-10M=90M這裡的90M就是老年代和年輕代的記憶體總和,且年輕代佔用(90/(7+1)*7)的記憶體,老年代就佔用(90/(7+1)*1)的記憶體。
-XX:NewRatio
- 二.老年代
年輕代在垃圾回收多次都沒有被GC回收的時候就會被放到老年代,以及一些大的物件(比如快取,這裡的快取是弱引用),這些大物件可以不進入年輕代就直接進入老年代(1.防止新生代有大量剩餘的空間,而大物件建立導致提前發生GC;2.防止在eden區和survivor區的大物件複製造成效能問題),這個可以通過如下引數設定,表示單個物件超過了這個值就會直接到老年帶(預設為0):
-XX:PretenureSizeThreshold
並且大的陣列物件也會直接放到老年代,比如array和arrayList(底層用陣列實現),因為陣列需要連續的空間儲存資料。
- 三.持久代
持久代用來儲存class,method元資訊,大小配置和專案規模,類和方法的數量有關,一般配置128M就夠了,設定原則是預留30%空間,它可以通過如下引數進行大小配置:
-XX: PermSize
-XX: MaxPermSize
持久代也可能會被GC回收,如果持久代理的常量池沒有被引用以及一些無用的類資訊和類的Class物件也會被回收。
- 四.JVM記憶體垃圾收集演算法
1.引用計數器的方法
類似於Objective-C的記憶體回收,在OC語言不使用arc機制時,在建立物件,且被引用時候也會對計數器加1,使用完成後會呼叫release方法就會對計數器減1
jvm的這個演算法也和上面的機制類似,但是他不能解決物件迴圈引用問題,也不好解決精確的計算,因為java程式開發,記憶體回收是對我們透明的,而OC是在程式碼層面自己去手動控制
-2.根搜尋演算法
有向圖演算法,從GC Roots開始向下面搜尋,搜尋走過的路徑叫做引用連結,當一個物件到達GC Roots沒有任何引用鏈時,則這個物件就是不可達物件,就可以被回收,但是回收的時候會篩查出覆蓋了finalze()方法且該物件finalze()方法沒有被虛擬機器呼叫過放入F-Queue佇列然後執行F-Queue佇列中物件的finalze()方法後如果該物件重新與某個GC Roots物件相關聯,那麼會將該物件從回收佇列中移除
比如jvm中棧中指向堆中物件的指標就可以理解成一種GC Roots,因為棧中儲存的是物件的指標指向的是堆中物件的首地址,當棧中的指標沒有了那麼堆中的物件就是不可達物件,就會被GC回收
- 五.jvm記憶體垃圾回收演算法
1.複製演算法 (用於新生代)
用於新生代從Eden區到survivor0或者survivor1移動的時候
從根集合掃描如果物件被引用,就會被copy到survivor0或者survivor1,然後剩下的都是不可達物件,就可以被回收掉,然後S0或者S1的物件就會到老年代中,在存活物件比較少的時候很高效並且不會產生記憶體碎片,就是記憶體需要額外劃分一塊survivor區出來
2.標記清除演算法 (用於老年代)
就是使用根搜尋演算法掃描,如果可達就標記,當掃描完成後,就對未標記的物件進行回收,它不需要像複製演算法一樣,需要一個新的記憶體區,而且不會對物件進行copy,這種對物件存活的多的情況下很高效,但是這樣會產生記憶體碎片
3.標記整理壓縮演算法 (用於老年代)
就是在標記清除演算法之後對記憶體碎片進行整理,只是他會對沒有清理的物件進行移動,代價高
- 六.記憶體容量配置建議
響應時間優先以及吞吐量優先型別的年輕代就儘可能設定大,老年代就可以設定小一點,因為在年輕代設定大了,那麼年輕代的GC收集頻率就會小,而且減少到達老年代的物件,最後儘量使老年代裡面管理的物件是可用率高的,這樣的話就會減少在年輕帶的GC頻次,並且防止進入老年代的物件過多觸發FGC而stop-the-world