《深入理解Java虛擬機器》讀書筆記

hipilee發表於2017-11-14

前言

  • 交流或更多內容請關注我的公眾號:nezha_blog
  • 我的技術部落格:nezha.github.io

微信公眾號

自己在閱讀《深入理解Java虛擬機器》後做了部分的整理,內容有些是來自網路,如有侵權,請聯絡郵箱:nezhaxiaozi@qq.com

本書第一次閱讀,所以並沒有全篇通讀,主要的閱讀的章節是

  • 第2章 Java記憶體區域與記憶體溢位異常
  • 第3章 Java垃圾回收機器與記憶體分配策略
  • 第4章 JVM效能監控與故障處理工具
  • 第7章 虛擬機器類載入機制

總之此書很值得一讀,不管是理解JVM記憶體模型或者GC的機制及怎麼去JVM調優這本書上寫的還是挺全面的。

第2章 Java記憶體區域與記憶體溢位異常

概述

對於Java程式設計師來說,在虛擬機器自動記憶體管理機制下,不需要為new操作去寫配對的delete/free程式碼,不容易出現記憶體洩漏。但是如果出現記憶體洩漏問題,如果不瞭解虛擬機器的機制,便難以定位。

執行時資料區域

下圖是Java執行時資料區域劃分圖

執行時資料區域

區域 是否執行緒共享 是否會記憶體溢位
程式計數器 不會
java虛擬機器棧
本地方法棧
方法區

1.程式計數器(執行緒私有)

  • 一塊較小的記憶體,可以看作是當前執行緒所執行的位元組碼的行號指示器;
  • 在虛擬機器概念模型(各種虛擬機器實現可能不一樣)中,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令;
  • 程式計數器是屬於執行緒私有的記憶體;
  • 如果執行的是Java方法,該計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果是Native方法則為空;

2.Java虛擬機器棧(執行緒私有)

  • Java虛擬機器棧也是執行緒私有的;
  • 描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變數表運算元棧動態連結方法出口等資訊。每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器中入棧到出棧的過程;
  • 區域性變數表存放了編譯器可知的各種基本資料型別、物件引用和returnAddress型別;其所需的記憶體空間在編輯期完成分配,不會再執行期改變;
  • 可能存在兩種異常:StackOverflowError(請求棧深度過大)和OutOfMemoryError(記憶體不夠時);

3.本地方法棧

  • 與虛擬機器棧非常相似,只不過是為虛擬機器使用到的Native方法服務;
  • 可能存在兩種異常:StackOverflowError和OutOfMemoryError;

4.Java堆(執行緒共享)

  • Java堆是被所有執行緒共享的,在虛擬機器啟動時建立;
  • 此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這分配;
  • 是垃圾收集器管理的主要區域,可以分為新生代老年代
  • 可以物理不連續,只要邏輯上是連續的即可;
  • 如果堆中沒有記憶體完成例項分配也無法再擴充套件時,會丟擲OutOfMemoryError異常;

Eden:From Survivor:To Survivor比值為8:1:1

Java堆記憶體結構

5.方法區/元空間(永久代)(執行緒共享)

  • 是執行緒共享的區域;
  • 用於儲存已被虛擬機器載入的類資訊常量靜態變數、即時編譯器編譯後的程式碼等資料;
  • 該區域對於垃圾收集來說條件比較苛刻,但是還是非常有必要要進行回收處理;
  • 當無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常;

6.執行時常量池

  • 是方法區的一部分;
  • Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放;
  • Java虛擬機器規範要求較少,通常還會把翻譯出來的直接引用也儲存在此;
  • 另外一個重要特徵是具備動態性,可以在執行期間將新的常量放入池中,如String的intern方法;
  • 可能存在的異常:OutOfMemoryError;

7.直接記憶體

  • 並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域;
  • JDK 1.4的NIO引入了基於通道(Channel)和緩衝區(Buffer)的IO方法,可以使用Native函式庫直接分配對外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作以提升效能;

