JVM调优系列:(四)GC垃圾回收
跟蹤收集算法:
復制(copying):將堆內分成兩個相同空間,從根(ThreadLocal的對象,靜態對象)開始訪問每一個關聯的活躍對象,將空間A的活躍對象全部復制到空間B,然后一次性回收整個空間A。因為只訪問活躍對象,將所有活動對象復制走之后就清空整個空間,不用去訪問死對象,不需要標記驟,所以遍歷空間的成本較小,但需要巨大的復制成本和較多的內存。
標記清除(mark-sweep):收集器先從根開始訪問所有對象,標記活躍對象。然后再遍歷一次整個內存區域,把所有沒有標記活躍的對象進行回收處理。該算法遍歷整個空間的成本較大暫停時間隨空間大小線性增大,而且標記結束后,相鄰的不可觸及對象所占空間會合并在一起,但不會進行整理,將產生越來越多的碎片。
標記整理(mark-sweep-compact):收集器先從根開始訪問所有對象,先標記活躍對象,然后清除未標記的對象,再將堆中活躍對象復制到堆的底部,使活躍對象緊湊的排列在一起,避免碎片產生。
?常用GC收集器類型:
1.單CPU串行收集器(Serial Collector)使用 -XX:+UseSerialGC,策略為:
年輕代串行復制,-XX:MaxTenuringThreshold來設置對象復制的次數。
年老代串行標記整理。
使用 -XX:+UseParallelGC ,也是JDK -server的默認值, 它不能和CMS配合使用。策略為:
1.年輕代暫停應用程序,多個垃圾收集線程并行的復制收集,線程數默認為CPU個數,CPU很多時,可用–XX:ParallelGCThreads=線程數。
??? 2.年老代暫停應用程序,與串行收集器一樣,單垃圾收集線程標記整理。使用-XX:+UseParallelOldGC打開年老代并行垃圾回收的線程數.
可以使用-XX:MaxGCPauseMillis 和 -XX:GCTimeRatio 來調整GC的時間。
-XX:MaxGCPauseMillis=毫秒:指定垃圾回收時的最長暫停時間,如果指定了此值的話,堆大小和垃圾回收相關參數會進行調整以達到指定值。
-XX:GCTimeRatio :?吞吐量為垃圾回收時間與非垃圾回收時間的比值,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用于垃圾回收。默認情況為99,即1%的時間用于垃圾回收。
Parallel?Copying?Collector用-XX:UseParNewGC參數配置,它主要用于新生代的收集,此GC可以配合CMS一起使用。
Parallel?Mark-Compact?Collector用-XX:UseParallelOldGC參數配置,此GC主要用于老生代對象的收集(jdk1.6)。
??使用-XX:+UseConcMarkSweepGC, 主要用于老生代,策略為:
??? 1.年輕代同樣是暫停應用程序,多個垃圾收集線程并行的復制收集。
??? 2.年老代則只有兩次短暫停,其他時間應用程序與收集線程并發的清除。
采用兩次短暫停來替代標記整理算法的長暫停,它的收集周期:??初始標記(CMS-initial-mark) -> 并發標記(CMS-concurrent-mark) -> 重新標記(CMS-remark)-> 并發清除(CMS-concurrent-sweep) ->并發重設狀態等待下次CMS的觸發(CMS-concurrent-reset)。
它的主要適合場景是對響應時間的重要性需求大于對吞吐量的要求,能夠承受垃圾回收線程和應用線程共享處理器資源,并且應用中存在比較多的長生命周期的對象的應用。但CMS收集算法在最為耗時的內存區域遍歷時采用多線程并發操作,但對于服務器CPU資源不夠的情況下,其實對性能是沒有提升的,反而會導致系統吞吐量的下降;
CMS默認啟動的回收線程數目是??(ParallelGCThreads+ 3)/4) ,可以通過來設定
-XX:ParallelGCThreads=N 來調整年輕代的并行收集線程數, 年輕代的并行收集線程數默認是(cpu <= 8) ? cpu : 3 +((cpu * 5) / 8).
-XX:ParallelCMSThreads=N調整CMS收集線程數,CMS默認啟動的回收線程數目??(ParallelGCThreads+ 3)/4)
-XX:+UseCMSCompactAtFullCollectionCMS是不會整理堆碎片的,因此為了防止堆碎片引起full gc,通過會開啟CMS階段進行合并碎片選項. 為了減少第二次暫停的時間,開啟并行remark:-XX:+CMSParallelRemarkEnabled。如果remark還是過長的話,可以開啟-XX:+CMSScavengeBeforeRemark選項,強制remark之前開始一次minor gc,減少remark的暫停時間,但是在remark之后也將立即開始又一次minor gc.
-XX:+CMSClassUnloadingEnabled-XX:+CMSPermGenSweepingEnabled一般情況下,持久代是不會進行GC的,通過以上參數進行強制設置。
-XX+UseCMSCompactAtFullCollection?在FULL GC的時候, 對年老代的壓縮
-XX:CMSFullGCsBeforeCompaction對年老代的壓縮開啟的情況下,多少次FULL GC后進行內存壓縮,整理
-XX:+UseCMSInitiatingOccupancyOnly?指示只有在old generation在使用了初始化的比例后concurrentcollector啟動收集
-XX:CMSInitiatingOccupancyFraction=80默認CMS是在tenured generation沾滿68%的時候開始進行CMS收集, 如果你的年老代增長不是那么快,并且希望降低CMS次數的話,可以適當調高此值
-XX:+AggressiveHeap?試圖是使用大量的物理內存,長時間大內存使用的優化,CMS收集生效
-XX:+CMSIncrementalMode設置為增量模式, 單CPU情況使用
?
?
-XX:+UseAdaptiveSizePolicy?設置此選項后,并行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用并行收集器時,一直打開。
-XX:MaxGCPauseMillis=100?設置每次年輕代垃圾回收的最長時間,如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值
-XX:GCTimeRatio=19?設置垃圾回收時間占程序運行時間的百分比,公式為1/(1+N),默認情況為99,即1%的時間用于垃圾回收。
?
JVM GC機制:
在Java語言里面,可作為GC Roots的節點主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表)中。如果要使用可達性分析來判斷內存是否可回收的,那分析工作必須在一個能保障一致性的快照中進行——這里“一致性”的意思是整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不可以出現分析過程中,對象引用關系還在不斷變化的情況,這點不滿足的話分析結果準確性就無法保證。這點也是導致GC進行時必須暫停的其中一個重要原因.
HOTSPOT使用準確式GC, 從外部記錄下類型信息,存成映射表。HotSpot把這樣的數據結構叫做OopMap,需要虛擬機里的解釋器和JIT編譯器都有相應的支持,在類加載完成的時候,HotSpot就把對象內什么偏移量上是什么類型的數據計算出來,在JIT編譯過程中,也會在特定的位置記錄下棧里和寄存器里哪些位置是引用, 這樣GC在掃描時就就可以直接得知這些信息了。HotSpot是用“解釋式”的方式來使用OopMap的,每次都循環變量里面的項來掃描對應的偏移量。
解釋式: 每次都遍歷原始的映射表,循環的一個個偏移量掃描過去,HOTSPOT采用
編譯式: 為每個映射表生成一塊定制的掃描代碼(想像掃描映射表的循環被展開的樣子),以后每次要用映射表就直接執行生成的掃描代碼。
?
在OopMap的協助下,HotSpot可以快速準確地地完成GC Roots枚舉,但一個很現實的問題隨之而來,可能導致引用關系變化,或者說OopMap內容變化的指令非常多,如果為每一條指令都生成對應的OopMap,那將會需要大量的額外空間,這樣GC的空間成本將會變得很高。
所以HotSpot也的確沒有為每條指令都生成OopMap,前面已經提到,只是在“特定的位置”記錄了這些信息,這些位置被稱為安全點(Safepoint),即程序執行時并非在所有的地方都能停頓下來開始GC,只有在到達安全點時才能暫停。
每個被JIT編譯過后的方法也會在安全點記錄下OopMap,記錄了執行到該方法的某條指令的時候,棧上和寄存器里哪些位置是引用。這樣GC在掃描棧的時候就會查詢這些OopMap就知道哪里是引用了。Safepoint的選定既不能太少以至于讓GC等待時間太長,也不能過于頻繁以至于過分增大運行時的負荷。所以安全點的選定基本上是以程序“是否具有讓程序長時間執行的特征”為標準進行選定:
1、循環的末尾
2、方法臨返回前 / 調用方法的call指令后
3、可能拋異常的位置
?
如何讓GC發生時,讓所有線程(這里不包括執行JNI調用的線程)都跑到最近的安全點上再停頓下來。HotSpot采用主動式中斷,主動式中斷的是當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單地設置一個標志,各個線程執行時主動去輪詢這個標志,發現中斷標志為真時就自己中斷掛起。
對Java線程中的JNI方法,它們既不是由JVM里的解釋器執行的,也不是由JVM的JIT編譯器生成的,所以會缺少OopMap信息,HotSpot的解決方法是:所有經過JNI調用邊界(調用JNI方法傳入的參數、從JNI方法傳回的返回值)的引用都必須用“句柄”(handle)包裝起來。JNI需要調用Java API的時候也必須自己用句柄包裝指針。在這種實現中,JNI方法里寫的“jobject”實際上不是直接指向對象的指針,而是先指向一個句柄,通過句柄才能間接訪問到對象。這樣在掃描到JNI方法的時候就不需要掃描它的棧幀了,只要掃描句柄表就可以得到所有從JNI方法能訪問到的GC堆里的對象。
?
內存回收如何進行是由虛擬機所采用的GC收集器所決定的,而通常虛擬機中往往不止有一種GC收集器,目前HotSpot里面就包含有常用的garbage collectors:
·????????serial collector?:針對younggeneration的串行垃圾收集器,使用stop-the-world(就是暫停整個應用程序的執行)的形式,利用單線程通過復制live objects到survivor space或tenured generation的方法來進行垃圾收集。
·????????parallel scavenge?collector?:針對younggeneration的并行垃圾收集器,利用多個GC線程來進行垃圾收集,每個線程的GC方法和serial collector一樣。
·????????parallel ?new collector?:針對younggeneration的增強的并行垃圾收集器,以便可以和CMS一起使用。
·????????serial old collector?:針對tenuredgeneration的串行垃圾收集器,使用stop-the-world形式,利用單線程通過mark-sweep-compact的方法進行垃圾收集。
·????????parallel old collector?:針對tenuredgeneration的并行垃圾收集器,利用多線程進行垃圾收集,方法和serial old collector一樣。
·????????parallel compacting collector?:對于younggeneration使用和parallel new collector一樣的算法,對于tenured generation使用了新的算法(mark-summary-compact),該收集器用來替代parallel new collector和parallel oldcollector。
·????????concurrent mark-sweep collector?:對于younggeneration使用和parallel new collector一樣的算法,對于tenured generation使用跟應用程序并發的方式,收集期間也有引起stop-the-world的暫停Mark階段,也有伴隨著應用程序運行的并發 Mark和并發Sweep階段,降低了應用程序暫停的時間。
可以看出這些垃圾收集器分為3種類型:串行,并行,并發;
總結
以上是生活随笔為你收集整理的JVM调优系列:(四)GC垃圾回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM调优系列:(三)类加载和执行机制
- 下一篇: 并发工具类(一)等待多线程完成的Coun