本文整理自周志明老師的《深入理解Java虛擬機-JVM高級特性與最佳實踐》第3版的第二章和第三章簇抵。
加上了一些網(wǎng)上拼拼湊湊的圖片,個人認為很多博客復制來復制去射众,最后的東西都看不懂碟摆,所以從書里碼了一下知識點,也用作自己記憶叨橱。
一典蜕、一個命令
上面的結(jié)果顯示了 jvm 的模式:
Client VM(-client),為在客戶端環(huán)境中減少啟動時間而優(yōu)化罗洗;
Server VM(-server)愉舔,為在服務(wù)器環(huán)境中最大化程序執(zhí)行速度而設(shè)計。
在文件路徑:jdk-11.0.7+10\lib 下面可以更改 jvm.cfg 文件來決定是采用哪個模式伙菜,具體操作就是更改文件里面 Client 和 Server 這兩行的位置轩缤,誰在上就是選擇誰。
二、JVM 的內(nèi)存區(qū)域與內(nèi)存溢出異常
如上圖所示,是 Java 虛擬機規(guī)范規(guī)定的,jvm 管理的內(nèi)存區(qū)域涉馁。
- 灰色部分币励,即方法區(qū)和堆這兩個數(shù)據(jù)區(qū),是所有線程共享的數(shù)據(jù)區(qū)。
- 而白色部分,包括程序計數(shù)器、java虛擬機棧勃救、本地方法棧,叫線程隔離的數(shù)據(jù)區(qū)脱茉,或者叫線程私有的內(nèi)存剪芥。這三塊內(nèi)存區(qū)域隨線程生,隨線程死琴许。
每個部分的詳細介紹如下:
2.1 pc 寄存器( Program Counter)
也可叫程序計數(shù)器税肪。是一塊較小的內(nèi)存空間,可以看作是當前線程執(zhí)行的字節(jié)碼的行號指示器榜田。
在虛擬機的概念模型(注意只是概念)里益兄,字節(jié)碼解釋器工作的時候就是通過改變這個計數(shù)器的值來選取嚇一跳需要執(zhí)行的字節(jié)碼指令,顯然箭券,分支循環(huán)等基礎(chǔ)功能都要靠這個計數(shù)器净捅。
由于多線程實際上是線程輪流切換實現(xiàn)的,所以線程切換后為了能恢復到正確的執(zhí)行位置辩块,每個線程都要有一個獨立的程序計數(shù)器蛔六。如果線程執(zhí)行的是一個 java 方法,計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址废亭;如果正在執(zhí)行的是本地方法国章,計數(shù)器的值則為空(Undefined)。
此內(nèi)存區(qū)域是唯一個在java虛擬機規(guī)范里沒有規(guī)定任何 OutOfMemoryError情況的區(qū)域豆村。
2.2 java 虛擬機棧
棧是方法執(zhí)行的線程內(nèi)存模型液兽。每個方法執(zhí)行的時候,jvm都會同步創(chuàng)建一個棧幀用于存儲局部變量表掌动、操作數(shù)棧等到四啰,方法被調(diào)用直到執(zhí)行完畢,就是對應(yīng)一個棧幀在虛擬機棧里從入棧到出棧的過程粗恢。
大多情況棧主要指的是虛擬機棧里局部變量表的部分(實際上的劃分要更復雜)柑晒。局部變量表存放了各種基本java數(shù)據(jù)類型、對象引用和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)眷射。這些數(shù)據(jù)類型在局部變量表中以局部變量槽(Slot)來表示匙赞,其中64位長的long和double類型占用兩個槽恋追,其他的占一個。在編譯期間罚屋,局部變量表的空間就會分配完成,方法運行期間不會改變局部變量表的大小嗅绸。
java虛擬機規(guī)范對這個內(nèi)存區(qū)域規(guī)定了兩種異常:如果線程請求的棧深度大于虛擬機允許的深度脾猛,會拋出StackOverflowError;如果Java棧容量可以動態(tài)擴展鱼鸠,當擴展的時候無法申請到足夠的內(nèi)存會拋出OutOfMemoryError猛拴。
2.3 本地方法棧
本地方法棧和 java 虛擬機棧類似,區(qū)別只是虛擬機棧為虛擬機執(zhí)行 Java 方法蚀狰,本地方法棧是為虛擬機使用到的本地方法服務(wù)愉昆。
因此本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出拋出 StackOverflowError 和 OutOfMemoryError 異常。
2.4 java 堆
java 堆在虛擬機啟動的時候建立麻蹋,它是 java 程序最主要的內(nèi)存工作區(qū)域跛溉。
java堆的唯一目的就是存放對象實例。在JVM所管理的內(nèi)存中扮授,堆區(qū)是最大的一塊芳室,堆區(qū)也是Java GC機制所管理的內(nèi)存區(qū)域。需要注意刹勃,java堆只是邏輯上的連續(xù)區(qū)域堪侯,物理上可以不連續(xù)。
提到垃圾回收的時候總會說堆的區(qū)域劃分荔仁,但是實際上java虛擬機規(guī)范沒有規(guī)定伍宦,所謂的劃分是各種虛擬機實現(xiàn)的風格決定的。這部分后面垃圾回收的時候還會講乏梁。
java堆可以固定大小次洼,也可以實現(xiàn)成可擴展,當前主流的虛擬機都是按照可擴展來實現(xiàn)掌呜,基于 -Xmx和-Xms參數(shù)來設(shè)定滓玖。
異常:如果堆內(nèi)存不夠,并且堆也無法擴展质蕉,拋出OutOfMemoryError势篡。
2.5 方法區(qū)
用來存儲已經(jīng)被虛擬機加載的類型信息、常量模暗、靜態(tài)變量禁悠、即時編譯器編譯后的代碼緩存等數(shù)據(jù)。
在java虛擬機里把他描述為堆的一個邏輯部分兑宇,但是又要和堆區(qū)分開碍侦,還有一個別名叫“非堆”。類加載子系統(tǒng)負責從文件系統(tǒng)或者網(wǎng)絡(luò)中加載 Class 信息( ClassLoader 就是這個區(qū)域下的組件),加載的類信息就存放于方法區(qū)瓷产。(可以看到站玄,這里保存的東西都是唯一份的東西)
關(guān)于垃圾回收的永久代,一般都是指的方法區(qū)濒旦,原因是當時的hotspot虛擬機設(shè)計團隊把垃圾收集器的分代設(shè)計擴展到了這里株旷,或者說使用永久代實現(xiàn)了方法區(qū),后來因為這種方法更容易內(nèi)存溢出尔邓,永久代的設(shè)計已經(jīng)被取消:到j(luò)dk8完全放棄永久代晾剖,使用本地內(nèi)存的中元空間來替代這部分的功能。
異常:無法滿足新的內(nèi)存分配需求梯嗽,拋出OutOfMemoryError齿尽。
- 運行時常量池:是方法區(qū)的一部分,用來存放編譯器生成的各種字面量和符號引用灯节,在類加載后這些內(nèi)容都會進入方法區(qū)的常量池循头。
既然是方法區(qū)的一部分,顯然是受到方法區(qū)內(nèi)存的限制炎疆,如果常量池無法再申請到內(nèi)存贷岸,會拋出拋出OutOfMemoryError。
2.6 直接內(nèi)存
直接內(nèi)存指的就已經(jīng)不屬于虛擬機運行時數(shù)據(jù)區(qū)域的部分了磷雇,java虛擬機規(guī)范也沒有定義這塊內(nèi)存偿警。
java在jdk1.4 后,引入了 **NIO **類唯笙,允許 java 程序通過native函數(shù)庫直接分配堆外的內(nèi)存螟蒸,然后通過java堆里的 DirectByteBuffer對象作為對這一塊內(nèi)存的引用進行操作,在某些場景中能夠提高性能崩掘,因為避免了 java 堆和 native 堆的數(shù)據(jù)來回復制七嫌。
異常:頻繁使用也可能導致拋出OutOfMemoryError。畢竟雖然沒有收到j(luò)ava堆的限制苞慢,可是還是會受到本機的內(nèi)存诵原、以及處理器尋址空間的限制
三、垃圾回收算法
3.1 概述
上面的內(nèi)存區(qū)域里挽放,線程獨有的三個區(qū)域绍赛,并不需要過多考慮回收問題,因為分配和回收比較確定辑畦。
Java堆和方法區(qū)這兩個區(qū)域則有著很顯著的不確定性:一個接口的多個實現(xiàn)類需要的內(nèi)存可能會不一樣吗蚌,一個方法所執(zhí)行的不同條件分支所需要的內(nèi)存也可能不一樣,只有處于運行期間纯出,我們才能知道程序究竟會創(chuàng)建哪些對象蚯妇,創(chuàng)建多少個對象敷燎,這部分內(nèi)存的分配和回收是動態(tài)的。
對于方法區(qū)箩言,永久代的遺留問題關(guān)注比較多硬贯,最主要的垃圾回收算法還都是關(guān)注堆內(nèi)存。
垃圾收集器所關(guān)注的正是這部分內(nèi)存該如何管理陨收,我們討論的相關(guān)算法也是針對這部分內(nèi)存澄成。
從如何判定對象消亡的角度處罰,垃圾收集算法可以分為“引用計數(shù)式”(Reference Counting GC)和 “ 追蹤式”(Tracing GC)兩類畏吓。主流的 java 虛擬機都采用的第二種。所以下面講的算法都是這種模式下面的卫漫。
3.2 判斷對象是否需要回收
垃圾回收第一件事要做的就是菲饼,確定哪些對象死了,哪些活著列赎,死了的才要進行回收宏悦。對于判斷,一般有兩種算法包吝。
- 引用計數(shù)法(Reference Counting)
給對象添加一引用計數(shù)器饼煞,被引用一次計數(shù)器值就加 1;當引用失效時诗越,計數(shù)器值就減 1砖瞧;計數(shù)器為 0 時,對象就是不可能再被使用的嚷狞,簡單高效块促。
存在問題:無法解決對象之間相互循環(huán)引用的問題,要想采用這個算法床未,還需要很多的額外處理竭翠。
- 可達性分析算法
通過一系列的稱為 "GC Roots" 的對象作為起始節(jié)點集合,從這些節(jié)點開始薇搁,根據(jù)引用關(guān)系向下搜索斋扰,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到 GC Roots 沒有任何引用鏈相連時啃洋,則證明此對象是不可能再被使用的传货。
可達性分析算法是當前主流商用程序語言的內(nèi)存管理子系統(tǒng)采用的算法。
- 哪些對象可以作為 GC Roots 呢宏娄?
java 技術(shù)體系里损离,固定可作為 GC Roots 的對象包括以下幾種:
- 在虛擬機棧中引用的對象。比如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)绝编、局部變量僻澎、臨時變量等貌踏;
- 方法區(qū)中類靜態(tài)屬性引用的對象,比如java類的引用類型靜態(tài)變量窟勃;
- 方法區(qū)中常量引用的對象祖乳,比如字符串常量池里的引用;
- 本地方法棧JNI(也就是通常說的本地方法)中引用的對象秉氧;
- java虛擬機內(nèi)部的引用眷昆,比如基本數(shù)據(jù)類型對應(yīng)的 Class 對象,一些常駐的異常對象汁咏,和系統(tǒng)類加載器亚斋;
- 所有被同步鎖(synchronized關(guān)鍵字)持有的對象;
- 反應(yīng) java 虛擬機內(nèi)部情況的 JMXBean攘滩、JVMTI 中注冊的回調(diào)帅刊、本地代碼緩存等。
除了這些漂问,還會有一些臨時加入的對象赖瞒,共同構(gòu)成 GC Roots 集合。
- 方法區(qū)的垃圾回收
前面已經(jīng)說過蚤假,主要的收集區(qū)域是堆栏饮,而且方法區(qū)垃圾收集的性價比也比較低。比如在 hotspot 虛擬機采用了元空間來實現(xiàn)永久代磷仰,在這個區(qū)域沒有垃圾收集行為袍嬉。
如果要回收,方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類型灶平。
回收廢棄常量與回收Java堆中的對象非常類似冬竟。都是基于判斷是否還有對象引用指向這個常量。常量池中其他類(接口)民逼、方法泵殴、字段的符號引用也與此類似。
而判斷類型的回收要滿足三個條件:
- 該類的所有實例已經(jīng)被回收拼苍,也就是堆中不再存在該類以及任何派生子類的實例笑诅;
- 加載該類的類加載器已經(jīng)被回收(這個條件很難達成);
- 該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用疮鲫,無法在任何地方通過反射訪問該類的方法吆你。
3.3 分代收集理論
網(wǎng)上很多都說是分代收集算法,但是顯然這并不是具體的算法俊犯,更像一種策略妇多,選擇組合各種具體的算法,周志明老師的書上寫的是分代收集理論燕侠。
分代就是結(jié)合堆的區(qū)域劃分者祖,然后講回收對象根據(jù)年齡不同放到不同區(qū)域立莉,這樣的基礎(chǔ)上可以對某一區(qū)域單獨進行垃圾回收,當然七问,分代收集策略蜓耻、對應(yīng)的內(nèi)存分代,都有消亡的趨勢械巡。
分代至少會分為新生代和老年代兩個區(qū)域刹淌,新生代垃圾收集結(jié)束后,還存活的對象會逐步晉升到老年代存放讥耗,具體結(jié)合這一節(jié)的收集算法有勾,區(qū)域的劃分下一節(jié)會講解。
在內(nèi)存分出不同的區(qū)域后古程,對不同區(qū)域的回收也起了不同的名字:
- Minor GC / Young GC(新生代收集):目標只是新生代區(qū)域的收集蔼卡;
- Major GC / Old GC(老年代收集):目標只是老年代的垃圾收集;
- Mix GC(混合收集):目標是收集整個新生代+部分老年代的垃圾收集籍琳。目前只有 G1 收集器有這種行為。
以上三個都叫 Partial GC贷祈,也就是部分收集趋急。還有一種收集:
- Full GC(整堆收集):收集整個 java 堆和方法區(qū)的垃圾收集。
3.4 具體的垃圾回收算法
之前看網(wǎng)上有的說法講最早势誊、基本的垃圾回收算法是
引用計數(shù)(Reference Counting):有一個引用就加一個技術(shù)呜达,少一個引用就減一個計數(shù),垃圾回收的時候就收集計數(shù)為 0 的粟耻。
但是現(xiàn)在我明白了查近,這玩意確實劃分的有點亂,引用計數(shù)正如 3.2 講到的挤忙,應(yīng)該算到 如何判斷垃圾是否需要回收的算法里霜威,不應(yīng)該算在垃圾回收算法里。
所以仍然按照深入理解java虛擬機書里講的册烈,分為三個算法戈泼。
3.4.1 標記-清除算法(Mark-Sweep)
掃描GC Roots集合:
- 第一階段,從引用根節(jié)點赏僧,開始標記所有被引用的對象大猛;
- 第二階段,遍歷整個堆淀零,把未標記的對象清除挽绩。
- 也可以反過來,標記未被引用的對象驾中,然后清除未被標記的唉堪。
缺點:
- 效率很不穩(wěn)定模聋,如果堆包含大量對象,而大部分都要被收集巨坊,那么這個操作過程執(zhí)行效率一直降低撬槽;
- 內(nèi)存空間碎片化,如上圖可以看的很明顯趾撵,垃圾收集執(zhí)行完后空間碎片過多侄柔,可能會導致以后程序運行的時候需要分配大對象的時候找不到連續(xù)內(nèi)存從而又提前觸發(fā)垃圾收集。
3.4.2 標記-復制算法(簡稱復制算法)
把內(nèi)存劃分為兩個相等的區(qū)域占调,每次只用一個區(qū)域暂题,一個內(nèi)存用完就開始執(zhí)行算法:
- 把這個區(qū)域仍然存活的對象復制到另一個區(qū)域(這一步顯然還是要先標記的);
- 然后把這個區(qū)域一次清理(省掉了上一種方法的第二次遍歷)究珊。
優(yōu)點:
簡單薪者、高效,而且解決了產(chǎn)生空間碎片的問題剿涮。
缺點:
需要 2 倍內(nèi)存言津,總是有一半空的,可用的也只有一半取试,太浪費了悬槽。
3.4.3 標記-整理(Mark-Compact)
結(jié)合標記清除算法的第一步,第二步并不采用直接清理瞬浓,而是讓所有對象都向內(nèi)存空間的固定一端挪動初婆,最后清理掉邊界之外的內(nèi)存。
優(yōu)點:
顯然猿棉,進行垃圾收集后不會產(chǎn)生碎片磅叛。
缺點:
“整理”的過程,或者說移動萨赁,如果是在老年代弊琴,每次都沉積著大量對象,移動的過程顯然會是一個很負重的操作杖爽,必須全程暫停用戶應(yīng)用程序访雪。這種停頓還被設(shè)計者描述為 Stop the world。
權(quán)衡::
- 如果移動掂林,那么缺點已經(jīng)說過了臣缀;
- 如果不移動,那么要通過更復雜的策略解決內(nèi)存碎片問題泻帮,而內(nèi)存的訪問本身又是用戶程序最頻繁的操作精置,額外的負擔會影響應(yīng)用程序的吞吐量。
也就是說锣杂,如果移動脂倦,內(nèi)存回收會更復雜番宁,如果不移動,內(nèi)存分配會更復雜赖阻。從整個程序的吞吐量來看蝶押,移動會更劃算。
注意:因為有標記的過程火欧,通常都是需要停頓用戶線程來進行的棋电,只是總體來說,最后一種有整理的過程苇侵,前兩種的停頓時間就會短一些赶盔。
四、JVM堆內(nèi)存分代策略
需要再次強調(diào)的是:
從回收內(nèi)存的角度看榆浓,由于現(xiàn)代垃圾收集器大部分都是基于上一節(jié)所說的于未,分代收集理論設(shè)計的,區(qū)域劃分僅僅是一部分垃圾收集器的共同特性或者說設(shè)計風格而已,而非某個Java虛擬機具體實現(xiàn)的固有內(nèi)存布局,更不是《Java虛擬機規(guī)范》里對Java堆的進一步細致劃分陡鹃。
尤其到 G1 收集器的出現(xiàn)后烘浦,已經(jīng)打破了固有的策略,往后萍鲸,垃圾收集器技術(shù)的更新也會帶來更多的策略闷叉,而不是分代。
因此我們從分水嶺的前后來分別介紹猿推。
內(nèi)存分代策略:也就是根據(jù)對象存活的周期不同片习,將堆內(nèi)存劃分為幾塊捌肴,一般分為新生代蹬叭、老年代、永久代状知。
4.1 為什么要分代秽五?
很好理解,因為各種對象示例需要回收的頻率是不一樣的饥悴,分區(qū)操作能縮小操作的范圍坦喘,結(jié)合上一節(jié)的垃圾回收策略,更好理解西设。
- 如果沒有區(qū)域劃分瓣铣,頻繁進行垃圾收集的時候,遍歷范圍都是所有的對象贷揽,會嚴重影響的 GC 效率棠笑。
- 有了內(nèi)存分代,根據(jù)不同區(qū)域禽绪,采用不同的垃圾收集算法蓖救。
4.2 內(nèi)存劃分具體策略
新生代洪规、老年代、永久代(如上一節(jié)所介紹的循捺,永久代后來已經(jīng)被取締)斩例。
4.2.1 新生代(Young)
新生代又分為了三塊區(qū)域,他們的空間比例默認為 8:1:1从橘。
- Eden(伊甸園念赶,人類創(chuàng)建的地方),就是所有對象產(chǎn)生的地方洋满;
- From晶乔,屬于第一塊 Survivor 區(qū)域;
- To牺勾,屬于第二塊 Survivor 區(qū)域正罢。
這么個比例劃分是因為新生代的垃圾回收算法是標記-復制算法,設(shè)置這個比例是為了充分利用內(nèi)存空間驻民。
新生對象在 Eden 區(qū)分配翻具,除了大對象,大對象直接進入老年代回还。
大對象就是指需要大量連續(xù)內(nèi)存的對象裆泳,就是很大的數(shù)組,或者很長的字符串柠硕。比大對象更糟糕的就是遇到一個朝生夕滅的大對象工禾。
結(jié)合一般在這個區(qū)域采用標記-復制算法,看一看新生代的垃圾收集過程:
- 如果 Eden 區(qū)不夠了蝗柔,就會開始一次 Minor GC闻葵,將 Eden 里存活的復制到 From(Eden空了);
- 下次 Eden 區(qū)滿了癣丧,再執(zhí)行一次 Minor GC槽畔,將存活的對象復制到 To 中 (Eden空了),同時胁编,將 From 中消亡的對象清理掉厢钧,將存活的對象也復制到 To 區(qū),然后清空 From 區(qū)(此時 From空)嬉橙;
在 From 和 To 兩個區(qū)域的這種切換早直,顯然就是標記復制的算法,他們兩個的空間也確實是 1 : 1市框。此后從 Eden 區(qū)滿了后再往他們兩個區(qū)域移動的時候就是交替進行霞扬。
注意事項:
- 當兩個存活區(qū)切換了幾次(HotSpot虛擬機默認15次)之后,仍然存活的對象,將被復制到老年代祥得。實現(xiàn)方式兔沃,就是在不斷的 Minor GC ,這個復制的過程會給對象計算年齡级及,年齡計數(shù)器是存儲在對象頭里的(關(guān)于虛擬機的對象頭信息)乒疏。
- 除了年齡判斷,hotspot 虛擬機還有動態(tài)對象年齡判定的策略饮焦,如果 survivor 空間相同年齡所有對象大小總和 >= Survivor 空間的一半怕吴,這部分對象都直接進入老年代。
所以可以總結(jié)出有 3 類對象都會進入老年代:1.大對象直接進县踢;2.在Minor GC 存活15歲后進转绷;3.相同年齡對象成為眾數(shù),一起進硼啤。
4.2.2 老年代(Old)
這里的對象GC 頻率低议经。
4.2.3 永久代(Permanent)
正如前面所說,jdk8以前谴返,很多人愿意把方法區(qū)稱為永久代煞肾,本質(zhì)上是因為當時的hotspot虛擬機選擇把垃圾收集的設(shè)計擴展到了方法區(qū),或者說使用永久代實現(xiàn)方法區(qū)嗓袱,使得垃圾收集器能夠管理這部分內(nèi)存籍救,其他虛擬機不存在這個概念。
到j(luò)dk8就完全放棄了渠抹,因為實現(xiàn)方法區(qū)的內(nèi)容已經(jīng)改為用本地內(nèi)存的元空間蝙昙。
這里其實我有一個疑問,邏輯上本來方法區(qū)是屬于堆的一塊特殊區(qū)域梧却,現(xiàn)在改用本地直接內(nèi)存來實現(xiàn)奇颠,那么在內(nèi)存區(qū)域的劃分上,是應(yīng)該定義為直接內(nèi)存的一塊特殊區(qū)域篮幢?
反正說 jvm 的內(nèi)存區(qū)域的時候迷迷糊糊的大刊。
五为迈、垃圾回收器
這里指的都是“經(jīng)典”垃圾回收器三椿,是因為目前的新技術(shù)實現(xiàn)的高性能低延遲收集器還處于實驗狀態(tài)。
所以記錄一下時間:現(xiàn)在是2020.09.04葫辐,參考的書是基于 jdk11 的搜锰。
5.1 Serial 收集器(復制算法)
是新生代單線程收集器,標記和清理都是單線程耿战,需要其它工作線程暫停蛋叼,優(yōu)點是簡單高效。
這也是虛擬機在Client模式下運行的默認值,可以通過 -XX:+UseSerialGC 來強制指定狈涮。
5.2 Serial Old 收集器(標記-整理算法)
老年代單線程收集器狐胎,Serial收集器的老年代版本,需要其它工作線程暫停歌馍,簡單高效握巢。
5.3 ParNew 收集器(復制算法)
新生代收集器,實質(zhì)上是 Serial 收集器的多線程版本松却,各種策略都和 Serial 收集器一樣暴浦。除了支持多線程并行,沒有別的優(yōu)點晓锻,但是在 jdk7 之前歌焦,都會用他,原因和性能無關(guān)砚哆,原因是:只有他能和 CMS 配合工作独撇。(之后有 G1 了,他就沒這么高地位了)
5.4 Parallel Scavenge 收集器(復制算法)
新生代收集器躁锁,并行券勺,表面上看起來的特性和 ParNew 一樣。
但是他的特點是灿里,關(guān)注點不在縮短線程停頓時間关炼,而關(guān)注如何達到一個可控制的吞吐量,什么是吞吐量匣吊?
Parallel Scavenge+Serial Old 收集器組合回收垃圾(這也是在Server模式下的默認值)可用 -XX:+UseParallelGC 來強制指定儒拂,用 -XX:ParallelGCThreads=4 來指定線程數(shù)。
5.5 Parallel Old 收集器(標記-整理算法)
Parallel Scavenge 收集器的老年代版本色鸳,并行收集器社痛。
Parallel Scavenge 和 Parallel Old 搭配,產(chǎn)生了一種“吞吐量”優(yōu)先的收集器方案命雀。
5.6 CMS(Concurrent Mark Sweep)收集器(標記-清除算法)
老年代收集器蒜哀。從名字就可以看出來,是并發(fā)+標記清除吏砂。一些官方公開文檔里害稱之為Concurrent Low Pause Collector撵儿,并發(fā)低停頓收集器。
他的收集過程比較復雜狐血,分為四步:
- 初始標記淀歇;(需要停頓用戶線程,標記GC roots能直接關(guān)聯(lián)到的對象匈织,快)
- 并發(fā)標記浪默;(從上一步關(guān)聯(lián)到的對象遍歷整個圖牡直,但是是并發(fā)運行的,不用停頓用戶線程纳决,慢)
- 重新標記碰逸;(修正上一個階段可能因為用戶繼續(xù)操作又產(chǎn)生變動的部分,需要停頓用戶線程阔加,快)
- 并發(fā)清除花竞。(并發(fā)執(zhí)行,因為不需要整理移動存活對象)
最大的優(yōu)點就是名字體現(xiàn)出來的:并發(fā)收集掸哑、低停頓约急。
缺點:
- 對處理器資源非常敏感,原因就是苗分,雖然你是并發(fā)的厌蔽,但是你本身相當于其他的線程,這是境地總吞吐量的(空間換時間嘛)摔癣;
- 無法處理浮動垃圾奴饮。浮動垃圾就是說,他的四個步驟里择浊,并發(fā)的兩個步驟戴卜,用戶線程都是在同時產(chǎn)生垃圾的,只能等到下一次才能處理琢岩。所以垃圾收集還需要有額外預(yù)留的空間投剥,否則還會產(chǎn)生問題;
- 因為是標記清除算法担孔,所以有空間碎片以及后續(xù)會產(chǎn)生的問題江锨。
5.7 G1/Garbage First 收集器
這是一個里程碑式的成果。實驗期完成后糕篇,正式商用啄育,到j(luò)dk8后,官方稱之為全功能垃圾收集器(Fullly-Featured Garbage Collector)拌消。
jdk 9 后挑豌,G1 也替代了Parallel Scavenge 和 Parallel Old 搭配的組合,稱為服務(wù)端模式下的默認收集器墩崩,CMS 直接淪落到了不推薦使用氓英。
之前垃圾收集的目標都是基于分代的內(nèi)存,要么在新生代工作泰鸡、要么老年代债蓝、要么整個 java 堆壳鹤。G1 則跳出了這個牢籠盛龄,可以面向堆內(nèi)存的任何部分來組成回收集(Collection Set,簡稱 CSet),衡量標準不再是哪個分代余舶,而是哪塊垃圾最多我去哪快啊鸭,這就是 G1 收集器的 Mixed GC 模式。
G1 把堆內(nèi)存分為了不同的 Region 匿值,這些 Region 大小相等赠制,各自獨立。這個劃分不像以前遵循的那種固定比例挟憔,這樣钟些,每個 Region 都可能扮演以前的新生代的 Eden 空間、Survivor空間或者老年代空間绊谭,然后垃圾收集器采用不同的策略去收集政恍。
缺點:比CMS有更高的內(nèi)存占用炒瘸,更高的額外執(zhí)行負載定踱。