物件的訪問定位

  • 棧上的reference型別在虛擬機器規範中只規定了一個指向物件的引用,並沒有定義這個引用應該通過何種方式去定位、訪問堆疊物件的具體位置,目前主流的方式方式有控制程式碼直接指標兩種。
  • 通過控制程式碼:Java堆中劃出一塊記憶體作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料與型別資料各自的具體地址資訊。其最大好處就是reference儲存的是穩定的控制程式碼地址,在物件被移動(垃圾收集時移到)只改變例項資料指標,而reference不需要修改;

通過控制程式碼訪問物件

  • 通過直接指標:Java堆物件的佈局中必須考慮如果放置訪問型別資料的相關資訊,而reference中存在的直接就是物件地址。其最大好處在於速度更快,節省了一次指標定位的時機開銷。HotSpot採用該方式進行物件訪問,但其他語言和框架採用控制程式碼的也非常常見。

通過直接指標

記憶體引數的調節見這邊

JAVA物件在JVM中記憶體分配

第3章 Java垃圾回收機器與記憶體分配策略

概述

思考GC需要完成的3件事情:

  • 1.哪些記憶體需要回收?
  • 2.什麼時候回收?
  • 3.如何回收?

再回頭看看第二章介紹的Java記憶體執行時區域的各個部分:

  • 程式計時器、虛擬機器棧、本地方法棧:隨執行緒而滅,棧幀隨方法而進行出棧和入棧,每一個棧幀分配的記憶體在類結構確定就已知,因此這幾個區域不需要考慮回收
  • 對於Java堆和方法區,只有程式執行期間才知道會建立哪些物件,記憶體的分配和回收都是動態的,垃圾收集器所關注的是這部分記憶體

判斷Java中物件存活的演算法

1.引用計數演算法

給物件新增引用計數器,當有地方引用它時就加1,引用失效就減1,為0時就認為物件不再被使用可回收。該演算法失效簡單,判斷高效,但並不被主流虛擬機器採用,主要原因是它很難解決物件之間相互迴圈引用的問題。

2.可達性分析演算法

通過一系列的稱為“GC Roots”的物件作為起點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),如果一個物件到GC Roots沒有引用鏈相連,則該物件是不可用的。

在Java語言中,可作為GC Roots的物件包括:

  • 虛擬機器棧(棧幀中的本地變數表)中引用的物件;
  • 方法區中類靜態屬性引用的物件;
  • 方法區中常量引用的物件;
  • 本地方法棧中JNI(即一般說的Native方法)引用的物件;

垃圾收集演算法

  • 1.標記-清除演算法(Mark-Sweep):首先標記出所有需要回收的物件,然後統一回收所有被標記的物件;缺點是效率不高且容易產生大量不連續的記憶體碎片;

標記-清除演算法

  • 複製演算法:將可用記憶體分為大小相等的兩塊,每次只使用其中一塊;當這一塊用完了,就將還活著的物件複製到另一塊上,然後把已使用過的記憶體清理掉。在HotSpot裡,考慮到大部分物件存活時間很短將記憶體分為Eden和兩塊Survivor,預設比例為8:1:1。代價是存在部分記憶體空間浪費,適合在新生代使用

複製演算法

  • 標記-整理演算法:首先標記出所有需要回收的物件,然後讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體適用於老年代

    標記-整理演算法

  • 分代收集演算法:一般把Java堆分新生代和老年代,在新生代用複製演算法在老年代用標記-清理或標記-整理演算法,是現代虛擬機器通常採用的演算法。

分代收集演算法

垃圾收集器

  • 垃圾收集演算法是記憶體回收的方法論,垃圾收集器就是記憶體回收的具體實現。
  • 這裡討論JDK 1.7 Update 14之後的HotSpot虛擬機器(此時G1仍處於實驗狀態),包含的虛擬機器如下圖所示(存在連線的表示可以搭配使用):

gc_for_hotspot

1.Serial收集器(單執行緒的收集器)

Serial收集器

  • 最基本、發展歷史最悠久,在JDK 1.3之前是新生代收集的唯一選擇;
  • 是一個單執行緒(並非指一個收集執行緒,而是會暫停所有工作執行緒)的收集器,採用的是複製演算法;
  • 現在依然是虛擬機器執行在Client模式下的預設新生代收集器,主要就是因為它簡單而高效(沒有執行緒互動的開銷);

2.ParNew收集器(Serial收集器的多執行緒版本)

