目錄
1 JVM 類加載機制
2 JVM 記憶體區域
3 JVM 運行時記憶體
4 垃圾回收與演算法
5 JVM 引數詳解
6 JVM 調優工具介紹
1 JVM 類加載機制

-
1.1 JVM 類加載的五個階段
-
1.1.1 加載
-
加載時類加載程序中的一個階段,這個階段會在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的入口,注意這里不一定非得從一個Class檔案獲取,這里既可以從ZIP包中讀取(比如從jar包和war包中讀取),也可以在運行時計算生成(動態代理),也可以由其它檔案生成(比如將JSP檔案轉換成對應的Class類)
-
-
1.1.2 驗證
-
這一階段的主要目的是為了確保Class檔案的位元組流中包含的資訊是否符合當前虛擬機的要求,并且不會危害虛擬機自身的安全
-
-
1.1.3 準備
-
準備階段是正式為類變數分配記憶體并設定類變數的初始值階段,即在方法區中分配這些變數所使用的記憶體空間,注意這里所說的初始值概念,比如一個類變數定義為:public static int v = 8080;世紀上變數v在準備階段過后的初始值為0而不是8080,將 v賦值為8080的put static指令是程式被編譯后,存放于類構造器<client>方法之中,但是注意如果宣告為:public static final int v = 8080;在編譯階段會為v生成ConstantValue屬性,在準備階段虛擬機會根據 ConstantValue屬性將v賦值為8080
-
-
1.1.4 決議
-
決議階段是值虛擬機將常量池中的符號參考替換為直接參考的程序,符號參考就是class檔案中的
-
-
CONSTANT_Class_info
-
-
-
CONSTANT_Field_info
-
-
-
CONSTANT_Method_info 等型別的常量,
-
-
1.1.4.1 符號參考
-
符號參考與虛擬機實作的布局無關,參考的目標并不一定要已經加載到記憶體中,各種虛擬機實作的記憶體布局可以各不相同,但是他們能接受的符號參考是必須一致的,因為符號參考的字面量形式明確定義在Java虛擬機規范的Class檔案格式中
-
-
1.1.4.2 直接參考
-
直接參考可以是指向目標的指標,相對偏移量或是一個能間接定位到目標的句柄,如果有了直接參考,那參考的目標必定已經在記憶體中存在
-
-
-
-
1.1.5 初始化
-
初始化階段是類加載最后一個階段,前面的類加載階段之后,除了在類加載階段可以自定義類加載器以外,其它操作都由JVM主導,到了初始階段,才開始真正執行類中定義的Java程式代碼
-
-
1.1.6 類構造器<client>
-
初始化階段是執行類構造器<client>方法的程序,<client>方法是由編譯器自動收集類中的類變數的賦值操作和靜態陳述句塊中的陳述句合并而成的,虛擬機會保證子執行類構造器<client>方法執行之前,父類的<client>方法已經執行完畢,如果一個類中沒有對靜態變數賦值也沒有靜態陳述句塊,name編譯器可以不為這個類生成<client>()方法,注意以下幾種情況不會執行類初始化:
-
1.通過類參考父類的靜態欄位,只會觸發父類的初始化,而不會觸發子類的初始化,
-
2.定義物件陣列,不會觸發該類的初始化,
-
3.常量在編譯期間會存入呼叫類的常量池中,本質上并沒有直接飲用定義常量的類,不會觸發定義常量所在的類
-
4.通過類名獲取Class物件,不會觸發類的初始化
-
5.通過Class.forName加載指定類時,如果指定引數initialize為false時,也不會觸發類初始化,其實這個引數告訴虛擬機,是否要對類進行初始化,
-
6.通過ClassLoader默認loadClass方法,也不會觸發初始化動作
-
-
-
-
1.2 類加載器
-
虛擬機設計團隊把加載動作放到JVM外部實作,以便讓應用程式決定如何獲取所需的類,JVM提供了3種類加載器:
-
1.2.1啟動類加載器(Bootstrap ClassLoader)
-
-
負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath 引數指定路徑中的,且被 虛擬機認可(按檔案名識別,如 rt.jar)的類,
-
-
-
1.2.2 擴展類加載器(Extension ClassLoader)
-
-
負責加載 JAVA_HOME\lib\ext 目錄中的,或通過 java.ext.dirs 系統變數指定路徑中的類 庫,
-
-
-
1.2.3應用程式類加載器(Application ClassLoader):
-
-
負責加載用戶路徑(classpath)上的類別庫,JVM 通過雙親委派模型進行類的加載,當然我們也可以通過繼承 java.lang.ClassLoader 實作自定義的類加載器,
-

