Version:1.0 StartHTML:000000214 EndHTML:000030235 StartFragment:000001908 EndFragment:000030149 StartSelection:000001908 EndSelection:000030145 SourceURL:https://www.cnblogs.com/butterfly100/p/9175673.html <title>JVM的內(nèi)存管理機制 - butterfly100 - 博客園</title><link href="/bundles/blog-common.css?v=D7Le-lOZiZVAXQkZQuNwdTWqjabXaVBE_2YAWzY_YZs1" rel="stylesheet" type="text/css"><link id="MainCss" href="/skins/red_autumnal_leaves/bundle-red_autumnal_leaves.css?v=EDEp2d1uMe8iyN6qDoW8MQgYb-JCFIeiYP0oX3XiiRM1" rel="stylesheet" type="text/css"><link href="/blog/customcss/394698.css?v=tKalJHJDrWkZyO9Da530%2fWCycmM%3d" rel="stylesheet" type="text/css"><link id="mobile-style" href="/skins/red_autumnal_leaves/bundle-red_autumnal_leaves-mobile.css?v=d9LctKHRIQp9rreugMcQ1-UJuq_j1fo0GZXTXj8Bqrk1" rel="stylesheet" type="text/css" media="only screen and (max-width: 767px)"><link title="RSS" rel="alternate" type="application/rss+xml"><link title="RSD" rel="EditURI" type="application/rsd+xml"><link rel="wlwmanifest" type="application/wlwmanifest+xml"> <script type="text/javascript">var currentBlogApp = 'butterfly100', cb_enable_mathjax=false;var isLogined=true;</script>
一、JVM的內(nèi)存區(qū)域
對于C、C++程序員來說,在內(nèi)存管理領域,他們既擁有每一個對象的“所有權(quán)”,又擔負著每一個對象生命開始到終結(jié)的維護責任。
對Java程序員來說,在虛擬機的自動內(nèi)存管理機制的幫助下,不再需要為每個new操作去寫匹對的 delete/free 代碼,不容易出現(xiàn)內(nèi)存泄露和內(nèi)存溢出的問題。
1、內(nèi)存區(qū)域
根據(jù)《Java虛擬機規(guī)范(Java SE 7版)》規(guī)定,Java虛擬機所管理的內(nèi)存將包括以下幾個運行時數(shù)據(jù)區(qū)域,如圖:

線程私有的內(nèi)存區(qū)域:
- 程序計數(shù)器:可看做當前線程執(zhí)行字節(jié)碼的行號指示器,字節(jié)碼解釋器工作時通過改變計數(shù)器的值來選擇下一條所需執(zhí)行的字節(jié)碼指令
-
虛擬機棧:Java方法執(zhí)行的棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法從調(diào)用至執(zhí)行完成的過程,都對應一個棧幀在虛擬機棧的入棧到出棧的過程
- 局部變量表:存放編譯期可知的基本數(shù)據(jù)類型(boolean、byte、char、int等)、對象引用(reference類型)和 returnAddress類型(指向一條字節(jié)碼指令的地址)
- 本地方法棧:Native方法執(zhí)行的棧幀
所有線程共享的內(nèi)存區(qū)域:
- 堆:存放對象實例和數(shù)組
-
方法區(qū):存儲被虛擬機加載的Class類信息、final常量、static靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
- 運行時常量池:存放編譯生成的各種字面量和符號引用,運行期間也可能將新的常量放入池中
2、對象的創(chuàng)建
在語言層面,創(chuàng)建對象(例如:clone,反序列化)通常是一個 new 關(guān)鍵字,而在虛擬機中,對象創(chuàng)建的過程是如何呢?
在虛擬機遇到 new 指令時:
1. 類加載:確保常量池中存放的是已解釋的類,且對象所屬類型已經(jīng)初始化過,如果沒有,則先執(zhí)行類加載
2. 為新生對象分配內(nèi)存:對象所需內(nèi)存大小在類加載時可以確定,將確定大小的內(nèi)存從Java堆中劃分出來
- 分配空閑內(nèi)存方法:
- 指針碰撞:假如堆是規(guī)整的,用過的內(nèi)存和空閑的內(nèi)存各一邊,中間使用指針作為分界點,分配內(nèi)存時則將指針移動對象大小的距離
- 空閑列表:假如堆是不規(guī)整的,虛擬機需要維護哪些內(nèi)存塊是可用的列表,分配時候從列表中找出足夠大的空閑內(nèi)存劃分,并更新列表記錄
- 對象創(chuàng)建在并發(fā)情況下保證線程安全:例如,正在給對象A分配內(nèi)存,指針還沒修改,對象B同時使用了原來的指針來分配內(nèi)存
- CAS配上失敗重試
- 本地線程分配緩沖TLAB(ThreadLocal Allocation Buffer):將內(nèi)存分配動作按線程劃分到不同空間中進行,即每個線程在Java堆中預先分配一小塊內(nèi)存
3. 將分配的內(nèi)存空間初始化為零值:保證對象的實例在Java代碼中可以不賦值就可直接使用,能訪問到這些字段的數(shù)據(jù)類型對應的零值(例如,int類型參數(shù)默認為0)
4. 設置對象頭:設置對象的類的元數(shù)據(jù)信息、哈希碼、GC分代年齡等
5. 執(zhí)行<init>方法初始化:將對象按照程序員的意愿初始化
3、對象的內(nèi)存布局
在HotSpot虛擬機中,對象在內(nèi)存中存儲的布局分為3個區(qū)域,如下圖所示:

