1 JVM 簡單結構圖

1.1 類加載子系統與方法區:
類加載子系統負責從檔案系統或者網絡中加載 Class 資訊,加載的類資訊存放于一塊稱 為方法區的記憶體空間。除了類的資訊外,方法區中可能還會存放運作時常量池資訊,包括字 符串字面量和數字常量(這部分常量資訊是 Class 檔案中常量池部分的記憶體映射)。
1.2 Java 堆
java 堆在虛拟機啟動的時候建立,它是 java 程式最主要的記憶體工作區域。幾乎所有的 java 對象執行個體都存放在 java 堆中。堆空間是所有線程共享的,這是一塊與 java 應用密切相關的記憶體空間。
1.3 直接記憶體
java 的 NIO 庫允許 java 程式使用直接記憶體。直接記憶體是在 java 堆外的、直接向系統申 請的記憶體空間。通常通路直接記憶體的速度會優于 java 堆。是以出于性能的考慮,讀寫頻繁 的場合可能會考慮使用直接記憶體。由于直接記憶體在 java 堆外,是以它的大小不會直接受限 于 Xmx 指定的最大堆大小,但是系統記憶體是有限的,java 堆和直接記憶體的總和依然受限于 作業系統能給出的最大記憶體。
1.4 垃圾回收系統
垃圾回收系統是 java 虛拟機的重要組成部分,垃圾回收器可以對方法區、java 堆和直 接記憶體進行回收。其中,java 堆是垃圾收集器的工作重點。和 C/C++不同,java 中所有的對 象空間釋放都是隐式的,也就是說,java 中沒有類似 free()或者 delete()這樣的函數釋放指定 的記憶體區域。對于不再使用的垃圾對象,垃圾回收系統會在背景默默工作,默默查找、辨別 并釋放垃圾對象,完成包括 java 堆、方法區和直接記憶體中的全自動化管理。
1.5 Java 棧
每一個 java 虛拟機線程都有一個私有的 java 棧,一個線程的 java 棧線上程建立的時候 被建立,java 棧中儲存着幀資訊,java 棧中儲存着局部變量、方法參數,同時和 java 方法 的調用、傳回密切相關。
1.6 本地方法棧
本地方法棧和 java 棧非常類似,最大的不同在于 java 棧用于方法的調用,而本地方法 棧則用于本地方法的調用,作為對 java 虛拟機的重要擴充,java 虛拟機允許 java 直接調用 本地方法(通常使用 C 編寫)
1.7 PC寄存器
PC(Program Counter)寄存器也是每一個線程私有的空間,java 虛拟機會為每一個 java 線程建立 PC 寄存器。在任意時刻,一個 java 線程總是在執行一個方法,這個正在被執行的 方法稱為目前方法。如果目前方法不是本地方法,PC 寄存器就會指向目前正在被執行的指 令。如果目前方法是本地方法,那麼 PC 寄存器的值就是 undefined
1.8 執行引擎
執行引擎是 java 虛拟機的最核心元件之一,它負責執行虛拟機的位元組碼,現代虛拟機 為了提高執行效率,會使用即時編譯(just in time)技術将方法編譯成機器碼後再執行。
Java HotSpot Client VM(-client),為在用戶端環境中減少啟動時間而優化的執行引擎;本 地應用開發使用。(如:eclipse) Java HotSpot Server VM(-server),為在伺服器環境中最大化程式執行速度而設計的執行引擎。應用在服務端程式。(如:tomcat)
Java HotSpot Client 模式和 Server 模式的差別
當虛拟機運作在-client 模式的時候,使用的是一個代号為 C1 的輕量級編譯器, 而-server 模式啟動的虛拟機采用相對重量級,代号為 C2 的編譯器. C2 比 C1 編譯器編譯的相對徹底,服 務起來之後,性能更高
JDK 安裝目錄/jre/lib/(x86、i386、amd32、amd64)/jvm.cfg
檔案中的内容,-server 和-client 哪一個配置在上,執行引擎就是哪一個。如果是 JDK1.5 版本且是 64 位系統應用時,-client 無效。
--64 位系統内容
-server KNOWN
-client IGNORE
--32 位系統内容
-server KNOWN
-client KNOWN
注意 :在部分JDK1.6版本和後續的JDK版本 (64位系統 ) 中, -client參數已經不起作用了, Server模式成為唯一
2 堆結構及對象分代
2.1 什麼是分代,分代的必要性是什麼
Java 虛拟機根據對象存活的周期不同,把堆記憶體劃分為幾塊,一般分為新生代、老年 代和永久代(對 HotSpot 虛拟機而言),這就是 JVM 的記憶體分代政策。 堆記憶體是虛拟機管理的記憶體中最大的一塊,也是垃圾回收最頻繁的一塊區域,我們程式 所有的對象執行個體都存放在堆記憶體中。給堆記憶體分代是為了提高對象記憶體配置設定和垃圾回收的效 率。試想一下,如果堆記憶體沒有區域劃分,所有的新建立的對象和生命周期很長的對象放在 一起,随着程式的執行,堆記憶體需要頻繁進行垃圾收集,而每次回收都要周遊所有的對象, 周遊這些對象所花費的時間代價是巨大的,會嚴重影響我們的 GC 效率。 有了記憶體分代,情況就不同了,新建立的對象會在新生代中配置設定記憶體,經過多次回收仍 然存活下來的對象存放在老年代中,靜态屬性、類資訊等存放在永久代中,新生代中的對象 存活時間短,隻需要在新生代區域中頻繁進行 GC,老年代中對象生命周期長,記憶體回收的 頻率相對較低,不需要頻繁進行回收,永久代中回收效果太差,一般不進行垃圾回收,還可 以根據不同年代的特點采用合适的垃圾收集算法。分代收集大大提升了收集效率,這些都是 記憶體分代帶來的好處。
2.2 分代的劃分
Java 虛拟機将堆記憶體劃分為 新生代、老年代和永久代 ,永久代是 HotSpot 虛拟機特有的 概念(JDK1.8 之後為 metaspace 替代永久代),它采用永久代的方式來實作方法區,其他的 虛拟機實作沒有這一概念,而且 HotSpot 也有取消永久代的趨勢,在 JDK 1.7 中 HotSpot 已經
開始了“去永久化”,把原本放在永久代的字元串常量池移出。永久代主要存放常量、類資訊、 靜态變量等資料,與垃圾回收關系不大,新生代和老年代是垃圾回收的主要區域。 記憶體簡圖如下:
2.2.1 新生代(Young Generation)
新生成的對象優先存放在新生代中,新生代對象朝生夕死,存活率很低,在新生代中, 正常應用進行一次垃圾收集一般可以回收 70% ~ 95% 的空間,回收效率很高。
HotSpot 将新生代劃分為三塊,一塊較大的 Eden(伊甸)空間和兩塊較小的 Survivor(幸 存者)空間,預設比例為 8:1:1。劃分的目的是因為 HotSpot 采用複制算法來回收新生代, 設定這個比例是為了充分利用記憶體空間,減少浪費。新生成的對象在 Eden 區配置設定(大對象 除外,大對象直接進入老年代),當 Eden 區沒有足夠的空間進行配置設定時,虛拟機将發起一次 Minor GC。
GC 開始時,對象隻會存在于 Eden 區和 From Survivor 區,To Survivor 區是空的(作為保 留區域)。GC 進行時,Eden 區中所有存活的對象都會被複制到 To Survivor 區,而在 From Survivor 區中,仍存活的對象會根據它們的年齡值決定去向,年齡值達到年齡閥值(預設為 15,新生代中的對象每熬過一輪垃圾回收,年齡值就加 1,GC 分代年齡存儲在對象的 header 中)的對象會被移到老年代中,沒有達到閥值的對象會被複制到 To Survivor 區。接着清空 Eden 區和 From Survivor 區,新生代中存活的對象都在 To Survivor 區。接着, From Survivor 區和 To Survivor 區會交換它們的角色,也就是新的 To Survivor 區就是上次 GC 清空的 From Survivor 區,新的 From Survivor 區就是上次 GC 的 To Survivor 區,總之,不管怎樣都會保證 To Survivor 區在一輪 GC 後是空的。GC 時當 To Survivor 區沒有足夠的空間存放上一次新生代 收集下來的存活對象時,需要依賴老年代進行配置設定擔保,将這些對象存放在老年代中。
2.2.2 老年代(Old Generationn)
在新生代中經曆了多次(具體看虛拟機配置的閥值)GC 後仍然存活下來的對象會進入 老年代中。老年代中的對象生命周期較長,存活率比較高,在老年代中進行 GC 的頻率相對 而言較低,而且回收的速度也比較慢。
2.2.3 永久代(Permanent Generationn)
永久代存儲類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料,對這一區域而
言,Java 虛拟機規範指出可以不進行垃圾收集,一般而言不會進行垃圾回收。
3 垃圾回收算法及分代垃圾收集器
3.1 垃圾收集器的分類
3.1.1 次收集器
Scavenge GC,指發生在新生代的 GC,因為新生代的 Java 對象大多都是朝生夕死,是以 Scavenge GC 非常頻繁,一般回收速度也比較快。當 Eden 空間不足以為對象配置設定記憶體時,會 觸發 Scavenge GC。
一般情況下,當新對象生成,并且在 Eden 申請空間失敗時,就會觸發 Scavenge GC,對 Eden 區域進行 GC,清除非存活對象,并且把尚且存活的對象移動到 Survivor 區。然後整理 Survivor 的兩個區。這種方式的 GC 是對年輕代的 Eden 區進行,不會影響到年老代。因為大 部分對象都是從 Eden 區開始的,同時 Eden 區不會配置設定的很大,是以 Eden 區的 GC 會頻繁 進行。因而,一般在這裡需要使用速度快、效率高的算法,使 Eden 去能盡快空閑出來。
當年輕代堆空間緊張時會被觸發
相對于全收集而言,收集間隔較短
3.1.2 全收集器
Full GC,指發生在老年代的 GC,出現了 Full GC 一般會伴随着至少一次的 Minor GC(老 年代的對象大部分是 Scavenge GC 過程中從新生代進入老年代),比如:配置設定擔保失敗。Full GC 的速度一般會比 Scavenge GC 慢 10 倍以上。當老年代記憶體不足或者顯式調用 System.gc() 方法時,會觸發 Full GC。
當老年代或者持久代堆空間滿了,會觸發全收集操作
可以使用 System.gc()方法來顯式的啟動全收集
全收集一般根據堆大小的不同,需要的時間不盡相同,但一般會比較長。
3.1.3 垃圾回收器的正常比對
3.2 常見垃圾回收算法
3.2.1 引用計數(Reference Counting)
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,删除一個引用則減 少一個計數。垃圾回收時,隻用收集計數為 0 的對象。此算法最緻命的是無法處理循環引用 的問題。
3.2.2 複制(Copying)
此算法把記憶體空間劃為兩個相等的區域,每次隻使用其中一個區域。垃圾回收時,周遊 目前使用區域,把正在使用中的對象複制到另外一個區域中。此算法每次隻處理正在使用中 的對象,是以複制成本比較小,同時複制過去以後還能進行相應的記憶體整理,不會出現“碎 片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍記憶體空間。簡圖如下:
3.2.3 标記-清除(Mark-Sweep)
此算法執行分兩階段。第一階段從引用根節點開始标記所有被引用的對象,第二階段遍 曆整個堆,把未标記的對象清除。此算法需要暫停整個應用,同時,會産生記憶體碎片。簡圖 如下:
3.2.4 标記-整理(Mark-Compact)
此算法結合了“标記-清除”和“複制”兩個算法的優點。也是分兩階段,第一階段從 根節點開始标記所有被引用對象,第二階段周遊整個堆,把清除未标記對象并且把存活對象 “壓縮”到堆的其中一塊,按順序排放。此算法避免了“标記-清除”的碎片問題,同時也 避免了“複制”算法的空間問題。簡圖如下:
3.3 分代垃圾收集器
3.3.1 串行收集器(Serial)
Serial 收集器是 Hotspot 運作在 Client 模式下的預設新生代收集器, 它的特點是:隻用一 個 CPU(計算核心)/一條收集線程去完成 GC 工作, 且在進行垃圾收集時必須暫停其他所有 的工作線程(“Stop The World” -後面簡稱 STW)。可以使用-XX:+UseSerialGC 打開。
雖然是單線程收集, 但它卻簡單而高效, 在 VM 管理記憶體不大的情況下(收集幾十 M~一 兩百 M 的新生代), 停頓時間完全可以控制在幾十毫秒~一百多毫秒内。
3.3.2 并行收集器(ParNew)
ParNew 收集器其實是前面 Serial 的多線程版本, 除使用多條線程進行 GC外, 包括 Serial 可用的所有控制參數、收集算法、STW、對象配置設定規則、回收政策等都與 Serial 完全一樣(也 是 VM 啟用 CMS 收集器-XX: +UseConcMarkSweepGC 的預設新生代收集器)。
由于存線上程切換的開銷, ParNew 在單 CPU 的環境中比不上 Serial, 且在通過超線程技 術實作的兩個 CPU 的環境中也不能 100%保證能超越 Serial. 但随着可用的 CPU 數量的增加, 收集效率肯定也會大大增加(ParNew 收集線程數與 CPU 的數量相同, 是以在 CPU 數量過大的 環境中, 可用-XX:ParallelGCThreads=<N>參數控制 GC 線程數)。
3.3.3 Parallel Scavenge 收集器
與 ParNew 類似, Parallel Scavenge 也是使用複制算法, 也是并行多線程收集器. 但與其 他收集器關注盡可能縮短垃圾收集時間不同, Parallel Scavenge 更關注系統吞吐量: 系統吞吐量=運作使用者代碼時間/(運作使用者代碼時間+垃圾收集時間) 停頓時間越短就越适用于使用者互動的程式-良好的響應速度能提升使用者的體驗;而高吞 吐量則适用于背景運算而不需要太多互動的任務-可以最高效率地利用CPU時間,盡快地完成 程式的運算任務. Parallel Scavenge 提供了如下參數設定系統吞吐量:
Parallel Scavenge 參數 | 描述 |
-XX:MaxGCPauseMillis | (毫秒數) 收集器将盡力保證記憶體回收花費的時間不超過 設定值, 但如果太小将會導緻 GC 的頻率增加. |
-XX:GCTimeRatio | (整數:0 < GCTimeRatio < 100) 是垃圾收集時間占總時間的比率 |
XX:+UseAdaptiveSizePo licy | 啟用 GC 自适應的調節政策: 不再需要手工指定-Xmn、 -XX:SurvivorRatio、-XX:PretenureSizeThreshold 等細節參數, VM 會根據目前系統的運作情況收集性能監控資訊, 動态調整這些 參數以提供最合适的停頓時間或最大的吞吐量 |
3.3.4 Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本, 同樣是單線程收集器,使用“标記-整理”算法
3.3.5 Parallel Old 收集器
Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 使用多線程和“标記-整理”算 法, 吞吐量優先, 主要與 Parallel Scavenge 配合在注重吞吐量及 CPU 資源敏感系統内使用;
3.3.6 CMS 收集器(Concurrent Mark Sweep)
CMS(Concurrent Mark Sweep)收集器是一款具有劃時代意義的收集器, 一款真正意義上 的并發收集器, 雖然現在已經有了理論意義上表現更好的 G1 收集器, 但現在主流網際網路企 業線上選用的仍是 CMS(如 Taobao、微店).
CMS是一種以擷取最短回收停頓時間為目标的收集器(CMS又稱多并發低暫停的收集器), 基于”标記-清除”算法實作, 整個 GC 過程分為以下 4 個步驟:
1. 初始标記(CMS initial mark)
2. 并發标記(CMS concurrent mark: GC Roots Tracing 過程)
3. 重新标記(CMS remark)
4. 并發清除(CMS concurrent sweep: 已死對象将會就地釋放, 注意:此處沒有壓縮)
其中 1,3 兩個步驟(初始标記、重新标記)仍需 STW. 但初始标記僅隻标記一下 GC Roots 能直接關聯到的對象, 速度很快; 而重新标記則是為了修正并發标記期間因使用者程式繼續運 行而導緻标記産生變動的那一部分對象的标記記錄, 雖然一般比初始标記階段稍長, 但要遠 小于并發标記時間.
CMS 特點:
1. CMS 預設啟動的回收線程數=(CPU 數目+3)4
當 CPU 數>4 時, GC線程一般占用不超過 25%的 CPU 資源, 但是當 CPU 數<=4 時, GC線程 可能就會過多的占用使用者 CPU 資源, 進而導緻應用程式變慢, 總吞吐量降低.
2.無法處理浮動垃圾, 可能出現 Promotion Failure、Concurrent Mode Failure 而導緻另一 次 Full GC 的産生: 浮動垃圾是指在 CMS 并發清理階段使用者線程運作而産生的新垃圾. 由于 在 GC 階段使用者線程還需運作, 是以還需要預留足夠的記憶體空間給使用者線程使用, 導緻 CMS 不 能 像 其 他收 集 器那 樣 等到 老 年 代幾 乎 填滿 了 再進 行 收 集. 是以 CMS 提供了 -XX:CMSInitiatingOccupancyFraction 參 數 來 設定 GC 的 觸 發 百 分 比 ( 以及
-XX:+UseCMSInitiatingOccupancyOnly 來啟用該觸發百分比), 當老年代的使用空間超過該比例 後 CMS 就會被觸發(JDK 1.6 之後預設 92%). 但當 CMS 運作期間預留的記憶體無法滿足程式需 要, 就會出現上述 Promotion Failure 等失敗, 這時 VM 将啟動後備預案: 臨時啟用 Serial Old 收集器來重新執行Full GC(CMS通常配合大記憶體使用, 一旦大記憶體轉入串行的Serial GC, 那停 頓的時間就是大家都不願看到的了).
3.最後, 由于 CMS 采用”标記-清除”算法實作, 可能會産生大量記憶體碎片. 記憶體碎片過 多 可 能 會 導 緻 無 法 分 配 大 對 象 而 提 前 觸 發 Full GC. 是以 CMS 提供了 -XX:+UseCMSCompactAtFullCollection 開關參數, 用于在 Full GC 後再執行一個碎片整理過程. 但記憶體整理是無法并發的, 記憶體碎片問題雖然沒有了, 但停頓時間也是以變長了, 是以 CMS 還提供了另外一個參數-XX:CMSFullGCsBeforeCompaction 用于設定在執行 N 次不進行記憶體整 理的 Full GC 後, 跟着來一次帶整理的(預設為 0: 每次進入 Full GC 時都進行碎片整理).
3.3.7 分區收集- G1 收集器
G1(Garbage-First)是一款面向服務端應用的收集器, 主要目标用于配備多顆 CPU 的服務 器治理大記憶體.
- G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).
-XX:+UseG1GC 啟用 G1 收集器.
與其他基于分代的收集器不同, G1 将整個 Java 堆劃分為多個大小相等的獨立區域 (Region), 雖然還保留有新生代和老年代的概念, 但新生代和老年代不再是實體隔離的了, 它們都是一部分 Region(不需要連續)的集合.如:
每塊區域既有可能屬于 O 區、也有可能是 Y 區, 是以不需要一次就對整個老年代/新生 代回收. 而是當線程并發尋找可回收的對象時, 有些區塊包含可回收的對象要比其他區塊多 很多. 雖然在清理這些區塊時 G1 仍然需要暫停應用線程, 但可以用相對較少的時間優先回 收垃圾較多的 Region. 這種方式保證了 G1 可以在有限的時間内擷取盡可能高的收集效率.
G1的新生代收集跟ParNew類似: 存活的對象被轉移到一個/多個Survivor Regions. 如果 存活時間達到閥值, 這部分對象就會被提升到老年代.如圖:
其特定是:
一整塊堆記憶體被分為多個 Regions.
存活對象被拷貝到新的 Survivor 區或老年代.
年輕代記憶體由一組不連續的 heap 區組成, 這種方法使得可以動态調整各代區域尺寸.
Young GC 會有 STW 事件, 進行時所有應用程式線程都會被暫停.
多線程并發 GC.
G1老年代 GC特點如下 :
并發标記階段
1 在與應用程式并發執行的過程中會計算活躍度資訊 .
2 這 些活躍度資訊辨別出那些 regions 最适合在 STW 期間回收 (which regions will be best to reclaim during an evacuation pause).
3 不像 CMS 有清理階段 .
再次标記階段
1 使用 Snapshot-at-the-Beginning(SATB) 算法比 CMS 快得多 .
2 空 region 直接被回收 .
拷貝 / 清理階段 (Copying/Cleanup Phase)
1 年輕代與老年代同時回收 .
2 老年代記憶體回收會基于他的活躍度資訊 .
4 JVM 優化
4.1 JDK 常用 JVM優化相關指令
bin | 描述 | 功能 |
jps | 列印Hotspot VM 程序 | VMID、JVM 參數、main()函 數參數、主類名/Jar 路徑 |
jstat | 檢視 Hotspot VM 運作 時資訊 | 類加載、記憶體、GC[可分代 檢視]、JIT 編譯 指令格式:jstat -gc 10340 250 20 |
jinfo | 檢視和修改虛拟機各項配置 | -flag name=value |
jmap | heapdump: 生成VM堆 轉儲快照、查詢 finalize 執 行隊列、Java 堆和永久代詳 細資訊 | jmap -dump:live,format=b,file=heap.bin [VMID] |
jstack | 檢視 VM 目前時刻的線 程快照: 目前 VM 内每一條 線程正在執行的方法堆棧 集合 | Thread.getAllStackTraces()提 供了類似的功能 |
javap | 檢視經 javac 之後産生 的 JVM 位元組碼代碼 | 自動解析.class 檔案, 避免 了去了解 class 檔案格式以及手 動解析 class 檔案内容 |
jcmd | 一個多功能工具, 可以 用來導出堆, 檢視 Java 進 程、導出線程資訊、 執行 GC、檢視性能相關資料等 | 幾乎集合了 jps、jstat、jinfo、 jmap、jstack 所有功能 |
jconsole | 基于 JMX 的可視化監 視、管理工具 | 可以檢視記憶體、線程、類、 CPU 資訊, 以及對 JMX MBean 進 行管理 |
jvisualvm | JDK 中最強大運作監視 和故障處理工具 | 可以監控記憶體洩露、跟蹤垃 圾回收、執行時記憶體分析、CPU 分析、線程分析… |
4.1.1 jps
jps - l
顯示線程 id 和執行線程的主類名
jps -v
顯示線程 id 和執行線程的主類名和 JVM 配置資訊
4.1.2 jstat
jstat -參數 線程 id 執行時間(機關毫秒) 執行次數
jstat -gc 4488 30 10
SXC - survivor 初始空間大小,機關位元組。
SXU - survivor 使用空間大小, 機關位元組。
EC - eden 初始空間大小
EU - eden 使用空間大小
OC - old 初始空間大小
OU - old 使用空間大小
PC - permanent 初始空間大小
PU - permanent 使用空間大小
YGC - youngGC 收集次數
YGCT - youngGC 收集使用時長, 機關秒
FGC - fullGC 收集次數
FGCT - fullGC 收集使用時長
GCT - 總計收集使用總時長 YGCT+FGCT
4.1.3 jvisualvm
一個 JDK 内置的圖形化 VM 監視管理工具
4.1.4 visualgc 插件
重新開機 jvisualvm 工具
4.2 JVM常見參數
配置方式:java [options] MainClass [arguments]
options - JVM 啟動參數。 配置多個參數的時候,參數之間使用空格分隔。
參數命名: 常見為 -參數名
參數指派: 常見為 -參數名=參數值 | -參數名:參數值
4.2.1 記憶體設定
-Xms:初始堆大小,JVM 啟動的時候,給定堆空間大小。
-Xmx:最大堆大小,JVM 運作過程中,如果初始堆空間不足的時候,最大可以擴充到多 少。
-Xmn:設定年輕代大小。整個堆大小=年輕代大小+年老代大小+持久代大小。持久代一 般固定大小為 64m,是以增大年輕代後,将會減小年老代大小。此值對系統性能影響較大, Sun 官方推薦配置為整個堆的 3/8。
-Xss: 設定每個線程的 Java 棧大小。JDK5.0 以後每個線程 Java 棧大小為 1M,以前每 個線程堆棧大小為 256K。根據應用的線程所需記憶體大小進行調整。在相同實體記憶體下,減 小這個值能生成更多的線程。但是作業系統對一個程序内的線程數還是有限制的,不能無限 生成,經驗值在 3000~5000 左右。
-XX:NewSize=n:設定年輕代大小
-XX:NewRatio=n:設定年輕代和年老代的比值。如:為 3,表示年輕代與年老代比值為 1: 3,年輕代占整個年輕代+年老代和的 1/4
-XX:SurvivorRatio=n:年輕代中 Eden 區與兩個 Survivor 區的比值。注意 Survivor 區有兩個。 如:3,表示 Eden:Survivor=3:2,一個 Survivor 區占整個年輕代的 1/5
-XX:MaxPermSize=n:設定持久代大小
-XX:MaxTenuringThreshold:設定垃圾最大年齡。如果設定為 0 的話,則年輕代對象不經 過 Survivor 區,直接進入年老代。對于年老代比較多的應用,可以提高效率。如果将此值設 置為一個較大值,則年輕代對象會在 Survivor 區進行多次複制,這樣可以增加對象再年輕代 的存活時間,增加在年輕代即被回收的機率。
4.2.2 記憶體設定經驗分享
JVM 中最大堆大小有三方面限制:相關作業系統的資料模型(32-bt 還是 64-bit)限制; 系統的可用虛拟記憶體限制;系統的可用實體記憶體限制。32 位系統 下,一般限制在 1.5G~2G; 64 為作業系統對記憶體無限制。
Tomcat 配置方式:
編寫 catalina.bat|catalina.sh ,增加 JAVA_OPTS 參數設定。 windows 和 linux 配置方式不同。 windows - set "JAVA_OPTS=%JAVA_OPTS% 自定義參數 " ; linux - JAVA_OPTS="$JAVA_OPTS 自定義參數 "
常見設定: -Xmx3550m -Xms3550m -Xmn2g -Xss128k 适合開發過程的測試應用。要求實體記憶體大于 4G。
-Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=160m -XX:MaxTenuringThreshold=0 适合高并發本地測試使用。且大資料對 象相對較多(如 IO 流)
環境: 16G 實體記憶體,高并發服務,重量級對象中等(線程池,連接配接池等),常用對象 比例為 40%(運作過程中産生的對象 40%是生命周期較長的)
-Xmx10G -Xms10G -Xss1M -XX:NewRatio=3 -XX:SurvivorRatio=4 -XX:MaxPermSize=2048m -XX:MaxTenuringThreshold=5
4.2.3 收集器設定
收集器配置的時候,次收集器和全收集器必須比對。具體比對規則參考 3.1.3
-XX:+UseSerialGC:設定串行收集器,年輕帶收集器, 次收集器
-XX:+UseParallelGC:設定并行收集器
-XX:+UseParNewGC:設定年輕代為并行收集。可與 CMS 收集同時使用。JDK5.0 以上,JVM 會根據系統配置自行設定,是以無需再設定此值。
-XX:+UseParallelOldGC:設定并行年老代收集器,JDK6.0 支援對年老代并行收集。
-XX:+UseConcMarkSweepGC:設定年老代并發收集器,測試中配置這個以後, -XX:NewRatio 的配置失效,原因不明。是以,此時年輕代大小最好用-Xmn 設定。
-XX:+UseG1GC:設定 G1 收集器
4.2.4 垃圾回收統計資訊
類似日志的配置資訊。會有控制台相關資訊輸出。 商業項目上線的時候,不允許使用。 一定使用 loggc
-XX:+PrintGC
-XX:+Printetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
4.2.5 并行收集器設定
-XX:ParallelGCThreads=n:設定并行收集器收集時最大線程數使用的 CPU 數。并行收集線 程數。
-XX:MaxGCPauseMillis=n:設定并行收集最大暫停時間,機關毫秒。可以減少 STW 時間。
-XX:GCTimeRatio=n:設定垃圾回收時間占程式運作時間的百分比。公式為 1/(1+n)并發收 集器設定
-XX:+CMSIncrementalMode:設定為增量模式。适用于單 CPU 情況。
-XX:+UseAdaptiveSizePolicy:設定此選項後,并行收集器會自動選擇年輕代區大小和相應 的 Survivor 區比例,以達到目标系統規定的最低相應時間或者收集頻率等,此值建議使用并 行收集器時,一直打開。
-XX:CMSFullGCsBeforeCompaction=n:由于并發收集器不對記憶體空間進行壓縮、整理,所 以運作一段時間以後會産生“碎片”,使得運作效率降低。此值設定運作多少次 GC 以後對内 存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,但是可 以消除碎片
4.2.6 收集器設定經驗分享
關于收集器的選擇 JVM 給了三種選擇:串行收集器、并行收集器、并發收集器,但是 串行收集器隻适用于小資料量的情況,是以這裡的選擇主要針對并行收集器和并發收集器。 預設情況下,JDK5.0 以前都是使用串行收集器,如果想使用其他收集器需要在啟動時加入相 應參數。JDK5.0 以後,JVM 會根據目前系統配置進行判斷。
常見配置:
并行收集器主要以到達一定的吞吐量為目标,适用于科學計算和背景處理等。
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
使用 ParallelGC 作為并行收集器, GC 線程為 20(CPU 核心數>=20 時),記憶體問題根據 硬體配置具體提供。建議使用實體記憶體的 80%左右作為 JVM 記憶體容量。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
指定老年代收集器,在 JDK5.0之後的版本,ParallelGC對應的全收集器就是ParallelOldGC。 可以忽略
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
指定 GC 時最大暫停時間。機關是毫秒。每次 GC 最長使用 100 毫秒。可以盡可能提高 工作線程的執行資源。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
UseAdaptiveSizePolicy 是提高年輕代 GC 效率的配置。次收集器執行效率。
并發收集器主要是保證系統的響應時間,減少垃圾收集時的停頓時間。适用于應用服務 器、電信領域、網際網路領域等。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
指定年輕代收集器為 ParNew,年老代收集器 ConcurrentMarkSweep,并發 GC 線程數為 20(CPU 核心>=20),并發 GC 的線程數建議使用(CPU 核心數+3)/4 或 CPU 核心數【不推 薦使用】。
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
CMSFullGCsBeforeCompaction=5 執行 5 次 GC 後,運作一次記憶體的整理。
UseCMSCompactAtFullCollection 執行老年代記憶體整理。可以避免記憶體碎片,提高 GC 過 程中的效率,減少停頓時間。
4.2.7 簡單總結
年輕代大小選擇
響應時間優先的 應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情 況 選 擇 )。 在 此 種 情 況 下 , 年 輕 代 收 集 發 生 的 頻 率 也 是 最 小 的 。 同 時 , 減 少 到 達 年 老 代 的 對 象。
吞吐量優先的應用:盡可能的設定大,可能到達 Gbit 的程度。因為對響應時間沒有要 求,垃圾收集可以并行進行,一般适合 8CPU 以上的應用。
年老代大小選擇
響應時間優先的應用: 年老代使用并發收集器,是以其大小需要小心設定,一般要考 慮并發會話率和會話持續時間等一些參數。如果堆設定小了,可以會造成記憶體碎片、高回 收頻率以及應用暫停而使用傳統的标記清除方式;如果堆大了,則需要較 長的收集時間。 最優化的方案,一般需要參考以下資料獲得:
并發垃圾收集資訊
持久代并發收集次數
傳統 GC 資訊
花在年輕代和年老代回收上的時間比例
減少年輕代和年老代花費的時間,一般會提高應用的效率
吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老 代。原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而年老代存放長期 存活對象。
較小堆引起的碎片問題,因為年老代的并發收集器使用标記、清除算法,是以不會對堆 進行壓縮。當收集器回收時,他會把相鄰的空間進行合并,這樣可以配置設定給較大的對象。但 是,當堆空間較小時,運作一段時間以後,就會出現“碎片”,如果并發收集器找不到足夠的 空間,那麼并發收集器将會停止,然後使用傳統的标記、整理方式進行回收。如果出現“碎 片”,可能需要進行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并發收集器時,開啟對年老代的壓縮。
-XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這裡設定多少次 Full GC 後,對年老代進行壓縮
4.2.8 測試代碼
package jvm;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<GarbageCollectorMXBean> l = ManagementFactory.getGarbageCollectorMXBeans();
for(GarbageCollectorMXBean b : l) {
System.out.println(b.getName());
}
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}