-
-
-
-
-
1.3 雙親委派
-
當一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父類去完成,每一個層次類加載器都是如此,因此所有的加載請求都應該傳送到啟動類加載其中,只有當父類加載器反饋自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class)子類加載器才會嘗試自己去加載,采用雙親委派機制的一個好處就是比如加載位于tr.jar包中的類java.lang.Object,不管是哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的是同樣一個Object物件
-
-
1.4 OSGI(動態模型系統)
-
OSGi(Open Service Gateway Initiative),是面向 Java 的動態模型系統,是 Java 動態化模塊化系 統的一系列規范,
-
1.4.1 動態改變構造
-
OSGi 服務平臺提供在多種網路設備上無需重啟的動態改變構造的功能,為了最小化耦合度和促使 這些耦合度可管理,OSGi 技術提供一種面向服務的架構,它能使這些組件動態地發現對方,
-
-
1.4.2 模塊化編程與熱插拔
-
OSGi 旨在為實作 Java 程式的模塊化編程提供基礎條件,基于 OSGi 的程式很可能可以實作模塊級 的熱插拔功能,當程式升級更新時,可以只停用、重新安裝然后啟動程式的其中一部分,這對企 業級程式開發來說是非常具有傭訓力的特性,
-
OSGi 描繪了一個很美好的模塊化開發目標,而且定義了實作這個目標的所需要服務與架構,同時 也有成熟的框架進行實作支持,但并非所有的應用都適合采用 OSGi 作為基礎架構,它在提供強大 功能同時,也引入了額外的復雜度,因為它不遵守了類加載的雙親委托模型,
-
-
2 JVM 記憶體區域
-
JVM記憶體區域主要分為執行緒私有區域【程式計數器、虛擬機堆疊、本地方法區】、執行緒共享區域【Java堆、方法區】、直接記憶體,
-
執行緒私有資料區域生命周期與執行緒相同,依賴用戶執行緒的啟動/結束而創建/銷毀(在Hotspot VM 內,每個執行緒都與作業系統的本地執行緒直接映射,因此這部分記憶體區域的存/否跟隨本地執行緒的 生/死對應)
-
執行緒共享區域隨虛擬機的啟動/關閉而創建/銷毀,
-
直接記憶體并不是JVM 運行時資料區的一部分,但也會被頻繁的使用:在JDK 1.4引入的 NIO 提供了基于Channerl與Buffer的IO方式,他可以使用Native函式庫直接分配堆外記憶體,然后使用DirectByteBuffer物件作為這塊記憶體的參考進行操作,這樣就避免了在Java堆和Native堆中來回復制資料,因此在一些場景中可以顯著提高性能,
-
2.1 程式計數器(執行緒私有)
-
一塊較小的記憶體空間,是當前執行緒所執行的位元組碼的行號指示器,每條執行緒都要有一個獨立的程式計數器,這類記憶體也被稱為"執行緒私有的記憶體,"
-
正在執行Java方法的話,計數器記錄的是虛擬機位元組碼指令的地址(當前指令的地址),如果還是Native方法,則為空,
-
這個區域是唯一一個在虛擬機中沒有任何規定OutOfMemoryError情況的區域
-
-
2.2 虛擬機堆疊(執行緒私有)
-
是描述java方法執行的記憶體模型,每個方法在執行的同時都會創建一個堆疊幀(Stack Frame)用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,每一個方法從呼叫直至執行完成的程序,就對應著一個堆疊幀在虛擬機堆疊中入堆疊到出堆疊的程序,
-
堆疊幀(Frame是用來存盤資料和部分程序結果的資料結構,同時也被用來處理動態鏈接(Dynamic Linking)、方法回傳值和例外分派(Dispatch Exception),堆疊幀隨著方法呼叫而創建,隨著方法結束而銷毀——無論方法是正常完成還是例外完成(拋出了再方法內違背捕獲的例外)都算作方法結束,
-
-
2.3 本地方法區(執行緒私有)
-
本地方法區和Java Stack 作用類似,區別是虛擬機堆疊為執行Java方法服務,而本地方法堆疊則為Native方法服務,如果一個VM 實作使用C-linkage模型來支持Native呼叫,name該堆疊將會是一個C堆疊,但HotSpot VM直接把本地方法堆疊和虛擬機堆疊合二為一,
-
-
2.4 堆(Heap-執行緒共享)-運行時資料區
-
是被執行緒共享的一塊記憶體區域,創建的物件和陣列都保存在Java堆記憶體中,也是垃圾收集器進行垃圾收集的最重要的記憶體區域,由于現代VM采用分代收集演算法,因此Java堆從GC的角度還可以細分為:新生代(Eden區、From Survivor 區和To Survivor 區)和老年代,
-
-
2.5 方法區/永久代(執行緒共享)
-
即我們常說的永久代(Permanent Generation),用于存盤被JVM 加載的類資訊、常量、靜態變數、即使編譯器編譯后的代碼等資料,HotSpot VM把GC分代收集擴展至方法區,即使用Java堆的永久代來實作方法區,這樣HoTSpot 的垃圾收集器就可以像管理Java堆一樣管理這部分記憶體,而不必為方法區專門開發專門的記憶體管理器(永久帶的記憶體回收的主要目標是針對常量池的回收和型別的卸載,因此收益一般很小),
-
運行時常量池(Runtime Constant Pool是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊時常量池(Constant Pool Table),用于存放編譯生成的各種字面量和符合參考,這部分內容將在類加載后存放到方法區的運行時常量池中國,Java虛擬機對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用于存盤哪種資料都必須符合規范上的要求,這樣才會被虛擬機認可、裝載和執行,
-
3 JVM 運行時記憶體
-
Java 堆從 GC 的角度還可以細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年 代

-
3.1 新生代
-
3.1.1 Eden 區
-
Java新物件的出生地(如果新創建的物件占用記憶體很大,直接分配到老年代),當Eden區記憶體不夠的時候就會觸發MinorGC,對新生代區進行一次垃圾回收,
-
-
3.1.2 ServivorFrom
-
上一次GC的幸存者作為這一次GC的被掃描者
-
-
3.1.3 ServivorTo
-
保留了一次MinorGC程序中的幸存者
-
-
3.1.4 MinorGC 的程序(復制->清空->互換)
-
MinorGC采用復制演算法
-
1:eden、servicorFrom 復制到 ServicorTo,年齡+1,首先,把 Eden 和 ServivorFrom 區域中存活的物件復制到 ServicorTo 區域(如果有物件的年 齡以及達到了老年的標準,則賦值到老年代區),同時把這些物件的年齡+1(如果 ServicorTo 不 夠位置了就放到老年區);
-
2:清空 eden、servicorFrom然后,清空 Eden 和 ServicorFrom 中的物件;
-
3:ServicorTo 和 ServicorFrom 互換最后,ServicorTo 和 ServicorFrom 互換,原ServicorTo 成為下一次 GC 時的 ServicorFrom 區,
-
-
-
-
3.2 老年代
-
主要存放應用程式中生命周期長的記憶體物件,
-
老年代的物件比較穩定,所以MajorGC不會頻繁執行,在進行MajorGC前一般都先進行了一次MinorGC,是得有新生代的物件晉升入老年代,導致空間不夠用時才觸發,當無法找到足夠大的連續空間分配給新創建的較大物件時也會提前觸發一次MajorGC進行垃圾回收騰出空間,
-
MajorGc采用標記清除演算法:首先掃描一次所有的老年代,標記出存活的物件,然后回收沒有標記的物件,Major的耗時比價長,因為要掃描再回收,Major會產生記憶體碎片,為了減少記憶體損耗,我們一般需要進行合并或者標記出來方便下次直接分配,當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)例外
-
-
3.3 永久代
-
指記憶體的永久保存區域,主要存放Class和Meta(元資料)的資訊,Class在被加載的時候被放入永久區域,它和存放實體的區域不同,GC不會在主程式運行期對永久區域進行清理,所以這也導致了永久代區域會隨著加載Class的增多而脹滿,最終拋出OOM例外
-
3.3.1 JAVA8 與元資料
-
在Java8中,永久代已經被移除,被一個稱為“元資料區”(元空間)的區域所取代,元空間的本質和永久代類似,元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地記憶體,因此,默認情況下,元空間的大小僅受本地記憶體限制,類的元資料放入native memory,字串池和類的靜態變數放入java堆中,這樣可以加載多少類的元資料就不再由MaxPermSize控制,而由系統的實際可以用空間來控制
-
-
4 垃圾回收與演算法
-
JVM GC
-
gc要做的三件事
-
1.哪些記憶體需要回收?
-
2.什么時候回收?
-
3.怎么回收?
-
-
哪些物件已經“死亡”
-
參考計演算法(Reference Counting)
-
回圈參考的問題
-
-
根搜索演算法(GC Roots Tracing)
-
通過一系列稱為GC Roots的電作為起點,向下搜索,當一個物件到任何GC Roots沒有參考鏈相連,說明其已經“死亡”
-
GC Roots
-
VM 堆疊中的參考
-
方法區中的靜態參考
-
JNI 中的參考
-
-
-
-
垃圾收集演算法
-
標記清除(Mark-Sweep)
-
效率低
-
記憶體碎片多
-
-
復制(Coping)
-
1-Eden
-
2-Survivor
-
-
標記整理(Mark-Compact)
-
分代收集(Generational Collecting)
-
-
垃圾收集器
-
Serial
-
ParNew
-
Parallerl Scavenge
-
Serial Old
-
Parallel Old
-
CMS-Concurrent Mark Sweep
-
-
引數
-
Xms
-
Xmx
-
Xmn
-
-XX:+PrintGCDetails
-
-XX:sURIVORrATIO=8
-
-XX:PretenureSizeThreshold=xxx
-
-XX:MaxTenuringThreshold
-
-
-
4.1 如何確定垃圾
-
4.1.1參考計數法
-
在Java中,參考和物件是有關聯的,如果要操作物件則必須用參考進行,因此,很顯然一個簡單的辦法是通過參考計數來判斷一個物件是否可以回收,簡單說,即一個物件如果沒有任何與之關聯的參考,即他們的參考計數都不為0,則說明物件不太可能再被用到,name這個物件就是可回收物件
-
-
4.1.2可達性分析
-
為了解決參考計數法的回圈參考問題,Java使用了可達性分析的方法,通過一系列的“GC roots”物件作為起點搜索,如果“GC roods”和一個物件之間沒有可達路徑,則稱該物件是不可達的,要注意的是,不可達物件不等價于可回收物件,不可達物件變為可回收物件至少要經過兩次標記程序,兩次標記后仍然是可回收物件,則將面臨回收
-
-
-
4.2 標記清除演算法(Mark-Sweep)
-
最基礎的垃圾胡思后演算法,分為兩個階段,標注和清除,標記階段標記出所有需要回收的物件,清除階段回收被標記的物件所占用的空間,該演算法最大的問題就是記憶體碎片化嚴重,后續可能發生大物件不能找到可利用空間的問題
-

-
-
4.3 復制演算法(copying)
-
為了解決Nark-Sweep演算法記憶體碎片化的缺陷而被提出的演算法,按記憶體容量將記憶體劃分為等大小的兩塊,每次只使用其中一塊,當這一塊記憶體滿后將尚存活的物件復制到另一塊上去,把已使用的記憶體清掉,這種演算法雖然實作簡單,記憶體效率高,不易產生碎片,但是有最大的問題就是可能記憶體被壓縮到了原本的一半,且存活物件增多的話,Copying演算法的效率會大大降低
-

-
-
4.4 標記整理演算法(Mark-Compact)
-
結合了以上兩個演算法,為了避免缺陷而提出,標記階段和Mark-Sweep演算法相同,標記后不是清理物件,而是將存活物件移向記憶體的一端,然后清除端邊界外的物件
-
-
4.5 分代收集演算法
-
分代收集法師目前大部分JVM所采用的方法,其核心思想是根據物件存活的不同生命周期將記憶體劃分為不同的域,一般情況下將GC堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation),老生代的特點是每次垃圾回收時只有少量物件需要被回收,新生代的特點是每次垃圾回收都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的演算法
-
4.5.1 新生代與復制演算法
-
目前大部分JVM的GC對新生代都采取Copying演算法,因為新生代中每次垃圾回收都要回收大部分物件,即要賦值的操作比較少,但通常并不是按照1:1來劃分新生代,一般將新生代劃分為一塊較大的Eden空間和兩個較小的Survivor空間(From Space,To Space),每次使用Edenkongjian和其中的一塊Survivor空間,當進行回收時,將該兩塊空間中還存活的物件復制到另一塊Survivor空間中
-