-
對象頭(Header):
- MarkWord:存儲對象自身的運行時數(shù)據(jù),例如:哈希碼HashCode、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID等??紤]空間效率,MarkWord設計為非固定的數(shù)據(jù)結(jié)構(gòu),它根據(jù)對象的不同狀態(tài)復用自己的空間,如下圖所示:

- 指向Class的指針:即對象指向它的類的元數(shù)據(jù)的指針,虛擬機通過這個指針來確定是哪個類的實例
- 如果對象是Java數(shù)組,對象頭中還需要一塊記錄數(shù)組長度的數(shù)據(jù)
實例數(shù)據(jù)(Instance Data):對象真正存儲的有效信息,也是程序代碼中定義的各種類型字段的內(nèi)容
對齊填充(Padding):起占位符的作用。因為HotSpot VM的要求對象起始地址必須是8字節(jié)的整數(shù)倍,也就是對象的大小必須是8字節(jié)的整數(shù)倍。當對象實例數(shù)據(jù)部分沒有對齊時,需要對齊填充來補充
4、內(nèi)存溢出異常
除程序計數(shù)器外,JVM其他幾個運行時區(qū)域都可能發(fā)生OutOfMemoryError異常。
1. 堆內(nèi)存溢出,****OutOfMemoryError:java heap space
** 原因**:Java堆用于存儲對象實例,只要不斷創(chuàng)建對象,并保證GC Roots到對象間有可達路徑避免這些對象的GC,當對象數(shù)量達到堆的最大容量限制后就會產(chǎn)生OOM
** 解決方法**:
- 通過參數(shù) -XX:HeapDumpOnOutOfMemoryError 可以讓虛擬機在內(nèi)存溢出異常時Dump當前內(nèi)存堆轉(zhuǎn)儲快照
- 通過內(nèi)存映像分析工具(如:Eclipse Memory Analyzer)對Dump出的堆轉(zhuǎn)儲快照分析,判斷是內(nèi)存泄露還是內(nèi)存溢出
- 如果是內(nèi)存泄露:通過工具查看泄露對象的類型信息和它們到 GC Roots 的引用鏈信息,分析GC收集器無法自動回收它們的原因,定位內(nèi)存泄露的代碼位置
- 如果是內(nèi)存溢出:檢查堆參數(shù) -Xms和-Xmx,看是否可調(diào)大;代碼上檢查某些對象生命周期過長,持有時間過長的情況,嘗試減少程序運行期間內(nèi)存消耗
2. 棧內(nèi)存溢出,StackOverflowError
** 原因**:
- StackOverFlowError異常:線程請求的棧深度大于虛擬機所允許的最大深度
- OutOfMemoryError異常:虛擬機擴展棧時無法申請足夠的內(nèi)存空間
** 解決方法**:
- 檢查代碼中是否有死遞歸;配置 -Xss 增大每個線程的棧內(nèi)存容量,但會減少工作線程數(shù),需要權(quán)衡
二、垃圾回收策略
1、對象存活判斷
堆中存放著幾乎所有的對象實例,GC收集器在對堆進行回收前,首先要確定哪些對象是“存活”的,哪些是“死去”的
1. 引用計數(shù)法
給每個對象添加一個引用計數(shù)器,每當有地方引用它時,計數(shù)器 +1;引用失效時,計數(shù)器 -1。當計數(shù)器為0時對象就不再被引用。
但主流Java虛擬機沒有采用這種算法,主要原因是:它難以解決對象之間循環(huán)引用的問題
2. 可達性分析算法
通過一系列稱為“GC Roots”的對象作為起始點,從這些節(jié)點向下搜索,搜索的路徑稱為引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連(即從 GC Roots 到該對象不可達),則此對象是不可用的,會判斷為可回收對象。
Java中,可作為 GC Roots 的對象包括:
- 棧(棧幀中的本地變量表)中引用的對象
- 方法區(qū)中類 static 靜態(tài)屬性引用的對象
- 方法區(qū)中 final 常量引用的對象
- 本地方法棧中 JNI 引用的對象
2、垃圾回收區(qū)域
垃圾回收主要是回收堆內(nèi)存。在堆中,新生代常規(guī)應用進行一次GC一般可回收 70%~95% 的空間,永久代的 GC效率遠低于此
方法區(qū)進行垃圾回收的“性價比”一般比較低,主要回收兩部分內(nèi)容:廢棄常量和無用的類
- 廢棄常量回收:假如常量池的字符串,例如:“abc”,當前系統(tǒng)沒有任何一個String對象引用這個字面量,則“abc”常量會被清理出常量池。常量池中的其他類、方法、字段的符號引用與此類似
- 無用的類回收:類需要滿足下面3個條件才算是“無用的類”
- 該類的堆中所有實例都被回收
- 加載該類的 Class Loader 已被回收
- 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方反射訪問該類的方法
堆外內(nèi)存是把內(nèi)存對象分配在Java虛擬機的堆以外的內(nèi)存,包括JVM自身運行過程中分配的內(nèi)存,JNI 里分配的內(nèi)存、java.nio.DirectByteBuffer 分配的內(nèi)存等,這些內(nèi)存直接受操作系統(tǒng)管理。這樣能一定程度的減少GC對應用程序的影響。但 JVM 不直接管理這些堆外內(nèi)存,存在 OOM 的風險,可以在 JVM 啟動參數(shù)加上 -XX:MaxDirectMemorySize,對申請的堆外內(nèi)存大小進行限制
DirectByteBuffer 對象表示堆外內(nèi)存,DirectByteBuffer 對象中持有 Cleaner 對象,它唯一保存了堆外內(nèi)存的數(shù)據(jù)、開始地址、大小和容量。在創(chuàng)建完后的下一次 Full GC 時, Cleaner對象會對堆外內(nèi)存回收
3、垃圾回收算法
① 標記-清除算法