ParNew收集器

  • 其實就是Serial收集器的多執行緒版本;
  • ParNew收集器在單CPU環境中絕對不會有比Serial收集器更好的效果;
  • 是許多執行在Server模式下虛擬機器首選的新生代收集器,重要原因就是除了Serial收集器外,只有它能與CMS收集器配合工作
  • 並行(Parallel):指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍處於等待狀態;
  • 併發(Concurrent):指使用者執行緒與垃圾收集執行緒同時執行,使用者執行緒在繼續執行而垃圾收集程式執行在另外一個CPU上;

3.Parallel Scavenge收集器

吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)

  • 新生代收集器,使用複製演算法,並行的多執行緒收集器;
  • CMS關注於盡可能縮短垃圾收集時使用者執行緒停頓時間不同,PS的目標是達到一個可控制的吞吐量
  • 高吞吐量可以高效率利用CPU時間,適合在後臺運算而不需要太多互動的任務;
  • -XX:MaxGCPauseMillis引數可以設定最大停頓時間,而停頓時間縮短是以犧牲吞吐量和新生代空間來換取的;
  • 另外它還支援GC自適應的調節策略;

4.Serial Old收集器

Serial Old收集器

  • 是Serial收集器的老年代版本,同樣是單執行緒,使用標記-整理演算法;
  • 主要是給Client模式下的虛擬機器使用的;
  • 在Server模式下主要是給JDK 1.5及之前配合Parallel Scavenge使用或作為CMS收集器的後備預案

5.Parallel Old收集器

Parallel Old收集器

  • 是Parallel Scavenge的老年代版本,使用多執行緒和標記-整理演算法;

6.CMS收集器

CMS收集器

  • 是一種以獲取最短回收停頓時間為目標的收集器,特別適合網際網路站或者B/S的服務端;
  • 它是基於標記-清除 演算法實現的,主要包括4個步驟:初始標記(STW-stop the world,只是初始標記一下GC Roots能直接關聯到的物件,速度很快)、併發標記(非STW,執行GC RootsTracing,耗時比較長)、重新標記(STW,修正併發標記期間因使用者程式繼續導致變動的那一部分物件標記)和併發清除(非STW,耗時較長);
  • 還有3個明顯的缺點:CMS收集器對CPU非常敏感(佔用部分執行緒及CPU資源,影響總吞吐量)、無法處理浮動垃圾(預設達到92%就觸發垃圾回收)、大量記憶體碎片產生(可以通過引數啟動壓縮);

7.G1收集器

G1收集器

  • 一款面向服務端應用的垃圾收集器,後續會替換掉CMS垃圾收集器;
  • 特點:並行與併發(充分利用多核多CPU縮短Stop-The-World時間)、分代收集(獨立管理整個Java堆,但針對不同年齡的物件採取不同的策略)、空間整合(基於標記-整理)、可預測的停頓(將堆分為大小相等的獨立區域,避免全區域的垃圾收集);
  • 關於Region:新生代和老年代不再物理隔離,只是部分Region的集合;G1跟蹤各個Region垃圾堆積的價值大小,在後臺維護一個優先列表,根據允許的收集時間優先回收價值最大的Region;Region之間的物件引用以及其他收集器中的新生代與老年代之間的物件引用,採用Remembered Set來避免全堆掃描;
  • 分為幾個步驟:1.初始標記(標記一下GC Roots能直接關聯的物件並修改TAMS值,需要STW但耗時很短)、2.併發標記(從GC Root從堆中物件進行可達性分析找存活的物件,耗時較長但可以與使用者執行緒併發執行)、3.最終標記(為了修正併發標記期間產生變動的那一部分標記記錄,這一期間的變化記錄在Remembered Set Log裡,然後合併到Remembered Set裡,該階段需要STW但是可並行執行)、4.篩選回收(對各個Region回收價值排序,根據使用者期望的GC停頓時間制定回收計劃來回收);
  • 《深入理解Java虛擬機器》讀書筆記

理解GC日誌

詳細的解讀,請查閱本書籍。