-
-
4.5.2 老年代與標記復制演算法
-
而老年代因為每次只回收少量物件,因而采用Mark-Compact演算法
-
1.Java虛擬機提到過的處于方法區的永生代(Permanent Generation),它用來存盤class類,常量,方法描述等,對永生代的回收主要包括廢棄常量和無用的類
-
2.物件的記憶體分配主要在新生代的Eden Space和Surivior的From Space(Survivor目前存放物件的那一塊),少數情況會直接分配到老生代
-
3.當新生代的Eden Space和From Space空間不足時就會發生一次GC,進行GC后,Eden Space和From Space區的存活物件就會被挪到To Space,然后將整個物件存盤到老生代
-
4.如果To Space無法粗狗存盤某個物件,則將這個物件存盤到老生代,
-
5.在進行GC后,使用的便是Eden Space和To Space了,如此反復回圈
-
6.當物件在Survivor區躲過一次GC后,其年齡就會+1.默認情況下年齡到達15的物件會被移到老生代中,
-
-
-
4.6 GC 分代收集演算法 VS 磁區收集演算法
-
4.6.1 分代收集演算法
-
當前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)演算法, 這種演算法會根據 物件存活周期的不同將記憶體劃分為幾塊, 如 JVM 中的 新生代、老年代、永久代,這樣就可以根據 各年代特點分別采用最適當的 GC演算法
-
4.6.1.1 在新生代-復制演算法
-
每次垃圾回收都能發現大批量物件已死,只有少量存活,因此選用復制演算法,只需要付出少量存活物件的復制成本就可以完成收集
-
-
4.6.1.2 在老年代-標記整理演算法
-
因為物件存活率高、沒有額外空間對它進行分配擔保,就必須采用“標記——清理”或“標記——整理”演算法來進行回收,不必進行記憶體復制,且直接騰出空閑記憶體
-
-
-
4.6.2 磁區收集演算法
-
磁區演算法則將整個堆記憶體空間劃分為連續的不同小區間,每個小區間獨立使用,獨立回收,這樣做的好處是可以控制一次回收多少個小區間,根據目標停頓時間,每次合理地回收若干個小區間(而不是整個堆),從而減少一次GC所產生的停頓
-
-
-
4.7 GC 垃圾收集器
-
Java堆記憶體被劃分為新生代和老年代兩部分,新生代主要使用復制和標記——清除垃圾回收演算法:老年代主要使用標記——整理垃圾回收演算法,因此Java虛擬機中針對新生代和老年代分別提供了多種不同的垃圾收集器
-
4.7.1 Serial 垃圾收集器(單執行緒、復制演算法)
-
Serial(英文連續)是最基本垃圾收集器,使用復制演算法,曾經是 JDK1.3.1 之前新生代唯一的垃圾 收集器,Serial是一個單執行緒的收集器,它不但只會使用一個 CPU 或一條執行緒去完成垃圾收集工 作,并且在進行垃圾收集的同時,必須暫停其他所有的作業執行緒,直到垃圾收集結束,Serial 垃圾收集器雖然在收集垃圾程序中需要暫停所有其他的作業執行緒,但是它簡單高效,對于限 定單個 CPU 環境來說,沒有執行緒互動的開銷,可以獲得最高的單執行緒垃圾收集效率,因此 Serial 垃圾收集器依然是 java 虛擬機運行在 Client 模式下默認的新生代垃圾收集器,
-
-
4.7.2 ParNew 垃圾收集器(Serial+多執行緒)
-
ParNew 垃圾收集器其實是 Serial 收集器的多執行緒版本,也使用復制演算法,除了使用多執行緒進行垃 圾收集之外,其余的行為和 Serial 收集器完全一樣,ParNew 垃圾收集器在垃圾收集程序中同樣也 要暫停所有其他的作業執行緒,ParNew 收集器默認開啟和 CPU 數目相同的執行緒數,可以通過-XX:ParallelGCThreads 引數來限 制垃圾收集器的執行緒數,【Parallel:平行的】ParNew 雖然是除了多執行緒外和 Serial 收集器幾乎完全一樣,但是 ParNew 垃圾收集器是很多 java 虛擬機運行在 Server 模式下新生代的默認垃圾收集器,
-
-
4.7.3 Parallel Scavenge 收集器(多執行緒復制演算法、高效)
-
Parallel Scavenge 收集器也是一個新生代垃圾收集器,同樣使用復制演算法,也是一個多執行緒的垃 圾收集器,它重點關注的是程式達到一個可控制的吞吐量(Thoughput,CPU 用于運行用戶代碼 的時間/CPU 總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)), 高吞吐量可以最高效率地利用 CPU 時間,盡快地完成程式的運算任務,主要適用于在后臺運算而 不需要太多互動的任務,自適應調節策略也是 ParallelScavenge 收 集器與 ParNew 收集器的一個 重要區別,
-
-
4.7.4 Serial Old 收集器(單執行緒標記整理演算法 )
-
Serial Old 是 Serial 垃圾收集器年老代版本,它同樣是個單執行緒的收集器,使用標記-整理演算法, 這個收集器也主要是運行在 Client 默認的 java 虛擬機默認的年老代垃圾收集器,在 Server 模式下,主要有兩個用途:
-
-
在 JDK1.5 之前版本中與新生代的 Parallel Scavenge 收集器搭配使用,
-
-
-
作為年老代中使用 CMS 收集器的后備垃圾收集方案,新生代 Parallel Scavenge 收集器與 ParNew 收集器作業原理類似,都是多執行緒的收集器,都使 用的是復制演算法, 在垃圾收集程序中都需要暫停所有的作業執行緒,
-
-
-
-
4.7.5 Parallel Old 收集器(多執行緒標記整理演算法)
-
Parallel Old 收集器是 Parallel Scavenge 的年老代版本,使用多執行緒的標記-整理演算法,在 JDK1.6 才開始提供,在JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保證新生代的吞吐量優先,無法保證整體的吞吐量,Parallel Old 正是為了在年老代同樣提供吞 吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,可以優先考慮新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略,
-
-
4.7.6 CMS 收集器(多執行緒標記清除演算法)
-
Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾 回收停頓時間,和其他年老代使用標記-整理演算法不同,它使用多執行緒的標記-清除演算法,最短的垃圾收集停頓時間可以為互動比較高的程式提高用戶體驗, CMS 作業機制相比其他的垃圾收集器來說更復雜,整個程序分為以下 4 個階段:
-
4.7.6.1 初始標記
-
只是標記一下 GC Roots 能直接關聯的物件,速度很快,仍然需要暫停所有的作業執行緒
-
-
4.7.6.2 并發標記
-
進行 GC Roots 跟蹤的程序,和用戶執行緒一起作業,不需要暫停作業執行緒,
-
-
4.7.6.3 重新標記
-
為了修正在并發標記期間,因用戶程式繼續運行而導致標記產生變動的那一部分物件的標記 記錄,仍然需要暫停所有的作業執行緒
-
-
4.7.6.4 并發清除
-
清除 GC Roots 不可達物件,和用戶執行緒一起作業,不需要暫停作業執行緒,由于耗時最長的并 發標記和并發清除程序中,垃圾收集執行緒可以和用戶現在一起并發作業, 所以總體上來看 CMS 收集器的記憶體回收和用戶執行緒是一起并發地執行,
-
-
-
-
4.7.7 收集器
-
Garbage first 垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與 CMS 收集器,G1 收 集器兩個最突出的改進是:
-
-
基于標記-整理演算法,不產生記憶體碎片,
-
-
-
可以非常精確控制停頓時間,在不犧牲吞吐量前提下,實作低停頓垃圾回收,G1 收集器避免全區域垃圾收集,它把堆記憶體劃分為大小固定的幾個獨立區域,并且跟蹤這些區域 的垃圾收集進度,同時在后臺維護一個優先級串列,每次根據所允許的收集時間,優先回收垃圾 最多的區域,區域劃分和優先 級區域回識訓制,確保 G1 收集器可以在有限時間獲得最高的垃圾收 集效率
-
-
-
-
5 JVM 引數詳解
-
5.1 通用 JVM 引數
-
5.1.1 -server
-
如果不配置該引數,JVM會根據應用服務器硬體配置自動選擇不同模式,server模式啟動比較慢,但是運行期速度得到了優化,適合于服務器端運行的JVM
-
-
5.1.2 -client
-
啟動比較快,但是運行期相應沒有server模式的優化,適合于個人PC的服務開發和測驗
-
-
5.1.3 -Xmx
-
設定java heap的最大值,默認是機器物理記憶體的1/4.這個值決定了最多可用的Java堆記憶體:分配過少就會在應用中需要大量記憶體作為快取或者臨時物件時出現OOM(Out Of Memory)的問題:如果分配過大,name就會因PermSize過小而引起的另一種OutOfMemory,所以如何配置還是根據運行程序中的分析和計算來確定,如果不能確定還是采用默認的配置
-
-
5.1.4 -Xms
-
設定Java堆初始化時的大小,默認情況是機器物理記憶體的1/64.這個主要是根據應該用啟動時消耗的資源決定,分配少了申請起來會降低運行速度,分配多了也浪費,
-
-
5.1.5 -XX:PermSize
-
初始化永久記憶體區域大小,永久記憶體區域全稱是 Permanent Generation space,是指記憶體的永久保存區域,程式運行期不對 PermGen space 進行清理,所以如果你的 APP 會 LOAD 很多 CLASS 的話,就很可能出現 PermGen space錯誤,這種錯誤常見在 web 服務器對 JSP 進行 pre compile 的時候, 如果你的 WEB APP 下用了大量的第三方 jar, 其大小超過了 jvm 默認的 PermSize 大小(4M)那么就會產生此錯誤資訊了,
-
-
5.1.6 -XX:MaxPermSize
-
設定永久記憶體區域最大大小
-
-
5.1.7 -Xmn
-
直接設定青年代大小,整個 JVM 可用記憶體大小=青年代大小 + 老年代大小 + 持久代大小 ,持久代一般固定大小為 64m,所以增大年輕代后,將會減小老年代大小,此值對系統性能影響較大,Sun 官方推薦配置為整個堆的3/8,按照 Sun 的官方設定比例,則上面的例子中年輕代的大小應該為 2048*3/8=768M
-
-
5.1.8 -XX:NewRatio
-
控制默認的 Young 代的大小,例如,設定-XX:NewRatio=3 意味著 Young 代和老年代的比率是 1:3,換句話說,Eden 和 Survivor 空間總和是整個堆大小的 1/4,
-