標記-清除算法分為“標記”階段和“清除”階段。標記階段是把所有活動對象都做上標記。清除階段是把那些沒有標記的對象(非活動對象)回收
它主要有兩個不足:
- 效率問題:標記和清除兩個過程的效率都不高
- 空間問題:標記清除之后會有大量不連續(xù)的內(nèi)存碎片??臻g碎片過多可能導致后續(xù)需要分配大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次 GC
****② **復制算法******

復制算法是將可用內(nèi)存劃分為大小相等的兩塊,每次只使用一塊,當一塊內(nèi)存用完了,就將存活的對象復制到另一塊上,然后將已使用的內(nèi)存空間一次清理掉。
這樣分配內(nèi)存時不用考慮內(nèi)存碎片等復雜情況,但代價是將內(nèi)存縮小為原來的一半。當對象存活率較高時,就要較多的復制操作,效率也會降低。
現(xiàn)在的商業(yè)虛擬機都采用復制算法來回收新生代。IBM專門的研究表明:新生代中對象 98% 是“朝生夕死”的,所有不需要 1:1 來劃分空間,HotSpot虛擬機是將內(nèi)存分為1塊大的 Eden 和 2塊小的 Survivor 空間,大小比例為 8:1:1。每次使用 Eden 和 其中一塊 Survivor。當回收時,將 Eden 和 一塊 Survivor 中的存活對象復制到另一塊 Survivor 上,最后清理掉剛才的 Eden 和 Survivor。新生代每次可利用的整個新生態(tài)內(nèi)存的 90%,10% 會被浪費掉。但當每次回收多余 10% 對象存活時,即剩下一個 Survivor 空間不夠時,需要老年代內(nèi)存擔保,這些對象將直接進入老年代中。
**③ ****標記-整理算法******