《深入理解Java虛擬機器》讀書筆記

  • 最前面的數字代表GC發生的時間(虛擬機器啟動以後的秒殺);
  • “[GC”和“[Full GC”說明停頓型別,有Full代表的是Stop-The-World的;
  • “[DefNew”、“[Tenured”和“[Perm”表示GC發生的區域;
  • 方括號內部的“3324K -> 152K(3712K)” 含義是 “GC前該記憶體已使用容量 -> GC後該記憶體區域已使用容量(該區域總容量)”;
  • 方括號之外的“3324K -> 152K(11904)” 含義是 “GC前Java堆已使用容量 -> GC後Java堆已使用容量(Java堆總容量)”;
  • 再往後“0.0025925 secs”表示該記憶體區域GC所佔用的時間;

記憶體分配與回收策略

  • 物件優先在新生代分配
  • 大物件直接進入老年代
  • 長期存活的物件將進入老年代
  • 動態物件年齡判斷:如果在Survivor空間中相同年齡所有物件大小總和大於Survivor空間的一半,大於或等於該年齡的物件直接進入老年代;
  • 空間分配擔保:發生Minor GC前,虛擬機器會先檢查老年代最大可用連續空間是否大於新生代所有物件總空間,如果不成立,虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗,如果允許繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代的平均大小,如果大於會嘗試進行一次Minor GC;如果小於或者不允許冒險,會進行一次Full GC;

第4章 JVM效能監控與故障處理工具

概述

定位問題時,知識和經驗是關鍵基礎、資料(執行日誌、異常堆疊、GC日誌、執行緒快照、堆轉儲快照)是依據、工具是運用知識處理資料的手段。

JDK命令列工具

《深入理解Java虛擬機器》讀書筆記

1.jps: 虛擬機器程式狀況工具

image.png

image.png

  • 功能:可以列出正在執行的虛擬機器程式,併線上虛擬機器執行的主類名稱及其本地虛擬機器唯一ID(LVMID);
  • 對於本地虛擬機器來說,LVMID和作業系統的程式ID是一致的;
  • 其他的工具通常都需要依賴jps獲取LVMID;
  • 主要選項:-q(只輸出LVMID)、-m(輸出傳給main函式的引數)、-l(輸出主類的全名)、-v(輸出虛擬機器啟動JVM引數);

2.jstat:虛擬機器統計資訊監視工具

image.png

image.png

  • 功能:監視虛擬機器各種執行狀態資訊,包括類裝載、記憶體、垃圾收集、JIT等;
  • 純文字監控首選

3.jinfo:Java配置資訊工具

image.png

  • 功能:實時地檢視虛擬機器各項引數。雖然jps -v可以檢視虛擬機器啟動引數,但是無法檢視一些系統預設的引數。
  • 支援執行期修改引數的能力,格式為“jinfo -flag name=value pid”;

4.jmap:Java記憶體映像工具

image.png

image.png

  • 功能:用於生成堆轉儲快照(一般稱為heapdump或dump檔案);
  • 其他可生成heapdump的方式:使用引數-XX:+HeapDumpOnOutOfMemoryError;使用引數-XX:+HeapDumpOnCtrlBreak然後使用Ctrl+Break生成;Linux系統使用kill -3生成;
  • 另外它還可以查詢finalize執行佇列、Java堆和永久代的詳細資訊;

5.jhat:虛擬機器堆轉儲快照分析工具

《深入理解Java虛擬機器》讀書筆記

《深入理解Java虛擬機器》讀書筆記

  • 功能:用於分析jmap生成的heapdump。其內建了一個微型的HTTP伺服器,可以在瀏覽器檢視分析結果;
  • 實際很少用jhat,主要有兩個原因:一是分析工程會耗用伺服器資源;功能相對BisualVM、IBM HeapAnalyzer較為簡陋

6.jstack:Java堆疊跟蹤工具

image.png

image.png

  • 功能:用於生成虛擬機器當前時刻的執行緒快照(一般稱為threaddump或javacore檔案)。javacore主要目的是定位執行緒出現長時間停頓的原因,比如死鎖、死迴圈、請求外部資源響應長等;
  • 另外JDK 1.5後Thread類新增了getAllStackTraces()方法,可以基於此自己增加管理頁面來分析

7.HSDIS:JIT生成程式碼反編譯

《深入理解Java虛擬機器》讀書筆記

image.png

  • 現代虛擬機器的實現慢慢地和虛擬機器規範產生差距,如果要分析程式如果執行,最常見的就是通過軟體除錯工具(GDB、Windbg等)斷點除錯,但是對於Java來說,很多執行程式碼是通過JIT動態生成到CodeBuffer中的;
  • 功能:HSDIS是官方推薦的HotSpot虛擬機器JIT編譯程式碼的反彙編工具,它包含在HotSpot虛擬機器的原始碼中但沒有提供編譯後的程式,可以自己下載放到JDK的相關目錄裡;

JDK視覺化工具

1.JConsole:Java監視與管理控制檯

  • 是一種基於JMX的視覺化監控和管理工具,它管理部分的功能是針對MBean進行管理,由於MBean可以使用程式碼、中介軟體伺服器或者所有符合JMX規範的軟體進行訪問,因此這裡著重介紹JConsole的監控功能;
  • 通過jconsole命令啟動JConsole後,會自動搜尋本機所有虛擬機器程式。另外還支援遠端程式的監控;
  • 進入主介面,支援檢視以下標籤頁:概述、記憶體、執行緒、類、VM摘要和MBean;

2.VisualVM:多合一故障處理工具

image.png

  • 目前為止JDK釋出的功能最強調的執行監控和故障處理程式,另外還支援效能分析;
  • VisualVM還有一個很大的優點:不需要被監視的程式基於特殊Agent執行,對應用程式的實際效能影響很小,可直接應用在生成環境中;
  • VisualVM基於NetBeans平臺開發,具備外掛擴充套件功能的特性,基於外掛可以做到:顯示虛擬機器程式以及程式配置、環境資訊(jps、jinfo)、監視應用程式的CPU、GC、堆、方法區以及執行緒的資訊(jstat、jstack)、dump以及分析堆轉儲快照(jmap、jhat)、方法級的程式執行效能分析,找出被呼叫最多執行時間最長的方法、離執行緒序快照(收集執行時配置、執行緒dump、記憶體dump等資訊建立快照)、其他plugins的無限可能。
  • 使用jvisualvm首次啟動時需要線上自動安裝外掛(也可手工安裝);
  • 特色功能:生成瀏覽堆轉儲快照(摘要、類、例項標籤頁、OQL控制檯)、分析程式效能(Profiler頁籤可以錄製一段時間程式每個方法執行次數和耗時)、BTrace動態日誌跟蹤(不停止目標程式執行的前提下通過HotSwap技術動態加入除錯程式碼);

第7章 虛擬機器類載入機制

概述

  • 虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。
  • 在Java語言裡面,型別的載入、連線和初始化過程都是在程式執行期間完成,這雖然增量一些效能開銷,但是會為Java應用程式提供高度的靈活性。

類載入的時機

  • 類的整個生命週期:載入、驗證、準備、解析、初始化、使用和解除安裝;其中驗證、準備和解析統稱為連線;
  • 虛擬機器規範沒有強制約束類載入的時機,但嚴格規定了有且只有5種情況必須立即對類進行初始化:遇到new、getstatic、putstatic和invokestatic指令;對類進行反射呼叫時如果類沒有進行過初始化;初始化時發現父類還沒有進行初始化;虛擬機器啟動指定的主類;動態語言中MethodHandle例項最後解析結果REF_getStatic等的方法控制程式碼對應的類沒有初始化時;

類載入的過程

1.載入

  • 通過一個類的全限定名來獲取定義此類的二進位制位元組流;
  • 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構;
  • 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口;

2.驗證

  • 驗證是連線階段的第一步,其目的是確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全;
  • 驗證階段是非常重要的,這個階段是否嚴謹決定了Java虛擬機器是否能承受惡意程式碼的攻擊;
  • 校驗動作:檔案格式驗證(基於二進位制位元組流)、後設資料驗證(對類的後設資料語義分析)、位元組碼驗證(對方法體語義分析)、符號引用驗證(對類自身以外的資訊進行匹配性校驗);

3.準備

  • 正式為變數分配記憶體並設定類變數初始值的階段,這些變數所使用的記憶體都將在這個方法區中進行分配;
  • 需要強調兩點:這時候記憶體分配的僅包括類變數,而不包括類例項變數;這裡所說的初始化通常情況下是資料型別的零值,真正的賦值是在初始化階段,如果是static final的則是直接賦值;

4.解析

  • 解析階段是虛擬機器將常量池內的符號引用(如CONSTANT_Class_infoCONSTANT_Fieldref_infoCONSTANT_Methodref_info等7種)替換為直接引用的過程;
  • 符號引用可以是任何形式的字面量,與虛擬機器實現的記憶體佈局無關,引用的目標並不一定已經載入到記憶體中;而直接引用是直接指向目標的指標、相對偏移量或是一個能間接定位到目標的控制程式碼,它和虛擬機器實現的記憶體佈局相關,引用的目標必定以及在記憶體中存在;
  • 對同一個符號引用進行多次解析請求是很常見的事情,虛擬機器實現可以對第一次解析的結果進行快取;

5.初始化

  • 是類載入過程的最後一步,真正開始執行類中定義的Java程式程式碼(或者說是位元組碼);
  • 初始化階段是執行類構造器方法的過程,該方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊中的語句合併產生的;
  • 方法與類的建構函式(或者說是例項構造器方法)不同,它不需要顯式地呼叫父類構造器,虛擬機器會保證在子類的方法執行之前,父類的方法已執行完畢;
  • 執行介面的方法不需要先執行父介面的方法,只有當父介面中定義的變數使用時父介面才會初始化,介面的實現類在初始化時也一樣不會執行介面的方法;
  • 方法初始化是加鎖阻塞等待的,應當避免在方法中有耗時很長的操作;

類載入器

  • 虛擬機器設計團隊把類載入階段的“通過一個類的全限定名來獲取描述此類的二進位制位元組流”這個動作放到虛擬機器外部去實現,實現這個動作的程式碼模組稱為類載入器;
  • 這是Java語言的一項創新,也是Java語言流行的重要原因,在類層次劃分、OSGI、熱部署、程式碼加密等領域大放異彩

類與類載入器

  • 對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在Java虛擬機器的唯一性,每一個類載入器都擁有一個獨立的類名稱空間;
  • 比較兩個類是否相等(如Class物件的equals方法、isAssignableFrom方法、isInstance方法),只有在這兩個類是由同一個類載入器載入的前提下才有意義;

雙親委派模型

關於雙親委派模型,這篇文章寫得簡單易懂:http://www.jianshu.com/p/acc7595f1b9d

image.png

  • 三種系統提供的類載入器:啟動類載入器(Bootstrap ClassLoader)、擴充套件類載入器(Extension ClassLoader)、應用程式類載入器(Application ClassLoader);
  • 雙親委派模型要求除了頂層的啟動類載入器外,其他的類載入器都應當有自己的父類載入器,這裡一般不會以繼承的關係來實現,而是使用組合的關係來複用父載入器的程式碼;
  • 其工作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,只有父類載入器反饋自己無法完成這個載入請求時(它的搜尋範圍中沒有找到所需的類),子載入器才會嘗試自己去載入;
  • 這樣的好處是Java類隨著它的類載入器具備了一種帶有優先順序的層次關係,對保證Java程式的穩定運作很重要;
  • 實現雙親委派的程式碼都集中在java.lang.ClassLoader的loadClass方法中,邏輯清晰易懂;

JVM常用引數調節

記憶體引數

引數 作用
-Xmx 堆大小的最大值。當前主流虛擬機器的堆都是可擴充套件的
-Xms 堆大小的最小值。可以設定成和 -Xmx 一樣的值
-Xmn 新生代的大小。現代虛擬機器都是“分代”的,因此堆空間由新生代和老年代組成。新生代增大,相應地老年代就減小。Sun官方推薦新生代佔整個堆的3/8
-Xss 每個執行緒的堆疊大小。該值影響一臺機器能夠建立的執行緒數上限
-XX:MaxPermSize= 永久代的最大值。永久代是 HotSpot 特有的,HotSpot 用永久代來實現方法區
-XX:PermSize= 永久代的最小值。可以設定成和 -XX:MaxPermSize 一樣的值
-XX:SurvivorRatio= Eden 和 Survivor 的比值。基於“複製”的垃圾收集器又會把新生代分為一個 Eden 和兩個 Survivor,如果該引數為8,就表示 Eden
-XX:PretenureSizeThreshold= 直接晉升到老年代的物件大小。大於這個引數的物件將直接在老年代分配。預設值為0,表示不啟用
-XX:HandlePromotionFailure= 是否允許分配擔保失敗。在 JDK 6 Update 24 後該引數已經失效。
-XX:MaxTenuringThreshold= 物件晉升到老年代的年齡。物件每經過一次 Minor GC 後年齡就加1,超過這個值時就進入老年代。預設值為15
-XX:MaxDirectMemorySize= 直接記憶體的最大值。對於頻繁使用 nio 的應用,應該顯式設定該引數,預設值為0

GC引數

垃圾收集器 引數 備註
Serial(新生代) -XX:+UseSerialGC 虛擬機器在 Client 模式下的預設值,開啟此開關後,使用 Serial + Serial Old 的收集器組合。Serial 是一個單執行緒的收集器
ParNew(新生代) -XX:+UseParNewGC 強制使用 ParNew,開啟此開關後,使用 ParNew + Serial Old 的收集器組合。ParNew 是一個多執行緒的收集器,也是 server 模式下首選的新生代收集器
-XX:ParallelGCThreads= 垃圾收集的執行緒數
Parallel Scavenge(新生代) -XX:+UseParallelGC 虛擬機器在 Server 模式下的預設值,開啟此開關後,使用 Parallel Scavenge + Serial Old 的收集器組合
-XX:MaxGCPauseMillis= 單位毫秒,收集器儘可能保證單次記憶體回收停頓的時間不超過這個值。
-XX:GCTimeRatio= 總的用於 gc 的時間佔應用程式的百分比,該引數用於控制程式的吞吐量
-XX:+UseAdaptiveSizePolicy 設定了這個引數後,就不再需要指定新生代的大小(-Xmn)、 Eden 和 Survisor 的比例(-XX:SurvivorRatio)以及晉升老年代物件的年齡(-XX:PretenureSizeThreshold)了,因為該收集器會根據當前系統的執行情況自動調整。當然前提是先設定好前兩個引數。
Serial Old(老年代) Serial Old 是 Serial 的老年代版本,主要用於 Client 模式下的老生代收集,同時也是 CMS 在發生 Concurrent Mode Failure 時的後備方案
Parallel Old(老年代) -XX:+UseParallelOldGC 開啟此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合。Parallel Old 是 Parallel Scavenge 的老年代版本,在注重吞吐量和 CPU 資源敏感的場合,可以優先考慮這個組合
CMS(老年代) -XX:+UseConcMarkSweepGC 開啟此開關後,使用 ParNew + CMS 的收集器組合。
-XX:CMSInitiatingOccupancyFraction= CMS 收集器在老年代空間被使用多少後觸發垃圾收集
-XX:+UseCMSCompactAtFullCollection 在完成垃圾收集後是否要進行一次記憶體碎片整理
-XX:CMSFullGCsBeforeCompaction= 在進行若干次垃圾收集後才進行一次記憶體碎片整理

附圖:可以配合使用的收集器組合

gc_for_hotspot

其他引數

引數 作用
-verbose:class 列印類載入過程
-XX:+PrintGCDetails 發生垃圾收集時列印 gc 日誌,該引數會自動帶上 -verbose:gc 和 -XX:+PrintGC
-XX:+PrintGCDateStamps / -XX:+PrintGCTimeStamps 列印 gc 的觸發事件,可以和 -XX:+PrintGC 和 -XX:+PrintGCDetails 混用
-Xloggc:<path> gc 日誌路徑
-XX:+HeapDumpOnOutOfMemoryError 出現 OOM 時 dump 出記憶體快照用於事後分析
-XX:HeapDumpPath= 堆轉儲快照的檔案路徑

參考文章

1.Gino Zhang的部落格總結的很全面 http://ginobefunny.com

2.關於雙親委派模型,這篇文章寫得簡單易懂:http://www.jianshu.com/p/acc7595f1b9d

相關文章