垃圾回收器

垃圾回收器可以分爲三類:串行的垃圾回收器、吞吐量優先的垃圾回收器和響應時間優先的垃圾回收器。

串行的垃圾回收器,底層是一個單線程的垃圾回收器,也就是說在進行垃圾回收時,其他的線程會暫停。使用場景是堆內存較小的時候,適合個人電腦(CPU個數少)。而吞吐量優先的垃圾回收器和響應時間優先的垃圾回收器,則是一個多線程的垃圾回收器,適合堆內存較大,需要多核CPU來支持。顯然多核CPU都是服務器,即多用在服務器上。

響應時間優先注重的是垃圾回收時STW的單次時間儘可能短,它要考慮的是儘可能讓暫停的時間變短。而吞吐量優先的目標是單位時間內STW的時間最短。

串行

開啓串行垃圾回收器的語句是:-XX:+UseSerialGC=Serial+SerialOld。

串行垃圾回收器很明顯分爲兩個部分:Serial和SerialOld。其中Serial是運行在新生代中,採用的算法是複製算法;而SerialOld是工作在老年代,採用的算法是標記加整理,不會產生碎片但是效率比較低。新生代和老年代的垃圾回收器是分開運行的。

假設現在有多核CPU,如上圖所示,總共有四個CPU,剛開始的時候這些線程都在運行,這時候發現堆內存不夠了,隨即觸發了一次垃圾回收。觸發垃圾回收首先需要要這些線程在一個安全點停下來,因爲在垃圾回收的過程中,對象的地址要發生一個改變,爲了保證安全的使用這些對象地址,需要所有用戶工作的線程到達這個安全點暫停下來,然後就可以完成垃圾回收的工作,就不會有其他線程來干擾。因爲Serial和SerialOld都是單線程的垃圾回收器,因此只有一個垃圾回收線程在運行,在這個垃圾回收線程運行時,其他線程都要處於阻塞狀態,直到垃圾回收線程的結束纔可恢復運行。

吞吐量優先

要使用吞吐量優先的垃圾回收器的命令:-XX:+UseParallelGC(工作於新生代,採用的複製算法)~-XX:+UseParallelOldGc(工作於老年代,採用標記加整理的算法)。不過在JDK1.8中是默認開啓的。只要開啓其中的一個,會自動開啓另外一個

吞吐量優先採用的算法和串行垃圾回收器採用的算法都是一樣的,都不會產生碎片,只是吞吐量的是多線程的。

它的工作模式是:多核CPU下,多個線程都在運行,這個時候內存不足,觸發了一次垃圾回收,用戶線程都會在一個安全點停下來,之後垃圾回收器會開啓多個線程來進行垃圾回收。默認情況下,垃圾回收的線程數是和CPU的核數相關的,只要你的CPU核數是小於一個值的時候,就跟CPU的核數是一模一樣的,比如現在四個CPU,那麼就有四個線程進行垃圾回收,當垃圾回收之後就恢復其他線程的工作。在進行垃圾回收的時候CPU的使用是非常高的。

當然垃圾回收線程數可以通過指令來設置:-XX:ParallelGCThreads=n,後面跟上一個線程數。與之相關的還有三個參數:

-XX:+UseAdaptiveSizePolicy——採用自適應的大小調整策略,主要是調整新生代的大小,之前瞭解過新生代分爲伊甸園和兩個倖存區,如果把這個開關打開,在ParllelGC時會動態的去調整伊甸園和倖存區的一個比例,包括整個堆的一個大小。

-XX:GCTimeRatio=ration——主要是調整吞吐量的目標,調整垃圾回收時間和總時間的佔比。在這裏有一個公式:1/(1+ration),ration的默認值是99,即垃圾回收的時間不能超過整個時間的百分之一,如果不能達到這個目標,ParallelGC就會嘗試調整堆的大小從而來達到這個目標,一般是把堆增大(因爲增大堆空間就讓垃圾回收變得不頻繁),一般設置爲19

-XX:MaxGCPauseMillis=ms——最大暫停毫秒數,默認值爲200ms和上面一個命令是衝突的。要跟實際情況去一個合理的值。

響應時間優先

開啓響應時間垃圾回收使用命令:-XX:+UseConcMarkSweepGc(基於標記清除算法,並且爲併發的,工作在老年代的);與之相配對的是-XX:+UseParNewGC(工作在新生代,採用基於複製算法的垃圾回收),兩個一起運行。當併發失敗時,這時候會讓老年代的垃圾回收器從CMS垃圾回收器回退到SerialOld單線程垃圾回收器

所謂的併發是指垃圾回收器在工作的同時,其他的用戶線程也能同時進行,也就是用戶線程和垃圾回收線程是併發執行,會同時搶佔CPU,而吞吐量優先中的並行是指多個垃圾回收器它們並行運行,在這期間不允許用戶線程工作運行。併發進一步減少了STW的時間。

工作流程:多個CPU並行執行,老年代發生內存不足,這些線程會在安全點暫停下來,此時CMS垃圾回收器會執行一個初始標記的操作,在初始標記的過程中仍然需要STW,也就是其他線程會暫停下來,初始標記的時間是非常短的,因爲它只會標記堆內存中的根對象;當初始標記完成時,接下來用戶線程就可以恢復運行,與此同時,垃圾回收線程可以併發標記把那些剩餘的垃圾找出來,在這個過程中不用暫停其他線程時間;等到併發標記以後,還要做一步重新標記,這一步又要STW,因爲在併發標記的同時,用戶線程也在同時工作就有可能產生新的對象,改變現有對象的引用,對垃圾回收做一些干擾,因此需要重新標記。等重新標記完了以後,用戶線程就可以恢復運行,垃圾回收線程併發執行清理操作。

在初始標記時併發線程數會受到-XX:ParallelGCThreads=n和-XX:ConcGCThreads=n兩個的影響。前者是並行線程數,後者併發線程數,一般後者設置爲CPU核數的四分之一。CMS垃圾回收器雖然做到了響應時間優先,但是由於它佔用了一定的CPU使用量,因此對於整個應用的吞吐量是有所影響的。

-XX:CMSInitiatingOccupancyFraction=precent用於設置執行CMS垃圾回收的內存佔比,用於預留一部分空間給那些浮動垃圾來用,所謂的浮動垃圾是指在併發清理時,用戶線程可能會產生新的對象以及新的垃圾,而這些垃圾只有在下一次垃圾回收時才能被清理。在早期的JVM裏面這個默認值爲65%。

-XX:+CMSScavengeBeforeRemark——在重新標記的環節,有一個特殊的場景,有可能新生代的對象會引用老年代的對象,這時我在重新標記時必須掃描整個堆,通過新生代引用掃描一遍老年代的對象,做一次可達性分析,這樣的話對我的性能影響會非常大,因爲在找老年代的過程中會掃描新生代中的一些垃圾,即做了一些重複工作,此時就可以用這個參數,在重新標記之前先對新生代做一次垃圾回收,這樣的話就可以減輕重新標記的壓力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章