標記-整理算法在“標記”階段和標記-清除一樣,但后續(xù)是讓所有存活對象都向一端移動,然后清理掉端邊界外的內(nèi)存
****④ 分代算法****
根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊看,一般把堆分為“年輕代”和“老年代”,根據(jù)各個年代的特點采用適當?shù)氖占惴ā?/p>
新生態(tài)中,每次 GC 只有少量的對象存活,就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集
老年代中,對象存活率高、沒有額外的擔??臻g,就必須使用“標記-清除”或“標記-整理”算法
4、垃圾回收器比較
垃圾回收算法性能:
- 吞吐量:運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。吞吐量越高,CPU利用越高效,則算法越好
- 最大暫停時間:因 GC 而暫停應用程序線程的最長時間。暫停時間越短,則算法越好
高吞吐量和低暫停時間不可兼得。為了獲得最大吞吐量,JVM 必須盡可能少地運行 GC,只有在迫不得已的情況下才運行GC,比如:新生代或者老年代已經(jīng)滿了。但是推遲運行 GC 的結(jié)果是,每次運行 GC 時需要做的事情會很多,比如有更多的對象積累在堆上等待回收,因此每次的 GC 時間則會變高,由此引起的平均和最大暫停時間也會很高
垃圾收集器是內(nèi)存回收算法的具體實現(xiàn)。本文主要介紹 HotSpot 虛擬機中的垃圾回收器,如圖所示:

如果兩個收集器之間存在連線,說明他們可搭配使用。各垃圾回收器的功能比較如下表:

該選用哪一種垃圾回收器?
1. 客戶端程序: 一般使用 -XX:+UseSerialGC (Serial + Serial Old). 特別注意, 當一臺機器上起多個 JVM, 每個 JVM 也可以采用這種 GC 組合
2. 吞吐率優(yōu)先的服務端程序(計算密集型): -XX:+UseParallelGC 或者 -XX:+UseParallelOldGC
3. 響應時間優(yōu)先的服務端程序: -XX:+UseConcMarkSweepGC
4. 響應時間優(yōu)先同時也要兼顧吞吐率的服務端程序:-XX:+UseG1GC
5、CMS垃圾回收器
CMS(Concurrent Mark Sweep)垃圾收集器是以最短回收停頓時間為目標的垃圾收集器。一般B/S或互聯(lián)網(wǎng)站的服務端比較重視響應速度,希望系統(tǒng)的停頓時間最短,從而帶給用戶更好的體驗,CMS就比較符合這類應用的需求。
① 執(zhí)行過程
CMS是基于 "標記-清除" 算法實現(xiàn)的,由上文『復制GC算法』中所描述,新生代98%對象是朝生夕死的,所以將新生代分為1個Eden和2個survivor區(qū)(默認內(nèi)存大小是8:1:1),每次使用Eden和一個survivor區(qū),回收時,將活著的對象拷貝到剩余的一個survivor區(qū),并清理之前使用的Eden和survivor區(qū)的空間。

它運作過程分為以下幾個階段:
1、初始標記(需要 Stop The World):標記 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快
2、并發(fā)標記(和用戶線程一起工作):GC Roots Tracing的過程,例如:A是GC Root關(guān)聯(lián)到的對象,A引用B,A在初始階段標記出來,這個階段就是標記B對象
3、并發(fā)預清理(和用戶線程一起工作):并發(fā)查找在并發(fā)標記階段,從新生代晉升到老年代的對象、或直接在老年代分配的大對象、或被用戶線程更新的對象,來減少 "重新標記" 階段的工作量
4、重新標記(需要 Stop The World):修正『并發(fā)標記』和『并發(fā)預清理』用戶線程與GC線程并發(fā)執(zhí)行,用戶線程產(chǎn)生了新對象,將這些對象重新標記。這階段 STW 時間會比『初始標記』階段長一些,但遠比『并發(fā)標記』的時間短。暫停用戶線程,GC線程重新掃描堆中的對象,進行可達性分析,標記活著的對象
5、并發(fā)清理(和用戶線程一起工作):移除不用的對象,回收他們占用的堆空間。此時會產(chǎn)生新的垃圾,在此次GC無法清除,只好等到下次清理,這些垃圾名為:浮動垃圾
6、并發(fā)重置:重新設置 CMS 內(nèi)部的數(shù)據(jù)結(jié)構(gòu),準備下一次 CMS 生命周期的使用

