網際網路技術學習27———垃圾回收演算法+垃圾收集器
垃圾回收Garbage Collection,簡稱GC。GC中的垃圾指的是記憶體中不會再被使用的物件,而回收就是相當於把垃圾“倒掉”。垃圾回收有很多演算法:引用計數法、標記壓縮法、複製演算法、分代、分割槽。
在java堆中,新生代/老年代 =1/2 或1/3比較合適
垃圾回收演算法
引用計演算法:這是一個比較古老而經典的垃圾收集演算法,其核心就是在物件被引用的時候計數器加1,而當引用失效時減1,但是這種方式存在嚴重的問題,無法處理迴圈引用的情況,還有就是每次進行加減操作是比較浪費系統性能。
標記清除法:分為標記和清除兩個階段,這種方式的弊端就是空間碎片問題。垃圾回收後的空間是不連續的。不連續的記憶體空間工作效率要低於連續的空間,且造成了記憶體的浪費。
標記壓縮法:標記壓縮法在標記清除法的基礎上做了優化,把存活的物件壓縮到記憶體的一端,而後進行垃圾清理
分代演算法:根據物件的特點把記憶體分為N塊,然後根據每個記憶體塊的特點使用不同的演算法。如Java中的新生代和老年代
分割槽演算法:主要就是將整個記憶體分成N多個小空間,每個小空間都可以獨立使用,這樣細粒度的控制一次回收多少個小空間和哪些小空間,而不是對整記憶體進行GC,從而提高效能,並減少GC停頓時間。
對於新生代和老年代來說,新生代回收的頻率很高,但是每次回收耗時短,而老年代gc頻率較低,但是每次耗時長,所以應該儘量減少老年代的GC。
老年代中物件已經比較穩定,經過多次GC仍未被回收,在GC過程中,清理的記憶體比較小,存活的物件佔比非常高,因此老年代使用標記壓縮法。
垃圾回收時的停頓現象
為了讓垃圾回收機高效的進行,大部分情況下,會要求系統進入一個停頓狀態(stop the world)。停頓的目的是終止所有應用執行緒,只有這樣系統才不會有新的垃圾產生,同時停頓保證了系統狀態在某個瞬間的一致性,也有益於更好的標記垃圾物件。
物件如何進入老年代
新建立的物件放置在新生代的eden區,如果沒經歷過gc,改新建立的物件會一直放在eden區。經過一次gc後,進入新生代的s0或s1區,在新生代中物件每經歷過一次gc年齡就會增加1,在物件的年齡達到一定大小的時候,就會從新生代進入老年代,物件的年齡應該是該物件經歷過的gc次數決定的。虛擬機器提供了一個引數來控制信新生代物件的最大年齡,當超過這個年齡範圍就會晉升為老年代。另外,大物件即新生代eden區無法裝入時,也會直接進入老年代,JVM中存在相關引數可以設定物件大小超過指定對的大小之後,直接晉升為老年代。
-XX:MaxTenuringThresold 指定新生代物件經過多少次GC就進入老年代。預設情況下是15
-XX:PretenureSizeThreshold 指定不經過新生代直接進入老年代的物件大小
Test05.java
package com.jvmTest;
import java.util.HashMap;
import java.util.Map;
/**
* Created by BaiTianShi on 2018/9/24.
*/
public class Test05 {
public static void main(String[] args) {
Map<Integer,byte[]> map = new HashMap<>();
for (int i = 0; i < 5 ; i++) {
byte[] b = new byte[1024*1024];
map.put(i,b);
}
}
}
引數設定:
-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
控制檯列印:
上述程式碼中,設定了-XX:PretenureSizeThreshold=1000,即指定不經過新生代而直接晉升到老年代的物件大小為1000位元組,而程式碼中new byte[1024*1024] 的大小為1M>1000k,屬於大獨享直接放在老年代,因此老年代userd 5136k。
Test06.java
package com.jvmTest;
import java.util.HashMap;
import java.util.Map;
/**
* Created by BaiTianShi on 2018/9/24.
*/
public class Test06 {
public static void main(String[] args) {
Map<Integer,byte[]> map =new HashMap<>();
for (int i = 0; i < 5*1024 ; i++) {
byte[] b =new byte[1000];
map.put(i,b);
}
}
}
JVM設定引數: -Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
控制檯列印:
老年代僅僅使用了56k,這是因為虛擬機器對於體積不大的物件,會優先把資料分配到TLAB區域中,因此就失去了再老年代分配的機會。JVM中每個執行緒,預設是使用TLAB的。
TLAB
TLAB即Thread Local Allocation Buffer 即執行緒本地分配快取,每一個執行緒都會產生一個TLAB,是執行緒獨享的工作區域,JVM使用TLAB避免多執行緒衝突問題,提高物件的分配效率。TLAB空間一般不會太大,當大物件無法再TLAB分配至,會直接分配到堆上。
-XX:+UseTLAB 使用TLAB
-XX:-UseTLAB 禁用TLAB
-XX:+TLABSize 設定TLAB大小
-XX:TLABRefillWasteFraction 設定TLAB空間的單個物件的大小嗎,它是一個比例,預設為64,即物件大於整個空間的1/64時,則在堆中建立此物件。
-XX:+PrintTLAB 列印TLAB資訊
-XX:ResizeTLAB 自動調整TLABRefillWasteFraction閾值。
一個物件的建立流程
一個物件建立在什麼位置,JVM會有一個比價細節的流程,根據資料的大小,引數的設定,決定如何建立分配,以及其位置
垃圾收集器
在tomcat的catalina.sh中使用JAVA_OPT配置初始堆大小、最大堆大小
在JVM中,垃圾回收器有多種:序列垃圾回收器、並行垃圾回收器、CMS回收器(主流)、G1回收器
序列回收器:使用單執行緒進行垃圾回收的垃圾回收器,對於並行能力較弱的計算機來說,序列回收器的專注性和獨佔性往往有更好的效能表現。
+XX:UseSerialGC 設定新生代序列回收器和老年代序列回收器
並行回收器(新生代ParNew回收器):在序列回收器上做了改進,可以使用多個執行緒同時進行垃圾回收,對於計算能力強的計算機而言,可以有效的縮短垃圾回收所需要的時間,ParNew回收器是一個工作在新生代的垃圾回收器,它只是簡單的將序列回收器多執行緒話,它的回收策略和演算法與序列回收器一樣。
-XX:+UseParNewGC 使用新生代ParNew垃圾回收器
-XX:ParallelGCThreads 指定ParNew回收器工作時的執行緒數量,一般最好和計算機cpu核數相當,避免過多執行緒影響效能。
並行回收器(新生代ParallelGC):新生代ParalleGC回收器,它是使用了複製演算法的回收器,也就是多執行緒獨佔形式的收集器,它有個非常重要的特點,即非常關注系統的吞吐量。提供了2個非常關鍵的引數控制系統的吞吐量。
-XX:MaxGCPauseMillis 設定最大垃圾收集停頓時間,可將虛擬機器在GC的停頓時間控制在MaxGCPauseMillis範圍內。如果希望減少GC停頓時間,可將MaxGCPauseMillis設定的很小,但是會導致GC頻繁,從而增加GC總時間,降低了吞吐量,所以需要根據實際情況設定該值。
-XX:GCTimeRatio 設定吞吐量大小,它是一個0-100之間的數,預設情況下是99,那麼系統將花費不超過1/(1+n)的時間用於垃圾回收。
-XX:UseAdaptiveSizePolicy 開啟自適應模式,在這種模式下,新生代大小、eden和from或to的比例,以及晉升老年代的物件年齡引數都會被自動調整,以達到堆大小、吞吐量、停頓時間的平衡點
並行回收器(老年代ParallelOldGC):老年代ParallelOldGC回收器也是一種多執行緒回收器,和新生代的ParallelGC回收器一樣,也是一種關注吞吐量的回收器,使用了標記壓縮演算法進行實現。
-XX:+ParallelOldGC 設定使用老年代ParallelOldGC收集器
-XX:ParalleGCThreads 設定垃圾收集時的執行緒數量
CMS回收器(主流):全稱Concurrent Mark Sweep 意思為併發標記清除,使用的是標記清除演算法,主要關注系統停頓時。且適用於老年代。
-XX:+UseConcMarkSweepGC 使用cms垃圾回收器
-XX:ConcGCThreads 設定併發執行緒數
CMS並不是獨佔的垃圾回收器,在其進行垃圾回收的過程,程式仍然在工作(其實remark標記階段仍然需要stop the world),又會有新的垃圾不斷產生,所以在使用CMS的過程中要保證應用程式的記憶體足夠可用,CMS不會等到應用程式飽和的時候才去回收垃圾,而是在某一個閾值的時候就開始回收,回收閾值可以使用指定的引數進行設定-XX:CMSInitiatingOccupancyFraction來指定,預設為68,即當老年代中的使用空間達到68的時候,會執行CMS回收。如果記憶體使用增長過快,在CMS執行過程中,已經出現了記憶體不足的情況,此時CMS回收就會失敗,虛擬機器將啟動老年代序列回收器進行垃圾回收,這會導致應用程式中斷,直到垃圾回收完成後才會正常工作,這個過程GC停頓時間可能較長,所以-XX:CMSInitiatingOccupancyFraction的設定要根據實際的情況來決定。
標記清除法有個缺點就是存在碎片的問題,在CMS中存在相關的引數如下:
-XX:+UseCMSCompactAtFullCollection 設定在CMS回收完成之後進行一次碎片整理
-XX:CMSFullGCBeforeCompaction 設定進行多少次CMS回收之後,對記憶體進行一次壓縮
G1回收器:全稱Garbage-First,它是在jdk1.7中提出的垃圾回收器,長期目標是為了取代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬於分代垃圾回收器,區分新生代和老年代,依然有eden和from/to區,它並不要求整個eden區或者新生代、老年代空間都連續,它使用了分割槽演算法。
並行性:G1回收期間可以多執行緒同時工作
併發性:G1擁有與應用程式交替執行能力,部分工作可以與應用程式同時執行,在整個GC期間不會完全阻塞應用程式
分代GC:G1是一個分代收集器,但是它兼顧新生代和老年代一起工作,之前的垃圾收集器他們或者在新生代工作,或者在老年代工作,這是一個很大的不同。
空間整理:G1在回收過程中,不會像CMS那樣在若干次GC之後需要進行碎片整理,G1採用了有效複製物件的方式,減少空間碎片。
可預見性:由於分割槽的原因,G1可以只選擇部分割槽域進行回收,縮小了回收的範圍,提高了效能。
-XX:+UseG1GC 使用G1收集器
-XX:MaxGCPauseMillis 指定最大停頓時間
-XX:ParallelGCThreads 設定並行回收的執行緒數量