如圖中的實際設定,-XX:NewRatio=2,-Xmx=2048,則年輕代和老年代的分配比例為 1:2,即年輕代的大小為 682M, 而老年代的大小為 1365M,查看實際系統的 jvm 監控結果為: 記憶體池名稱: Tenured Gen Java 虛擬機最初向作業系統請求的記憶體量: 3,538,944 位元組 Java 虛擬機實際能從作業系統獲得的記憶體量: 1,431,699,456 位元組 Java 虛擬機可從作業系統獲得的最大記憶體量: 1,431,699,456 位元組,請注意,并不一定能獲得該記憶體量, Java 虛擬機此時使用的記憶體量: 1,408,650,472 位元組 即:1,408,650,472 位元組=1365M,證明了上面的計算是正確的
-
-
5.1.9 -XX:SurvivorRatio
-
設定年輕代中 Eden 區與 Survivor 區的大小比值,設定為 4,則兩個 Survivor 區與一個 Eden 區的比值為 2:4,一個 Survivor 區占整個年輕代的 1/6,越大的 survivor 空間可以允許短期物件盡量在年青代消亡;如果 Survivor 空間太小,Copying 收集將直接將其轉移到老年代中,這將加快老年代的空間使用速度,引發頻繁的完全垃圾回收【SurvivorRatio 的值設為 3,Xmn 為 768M,則每個 Survivor 空間的大小為 768M/5=153.6M,】
-
-
5.1.10 -XX:NewSize
-
為了實作更好的性能,您應該對包含短期存活物件的池的大小進行設定,以使該池中的物件的存活時間不會超過一個垃圾回識訓圈,新生成的池的大小由 NewSize 和 MaxNewSize 引數確定,通過這個選項可以設定 Java 新物件生產堆記憶體,在通常情況下這個選項的數值為 1024 的整數倍并且大于 1MB,這個值的取值規則為,一般情況下這個值-XX:NewSize 是最大堆記憶體(maximum heap size)的四分之一,增加這個選項值的大小是為了增大較大數量的短生命周期物件,增加 Java 新物件生產堆記憶體相當于增加了處理器的數目,并且可以并行地分配記憶體,但是請注意記憶體的垃圾回收卻是不可以并行處理的,作用跟-XX:NewRatio 相似, -XX:NewRatio 是設定比例而-XX:NewSize是設定精確的數值,
-
-
5.1.11 -XX:MaxNewSize
-
通過這個選項可以設定最大 Java 新物件生產堆記憶體,通常情況下這個選項的數值為 1 024 的整數倍并且大于1MB,其功用與上面的設定新物件生產堆記憶體-XX:NewSize 相同,一般要將 NewSize 和 MaxNewSize 設成一致,
-
-
5.1.12 -XX:MaxTenuringThreshold
-
設定垃圾最大年齡,如果設定為 0 的話,則年輕代物件不經過 Survivor 區,直接進入老年代,對于老年代比較多的應用,可以提高效率,如果將此值設定為一個較大值,則年輕代物件會在 Survivor 區進行多次復制,這樣可以增加物件在年輕代的存活時間,增加在年輕代即被回收的概率【-XX:MaxTenuringThreshold 引數被設定成 5,表示物件會在 Survivor 區進行 5 次復制后如果還沒有被回收才會被復制到老年代】
-
-
5.1.13 -XX:GCTimeRatio
-
設定垃圾回收時間占程式運行時間的百分比,該引數設定為 n 的話,則垃圾回收時間占程式運行時間百分比的公式為 1/(1+n) ,如果 n=19 表示 java 可以用 5%的時間來做垃圾回收,1/(1+19)=1/20=5%,
-
-
5.1.14 -XX:TargetsurvivorRatio
-
該值是一個百分比,控制允許使用的救助空間的比例,默認值是 50,該引數設定較大的話可提高對 survivor 空間的使用率,當較大的堆疊使用較低的 SurvivorRatio 時,應增加該值到 80 至 90,以更好利用救助空間,
-
-
5.1.15 -Xss
-
設定每個執行緒的堆疊大小,根據應用的執行緒所需記憶體大小進行調整,在相同物理記憶體下,減小這個值能生成更多的執行緒,但是作業系統對一個行程內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000 左右,當這個選項被設定的較大(>2MB)時將會在很大程度上降低系統的性能,因此在設置這個值時應該格外小心,調整后要注意觀察系統的性能,不斷調整以期達到最優,JDK5.0 以后每個執行緒堆疊大小為 1M,以前每個執行緒堆疊大小為 256K,
-
-
5.1.16 -Xnoclassgc
-
這個選項用來取消系統對特定類的垃圾回收,它可以防止當這個類的所有參考丟失之后,這個類仍被參考時不會再一次被重新裝載,因此這個選項將增大系統堆記憶體的空間,禁用類垃圾回收,性能會高一點;
-
-
-
5.2 串行收集器引數
-
5.2.1 -XX:+UseSerialGC:
-
設定串行收集器
-
-
-
5.3 并行收集器引數
-
5.3.1 -XX:+UseParallelGC:
-
選擇垃圾收集器為并行收集器,此配置僅對年輕代有效,即上述配置下,年輕代使用并行收集,而老年代仍舊使用串行收集,采用了多執行緒并行管理和回收垃圾物件,提高了回收效率,提高了服務器的吞吐量,適合于多處理器的服務器,
-
-
5.3.2 -XX:ParallelGCThreads
-
配置并行收集器的執行緒數,即:同時多少個執行緒一起進行垃圾回收,此值最好配置與處理器數目相等,
-
-
5.3.3 -XX:+UseParallelOldGC:
-
采用對于老年代并發收集的策略,可以提高收集效率,JDK6.0 支持對老年代并行收集,
-
-
5.3.4 -XX:MaxGCPauseMillis
-
設定每次年輕代并行收集最大暫停時間,如果無法滿足此時間,JVM 會自動調整年輕代大小以滿足此值,
-
-
5.3.5 -XX:+UseAdaptiveSizePolicy
-
設定此選項后,并行收集器會自動選擇年輕代區大小和相應的 Survivor 區比例,以達到目標系統規定的最低回應時間或者收集頻率等,此值建議使用并行收集器時,一直打開,
-
-
-
5.4 并發收集器引數
-
5.4.1 -XX:+UseConcMarkSweepGC
-
指定在 老年代 使用 concurrent cmark sweep gc,gc thread 和 app thread 并行 ( 在 init-mark 和 remark 時pause app thread),app pause 時間較短 , 適合互動性強的系統 , 如 web server,它可以并發執行收集操作,降低應用停止時間,同時它也是并行處理模式,可以有效地利用多處理器的系統的多行程處理,
-
-
5.4.2 -XX:+UseParNewGC
-
指定在 New Generation 使用 parallel collector, 是 UseParallelGC 的 gc 的升級版本 , 有更好的性能或者優點 , 可以和 CMS gc 一起使用
-
-
5.4.3 -XX:+UseCMSCompactAtFullCollection:
-
打開對老年代的壓縮,可能會影響性能,但是可以消除碎片,在 FULL GC 的時候, 壓縮記憶體, CMS 是不會移動記憶體的, 因此, 這個非常容易產生碎片, 導致記憶體不夠用, 因此, 記憶體的壓縮這個時候就會被啟用, 增加這個引數是個好習慣
-
-
5.4.4 -XX:+CMSIncrementalMode
-
設定為增量模式,適用于單 CPU 情況
-
-
5.4.5 -XX:CMSFullGCsBeforeCompaction
-
由于并發收集器不對記憶體空間進行壓縮、整理,所以運行一段時間以后會產生“碎片”,使得運行效率降低,此值設定運行多少次 GC 以后對記憶體空間進行壓縮、整理,
-
-
5.4.6 -XX:+CMSClassUnloadingEnabled
-
使 CMS 收集持久代的類,而不是 fullgc
-
-
5.4.7 -XX:+CMSPermGenSweepingEnabled
-
使 CMS 收集持久代的類,而不是 fullgc,
-
-
5.4.8 -XX:-CMSParallelRemarkEnabled
-
在使用 UseParNewGC 的情況下 , 盡量減少 mark 的時間,
-
-
5.4.9 -XX:CMSInitiatingOccupancyFraction
-
說明老年代到百分之多少滿的時候開始執行對老年代的并發垃圾回收(CMS),這個引數設定有很大技巧,基本上滿足公式:
(Xmx-Xmn)(100-CMSInitiatingOccupancyFraction)/100>=Xmn時就不會出現 promotion failed,在我的應用中 Xmx 是 6000,Xmn 是 500,那么 Xmx-Xmn 是 5500 兆,也就是老年代有 5500 兆,CMSInitiatingOccupancyFraction=90 說明老年代到 90%滿的時候開始執行對老年代的并發垃圾回 收(CMS),這時還剩 10%的空間是 550010%=550 兆,所以即使 Xmn(也就是年輕代共 500 兆)里所有物件都搬到老年代里,550 兆的空間也足夠了,所以只要滿足上面的公式,就不會出現垃圾回收時的 promotion failed;如果按照 Xmx=2048,Xmn=768 的比例計算,則 CMSInitiatingOccupancyFraction 的值不能超過 40,否則就容易出現垃圾回收時的 promotion failed,
-
子主題 1
-
-
-
5.4.10 -XX:+UseCMSInitiatingOccupancyOnly
-
指示只有在老年代在使用了初始化的比例后 concurrent collector 啟動收集
-
-
5.4.11 -XX:SoftRefLRUPolicyMSPerMB
-
相對于客戶端模式的虛擬機(-client 選項),當使用服務器模式的虛擬機時(-server 選項),對于軟參考(softreference)的清理力度要稍微差一些,可以通過增大-XX:SoftRefLRUPolicyMSPerMB 來降低收集頻率,默認值是1000,也就是說每秒一兆位元組,Soft reference 在虛擬機中比在客戶集中存活的更長一些,其清除頻率可以用命令列引數 -XX:SoftRefLRUPolicyMSPerMB=<N> 來控制,這可以指定每兆堆空閑空間的 soft reference 保持存活(一旦它不強可達了)的毫秒數,這意味著每兆堆中的空閑空間中的 soft reference 會(在最后一個強參考被回收之后)存活 1 秒鐘,注意,這是一個近似的值,因為 soft reference 只會在垃圾回收時才會被清除,而垃圾回收并不總在發生,
-
-
5.4.12 -XX:LargePageSizeInBytes
-
記憶體頁的大小, 不可設定過大,會影響 Perm 的大小,
-
-
5.4.13 -XX:+UseFastAccessorMethods
-
原始型別的快速優化,get,set 方法轉成本地代碼,
-
-
5.4.14 -XX:+DisableExplicitGC
-
禁止 java 程式中的 full gc, 如 System.gc() 的呼叫, 最好加上防止程式在代碼里誤用了,對性能造成沖擊,
-
-
5.4.15 -XX:+AggressiveHeap
-
特別說明下:(我感覺對于做 java cache 應用有幫助)試圖是使用大量的物理記憶體長時間大記憶體使用的優化,能檢查計算資源(記憶體, 處理器數量) 至少需要 256MB 記憶體大量的 CPU/記憶體, (在 1.4.1 在 4CPU 的機器上已經顯示有提升)
-
-
5.4.16 -XX:+AggressiveOpts
-
加快編譯
-
-
5.4.17 -XX:+UseBiasedLocking
-
鎖機制的性能改善,
-
-
6 JVM 調優工具介紹
-
6.1 jmap 命令
-
查看堆記憶體分配和使用情況
-
./jmap -heap 31 //31 為程式的行程號
-
-
-
6.2 Top 命令監控結果
-
通過使用 top 命令進行持續監控發現此時 CPU 空閑比例為 85.7%,剩余物理記憶體為 3619M,虛擬記憶體 8G 未使用,持續的監控結果顯示行程 29003 占用系統記憶體不斷在增加,已經快得到最大值,
-
-
6.3 Jstat 命令監控結果
-
./jstat –cutil 29003 1000 100 使用 jstat 命令對 PID 為 29003 的行程進行 gc 回收情況檢查,發現由于 Old 段的記憶體使用量已經超過了設定的 80%的警戒線,導致系統每隔一兩秒就進行一次 FGC,FGC 的次數明顯多余 YGC 的次數,但是每次 FGC 后 old的記憶體占用比例卻沒有明顯變化—系統嘗試進行 FGC 也不能有效地回收這部分物件所占記憶體,同時也說明年輕代的引數配置可能有問題,導致大部分物件都不得不放到老年代來進行 FGC 操作
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/296796.html
標籤:其他
上一篇:三種主流工業以太網概述及其應用