并發(fā)標記階段修改了對象如何處理?
上述 CMS GC過程中第3個步驟:并發(fā)預清理,如何處理并發(fā)標記階段被修改的對象呢?初始標記階段的引用為 A → B → C,并發(fā)標記時,引用關(guān)系由用戶程序改為 A → C,B不再引用C ,由于C在 "并發(fā)標記" 階段無法被標記,就會被回收,而這是不允許的。

這可以通過三色標記法解決,將GC中的對象分為三種情況:
- 黑色:自身和它的子對象都掃描完成的對象,不會當成垃圾對象,不會被GC
- 灰色:對象本身被掃描,但還沒掃描完成子對象
- 白色:還沒有掃描過的對象,掃描完所有對象后,最終為白色的為不可達對象,會被當做垃圾對象
初始標記時,A 會被標記為灰色(正在掃描 A 相關(guān)),然后掃描 A 的引用,將 B 標記為灰色,然后 A 就掃描完成了,變?yōu)楹谏?/p>
并發(fā)標記時,如果用戶線程將 A 引用改為了 C,即 A → C,此時 CMS 在寫屏障(Write Barrier)里發(fā)現(xiàn)有一個白色對象的引用(C)被賦值到黑色對象(A)的字段里,那就會將 C 這個白色對象設置為灰色,即增量更新(Imcremental update)。
出現(xiàn)老年代引用新生代的對象,GC 時如何處理?
JVM采用卡片標記(Card Marking)方法,避免 Minor GC 時需要掃描整個老年代。做法是:將老年代按照一定大小分片,每一片對應 Cards 中一項,如果老年代的對象發(fā)生了修改或指向了新生代對象,就將這個老年代的 Card 標記為 dirty。Young GC 時,dirty card 加入待掃描的 GC Roots 范圍,避免掃描整個老年代

② CMS的優(yōu)缺點
優(yōu)點:
1、并發(fā)收集、低停頓,Sun公司的一些官方文檔也稱之為并發(fā)低停頓收集器(Concurrent Low Pause Collector)
缺點:
1、對CPU資源非常敏感:在并發(fā)階段,它雖然不會導致用戶線程停頓,但會因為占用一部分線程(或CPU資源)而導致應用程序變慢,總吞吐量降低
2、產(chǎn)生空間碎片:基于“標記-清除”算法實現(xiàn),意味著收集結(jié)束后會有大量空間碎片產(chǎn)生,給大對象分配帶來麻煩
3、需要更大的堆空間:CMS標記階段應用程序還在繼續(xù)執(zhí)行,就會有堆空間繼續(xù)分配的情況,為保證 CMS 將堆回收完之前還有空間分配給正在運行的程序,必須預留一部分空間
③ CMS調(diào)優(yōu)策略
-XX:CMSInitiatingOccupancyFraction=70 : 該值代表老年代堆空間的使用率,默認值是92,假如設置為70,就表示第一次 CMS 垃圾收集會在老年代占用 70% 時觸發(fā)。過大會使 STW 時間過程,過小會影響吞吐率
-XX:+UseCMSCompactAtFullCollection,-XX:CMSFullGCsBeforeCompaction=4:執(zhí)行4次不壓縮的 Full GC 后,會執(zhí)行一次內(nèi)存壓縮的過程,用來消除內(nèi)存碎片
-XX:+ConcGCThreads:并發(fā) CMS 過程運行時的線程數(shù),CMS 默認回收線程數(shù)是 (CPU+3) / 4。更多的線程會加快并發(fā)垃圾回收過程,但會帶來額外的同步開銷。
年輕代調(diào)優(yōu):Young GC 頻次高,則增大新生代;Young GC 時間長,則減少新生代。盡量在 Young GC 時候回收大部分垃圾
6、G1
G1(Garbage-First)是一款面向服務端應用的垃圾收集器,G1的設計初衷是最小化 STW 中斷時間,通常限制 GC 停頓時間比最大化吞吐率更重要。在Java9里,G1已經(jīng)成為默認的垃圾回收器。
① 執(zhí)行過程


G1的內(nèi)存布局和其他垃圾收集器有很大區(qū)別,它將整個Java堆分為 n 個大小相等的 Region,每個 Region 占有一塊連續(xù)的虛擬內(nèi)存地址。新生代和老年代不再是物理隔離,而是一部分 Region 的集合。
Region的大小可以通過 -XX:G1HeapRegionSize 指定,如果未設置,默認將堆內(nèi)存平均分為 2048 份。G1仍屬于分代收集器,除了Eden、Survivor、Old區(qū)外,還有 Humongous 區(qū)用于專門存放巨型對象(一個對象占用空間>50%分區(qū)容量),減少短期存在的巨型對象對垃圾收集器造成的負面影響。
G1 的運作過程分為以下幾個步驟:
1、全局并發(fā)標記:基于 STAB(snapshot-at-the-beginning)形式的并發(fā)標記,標記完成后,G1 基本知道了哪個區(qū)域是空的,它首先會收集哪些產(chǎn)出大量空閑空間的區(qū)域,這也是它命名為 Garbage-First 的原因
1.1 初始標記(STW,耗時很短):標記 GC Roots 能直接關(guān)聯(lián)到的對象,將它們的字段壓入掃描棧
1.2 并發(fā)標記(與用戶線程并發(fā)執(zhí)行,耗時較長):GC 線程不斷從掃描棧中取出引用,然后遞歸標記,直至掃描棧清空
1.3 最終標記(STW):重新標記『并發(fā)標記』期間因用戶程序執(zhí)行而導致引用發(fā)生變動的那一部分標記(寫入屏障 Write Barrier 標記的對象)
1.4 清理(STW):統(tǒng)計各個 Region 被標記存活的對象有多少,如果發(fā)現(xiàn)沒有存活對象的 Region,就會將其整體回收到可分配的 Region 中
2、拷貝存活對象:將一部分 Region 里的存活對象拷貝到空 Region 里去,然后回收原本的 Region 的空間。
G1 的 GC 可以分為 Young GC 和 Mixed GC 兩種類型。Young GC 是選定所有新生態(tài)的 Region,通過控制新生代的 Region 個數(shù)控制 Young GC 的開銷。Mixed GC 是選定所有新生代里的 Region,外加根據(jù)『全局并發(fā)標記』統(tǒng)計的收益較高的若干老年代 Region,在用戶指定的停頓時間內(nèi)盡可能選擇收益較高的老年代 Region。G1 里不存在 Full GC,老年代的收集全靠 Mixed GC 來完成。

在 G1 中,使用 Rememberd Set 跟蹤 Region 內(nèi)的對象引用,來避免全堆掃描的。每個 Region 都有一個與之對應的 Rememberd Set,當程序?qū)?Reference 類型的數(shù)據(jù)進行寫操作時,會產(chǎn)生 Write Barrier 暫停中斷寫操作,檢查 Reference 引用的對象是否處于不同的 Region 之中,如果是,便通過 CardTable 把相關(guān)引用信息記錄到被引用對象所屬 Region 的 Rememberd Set 中。當 GC 時,在GC Root 的枚舉范圍中加入 Rememberd Set 即可保證不對全堆掃描,也不會遺漏。
② G1與CMS的比較
G1的設計目標是取消CMS收集器,G1與CMS相比,有一些顯而易見的優(yōu)點:
1、簡單可行的性能調(diào)優(yōu):-XX:+UseG1GC -Xmx32g,使用這兩個參數(shù)即可應用于生產(chǎn)環(huán)境,表示開啟G1,堆最大內(nèi)存為32G;-XX:MaxGCPauseMillis=n 使用這個參數(shù)可設置期望的GC中暫停時間。取消老年代的物理空間劃分,無需對每個代的空間進行大小設置
2、可預測的 STW 停頓時間:G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定 GC 的停頓時間不超過 n 毫秒。這是通過跟蹤各個 Region 里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經(jīng)驗值),在后臺維護優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region,保證了 G1 能在有限的時間內(nèi)可以獲取盡可能高的收集效率
3、空間整合:G1 的兩個 Region 之間是基于『復制』算法實現(xiàn),在運作期間不會產(chǎn)生內(nèi)存碎片,分配大對象時不會因為無法找到連續(xù)空間而提前出發(fā) Full GC
③ CMS調(diào)優(yōu)策略
-XX:MaxGCPauseMillis=n:設置GC時最大暫停時間,這個目標不一定能滿足,JVM會盡最大努力實現(xiàn)它,不建議設置的過?。?lt;50ms)
-XX:InitiatingHeapOccupancyPercent=n:觸發(fā)G1啟動 Mixed GC,表示垃圾對象在整個 G1 堆內(nèi)存空間的占比
避免使用 -Xmn 或 -XX:NewRatio等其他顯式設置年輕代大小的選項,固定年輕代大小會覆蓋暫停時間目標