如題,本文的宗旨既是透過對象的生命週期,來梳理JVM內存結構及GC相關知識,並輔以AOP及雙親委派機制原理,學習不只僅是海綿式的吸取學習,還須要本身去分析why,加深對技術的理解和認知,祝你們早日走上本身的「成金之路」。html
本部分,從攻城獅編寫.java文件入手,詳解了編譯、載入、AOP原理。
讀過《程序員的自我修養》的朋友,對程序的編譯及執行會有一個很清晰的認識:編譯其實就是將人類能理解的代碼文件轉譯爲機器/CPU能執行的文件(包括數據段、代碼段),而執行的過程,則是根據文件頭部字節的標識(簡稱魔數),映射爲對應的文件結構體,找到程序入口,當獲取到CPU執行權限時,將方法壓棧,執行對應的指令碼,完成相應的邏輯操做。
而對應.java文件,則先須要使用javac進行編譯,編譯後的.class文件,此文件將java程序能讀懂的數據段和代碼段,以後用java執行文件,既是載入.class文件,找到程序入口,並根據要執行的方法,不停的壓棧、出棧,進行邏輯處理。java
在加載階段,虛擬機須要完成如下三件事情:linux
- 經過一個類的全限定名來獲取其定義的二進制字節流。
- 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
- 在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口。
即至關於在內存中將代碼段和數據段關聯起來,組織好Class對象的內存空間,做爲對象成員和方法的引入入口,並將.class及方法載入Perm內存區。相對於類加載的其餘階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動做)是可控性最強的階段,由於開發人員既可使用系統提供的類加載器來完成加載,也能夠自定義本身的類加載器來完成加載。程序員
分爲四個階段:算法
這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。驗證階段大體會完成4個階段的檢驗動做:
文件格式驗證驗證字節流是否符合Class文件格式的規範;例如:是否以0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍以內、常量池中的常量是否有不被支持的類型。
元數據驗證對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了java.lang.Object以外。
字節碼驗證經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。
符號引用驗證確保解析動做能正確執行。spring
驗證階段是很是重要的,但不是必須的,它對程序運行期沒有影響,若是所引用的類通過反覆驗證,那麼能夠考慮採用-Xverifynone參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。數據庫
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有如下幾點須要注意:
一、這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨着對象一塊分配在Java堆中。編程
二、這裏所設置的初始值一般狀況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。如public static int value = 3;那麼變量value在準備階段事後的初始值爲0,而不是3,由於這時候尚未開始執行任何Java方法,而把value賦值爲3的putstatic指令是在程序編譯後,存放於類構造器
()方法之中的,因此把value賦值爲3的動做將在 初始化階段纔會執行。這裏還須要注意以下幾點: 數組
i.對基本數據類型來講,對於類變量(static)和全局變量,若是不顯式地對其賦值而直接使用,則系統會爲其賦予默認的零值,而對於 局部變量來講,在使用前必須顯式地爲其賦值,不然編譯時不經過。
ii.對於同時被static和final修飾的常量,必須在聲明的時候就爲其顯式地賦值,不然編譯時不經過;而只被final修飾的常量則既能夠在聲明時顯式地爲其賦值,也能夠在類初始化時顯式地爲其賦值,總之,在使用前必須爲其顯式地賦值, 系統不會爲final修飾的常量賦予默認零值。
iii.對於 引用數據類型 reference來講,如數組引用、對象引用等,若是沒有對其進行顯式地賦值而直接使用,系統都會爲其賦予默認的零值,即null。
iv.若是在 數組初始化時沒有對數組中的各元素賦值,那麼其中的元素將根據對應的數據類型而被賦予默認的零值。
三、若是類字段的字段屬性表中存在ConstantValue屬性,即同時被final和static修飾,那麼在準備階段變量value就會被初始化爲ConstValue屬性所指定的值。如public static final int value = 3;編譯時Javac將會爲value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值爲3。瀏覽器
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程,解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,能夠是任何字面量。
直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
這個階段的主要目的將編譯後的虛擬地址(相似動態庫,庫數據段都是0x00開始,載入內存後須要與實際分配的地址關聯起來)與實際運行的地址關聯起來。
一、假如這個類尚未被加載和鏈接,則程序先加載並鏈接該類
二、假如該類的直接父類尚未被初始化,則先初始化其直接父類
三、假如類中有初始化語句,則系統依次執行這些初始化語句
類初始化時機:只有當對類的主動使用的時候纔會致使類的初始化,類的主動使用包括如下六種:
– 建立類的實例,也就是new的方式
– 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
– 調用類的靜態方法
– 反射(如Class.forName(「com.xxx.Test」))
– 初始化某個類的子類,則其父類也會被初始化
– Java虛擬機啓動時被標明爲啓動類的類(Java Test),直接使用java.exe命令來運行某個主類。
JVM最底層的載入是BootstrapClassLoader,爲C語言編寫,從Java中引用不到,其上是ExtClassLoader,而後是AppClassLoader,普通類中getContextClassLoader()獲得的是AppClassLoader,getContextClassLoader().getParent()獲得的是ExtClassLoader,而再往上getParent()爲null,即引用不到BootstrapLoader。
啓動類加載器:BootstrapClassLoader,負責加載存放在JDK\jre\lib(JDK表明JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,而且能被虛擬機識別的類庫(如rt.jar,全部的java.*開頭的類均被BootstrapClassLoader加載)。啓動類加載器是沒法被Java程序直接引用的。
擴展類加載器:ExtensionClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的全部類庫(如javax.*開頭的類),開發者能夠直接使用擴展類加載器。
應用程序類加載器:ApplicationClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者能夠直接使用該類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。
應用程序都是由這三種類加載器互相配合進行加載的,若是有必要,咱們還能夠加入自定義的類加載器。由於JVM自帶的ClassLoader只是懂得從本地文件系統加載標準的java class文件,所以若是編寫了本身的ClassLoader,即可以作到以下幾點:
1)在執行非置信代碼以前,自動驗證數字簽名。
2)動態地建立符合用戶特定須要的定製化構建類。
3)從特定的場所取得java class,例如數據庫中和網絡中。
雙親委派機制
簡單來講既是先拿父類加載器加載class,父類加載失敗後再使用本類加載器加載。當AppClassLoader加載一個class時,它首先不會本身去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。當ExtClassLoader加載一個class時,它首先也不會本身去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。若是BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,若是AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。
其它相關點
1.全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入。
2.父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時,才使用本類加載器從本身的類路徑中加載該類。
3.緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲何修改了Class後,必須重啓JVM,程序的修改纔會生效。
--
AOP面向切面編程
AOP 專門用於處理系統中分佈於各個模塊(不一樣方法)中的交叉關注點的問題,在 Java EE 應用中,經常經過AOP來處理一些具備橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等,在不改變已有代碼的狀況下,靜態/動態的插入代碼。
將AOP放到這裏的主要緣由是由於AOP改變的class文件,達到嵌入方法的目的,靜態模式使用AspectJ進行由.java到.class文件編譯。而動態模式時使用CGLIB載入使用javac編譯的.class文件後,使用動態代理的方式,將要執行的方法嵌入到原有class方法中,完成在內存中對class對象的構造,這也就是所謂動態代理技術的內在原理。同時靜態方式在載入前已經修好完.class文件,而動態方式在.class載入時須要作額外的處理,致使性能受到必定影響,但其優點是無須使用額外的編譯器。整體的技術的切入點在於在修改機器執行碼,達到增長執行方法的目的。參考我。
數據分類:基本類型與引用類型
基本類型包括:byte,short,int,long,char,float,double,boolean,returnAddress
引用類型包括:類類型,接口類型和數組。
數據存儲:使用堆存儲對象信息
方法調用:使用棧來解決方法嵌套調用,而棧內部由一個個棧幀構成,調用一個方法時,在當前棧上壓入一個棧幀,此棧幀包含局部變量表,操做棧等子項,每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。表面上代碼在運行時,是經過程序計數器不斷執行下一條指令;而實際指令運算等操做是經過控制操做棧的操做數入棧和出棧,將操做數在局部變量表和操做棧之間轉移。參見我。
這裏對內存的分配作個深刻的擴展,解釋下基礎類型的自動裝箱 boxing
以"Object obj = new Object();"爲例,一個空Object對象佔用8byte空間,而obj引用佔用4byte,此條語句執行後共佔用12byte;而Java中對象大小是8的整數倍,則Boolean b = new Boolean(true)至少須要20byte(16byte+4byte),而若是直接使用基本數據類型boolean b = true則僅僅須要1byte,在棧幀中存儲;爲優化此問題,JVM提出了基本類型的自動裝載技術,來自動化進行基本類型與基本類型對象間的轉換,來下降內存的使用量。
內存結構主要有三大塊:堆內存、方法區和棧。
1.堆內存是JVM中最大的一塊由Young Generation(年輕代、新生代)和Old Generation(年老代)組成,而Young Generation內存又被分紅三部分,Eden空間、From Survivor空間、To Survivor空間,默認狀況下年輕代按照8:1:1的比例來分配。
2.方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆)。
3.棧又分爲java虛擬機棧(方法執行的內存區,每一個方法執行時會在虛擬機棧中建立棧幀)和本地方法棧(虛擬機的Native方法執行的內存區)主要用於方法的執行。
4.內存設置參數:
-Xms設置堆的最小空間大小。
-Xmx設置堆的最大空間大小。
-XX:NewSize設置新生代最小空間大小。
-XX:MaxNewSize設置新生代最大空間大小。
-XX:PermSize設置永久代最小空間大小。
-XX:MaxPermSize設置永久代最大空間大小。
-Xss設置每一個線程的堆棧大小。
-XX:SurvivorRatio=x #Eden區與Survivor區的大小比值,默認爲8(Eden:From-Survivor=8:1)
-XX:NewRatio=x #年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)默認值爲2,即年輕代:年老代=1:2(這裏與數學的比值有差別)
對象分配先從Eden空間與From Survivor空間獲取內存,當二者中空間不足時,進行Minor GC,將Eden與From Survivor空間存活的對象,Copy到To Survivor空間,而後清空Eden與From Survivor空間,以後將To Survivor空間對象年齡加1,並將To Survivor空間設置爲From Survivor空間,保證Minor GC時To Survivor空間始終爲空;而當對象年齡爲15後(默認是 15,能夠經過參數-XX:MaxTenuringThreshold 來設定),將存活對象放入老年代。
例外狀況:
1.對於一些較大的對象(即須要分配一塊較大的連續內存空間)則是直接進入到老年代。虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。避免在新生代採用複製算法收集內存時,在Eden區及兩個Survivor區之間發生大量的內存複製。
2.爲了更好的適應不一樣的程序,虛擬機並非永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象能夠直接進入Old Generation,無需等到MaxTenuringThreshold中要求的年齡。
這裏的疑問,按照默認的8:1:1設置,一個To Survivor空間佔10%空間,每次Minor GC能保證To Survivor空間夠用嗎?IBM的研究代表,98%的對象都是很快消亡的,大部分的對象在建立後很快就再也不使用。這裏能夠根據GC detail來查看和分析比例設置是否合理。
工做:同時回收年輕代、年老代,按照配置的不一樣算法進行回收。
時機:在Minor GC觸發時,會檢測以前每次晉升到老年代的平均大小是否大於老年代的剩餘空間,若是大於,改成直接進行一次Full GC;若是小於則查看HandlePromotionFailure設置(是否容許擔保,使用Old Gerneration空間擔保),若是容許,那仍然進行Minor GC,若是不容許,則也要改成進行一次Full GC。
取平均值進行比較其實仍然是一種動態機率的手段,也就是說若是某次Minor GC存活後的對象突增,大大高於平均值的話,依然會致使擔保失敗,這樣就只好在失敗後從新進行一次Full GC。
首先判斷對象是否存活,通常有兩種方式:
- 引用計數:每一個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時能夠回收。此方法簡單,沒法解決對象相互循環引用的問題。
- 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的、不可達對象。
在Java語言中,GC Roots包括:
--虛擬機棧中引用的對象。
--方法區中類靜態屬性實體引用的對象。
--方法區中常量引用的對象。
--本地方法棧中JNI引用的對象。
標記-清除算法:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。之因此說它是最基礎的收集算法,是由於後續的收集算法都是基於這種思路並對其缺點進行改進而獲得的。
缺點:一個是效率問題,標記和清除過程的效率都不高;另一個是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使,當程序在之後的運行過程當中須要分配較大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
複製算法:對空間問題的改進,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。
缺點是這種算法的代價是將內存縮小爲原來的一半,持續複製長生存期的對象則致使效率下降。若是不想浪費50%的空間,就須要有額外的空間進行分配擔保(HandlePromotionFailure設置爲true),以應對被使用的內存中全部對象都100%存活的極端狀況,因此在老年代通常不能直接選用這種算法。
標記-壓縮/整理算法:對複製算法在老年代上的改進,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
分代收集算法:把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。
而老年代中,由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」算法來進行回收。
串行收集器是最古老,最穩定以及效率高的收集器,使用中止複製方法,只使用一個線程去串行回收;垃圾收集的過程當中會Stop The World(服務暫停);
參數控制:使用-XX:+UseSerialGC可使用Serial+Serial Old模式運行進行內存回收(這也是虛擬機在Client模式下運行的默認值)
缺點是串行效率較低
ParNew收集器其實就是Serial收集器的多線程版本,使用中止複製方法。新生代並行,其它工做線程暫停。
參數控制:使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集內存;使用-XX:ParallelGCThreads來設置執行內存回收的線程數。
Parallel Scavenge收集器相似ParNew收集器,Parallel收集器更關注CPU吞吐量,即運行用戶代碼的時間/總時間,使用中止複製算法。能夠經過參數來打開自適應調節策略,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量;也能夠經過參數控制GC的時間不大於多少毫秒或者比例。
參數控制:使用-XX:+UseParallelGC開關控制使用Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的默認值);使用-XX:GCTimeRatio來設置用戶執行時間佔總時間的比例,默認99,即1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設置GC的最大停頓時間(這個參數只對Parallel Scavenge有效),用開關參數-XX:+UseAdaptiveSizePolicy能夠進行動態控制,如自動調整Eden/Survivor比例,老年代對象年齡,新生代大小等,這個參數在ParNew下沒有。
Serial Old收集器:老年代收集器,單線程收集器,串行,使用"標記-整理"算法(整理的方法是Sweep(清理)和Compact(壓縮),
Parallel Old 收集器
多線程機制與Parallel Scavenge差不錯,使用標記整理(與Serial Old不一樣,這裏的整理是Summary(彙總)和Compact(壓縮),彙總的意思就是將倖存的對象複製到預先準備好的區域,而不是像Sweep(清理)那樣清理廢棄的對象)算法,在Parallel Old執行時,仍然須要暫停其它線程。Parallel Old在多核計算中頗有用。這個收集器是在JDK 1.6中,與Parallel Scavenge配合有很好的效果。
參數控制: 使用-XX:+UseParallelOldGC開關控制使用Parallel Scavenge +Parallel Old組合收集器進行收集。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。
從名字(包含「Mark Sweep」)上就能夠看出CMS收集器是基於「標記-清除」算法實現的,它的運做過程相對於前面幾種收集器來講要更復雜一些,整個過程分爲6個步驟,其中初始標記、從新標記這兩個步驟仍然須要「Stop The World」,包括:
優勢:併發收集、低停頓
缺點:產生大量空間碎片、併發階段會下降吞吐量
- 堆碎片:CMS收集器並無任何碎片整理的機制,可能出現總的堆大小遠沒有耗盡,但由於沒有足夠連續的空間卻不能分配對象,只能觸發Full GC來解決,形成應用停頓。
參數控制
-XX:+UseConcMarkSweepGC 使用CMS收集器
當使用-XX:+UseConcMarkSweepGC時,-XX:UseParNewGC會自動開啓。所以,若是年輕代的並行GC不想開啓,能夠經過設置-XX:-UseParNewGC來關掉
-XX:+CMSClassUnloadingEnabled相對於並行收集器,CMS收集器默認不會對永久代進行垃圾回收。
-XX:+CMSConcurrentMTEnabled當該標誌被啓用時,併發的CMS階段將以多線程執行(所以,多個GC線程會與全部的應用程序線程並行工做)。該標誌已經默認開啓,若是順序執行更好,這取決於所使用的硬件,多線程執行能夠經過-XX:-CMSConcurremntMTEnabled禁用(注意是-號)。
-XX:+UseCMSCompactAtFullCollection Full GC後,進行一次碎片整理;整理過程是獨佔的,會引發停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設置進行幾回Full GC後,進行一次碎片整理
-XX:ParallelCMSThreads 設定CMS的線程數量(通常狀況約等於可用CPU數量)
-XX:CMSMaxAbortablePrecleanTime:當abortable-preclean階段執行達到這個時間時纔會結束
-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly來決定什麼時間開始垃圾收集;若是設置了-XX:+UseCMSInitiatingOccupancyOnly,那麼只有當old代佔用確實達到了-XX:CMSInitiatingOccupancyFraction參數所設定的比例時纔會觸發cms gc;若是沒有設置-XX:+UseCMSInitiatingOccupancyOnly,那麼系統會根據統計數據自行決定何時觸發cms gc;所以有時會遇到設置了80%比例才cms gc,可是50%時就已經觸發了,就是由於這個參數沒有設置的緣由.
G1 GC是Jdk7的新特性之1、Jdk7+版本均可以自主配置G1做爲JVM GC選項;做爲JVM GC算法的一次重大升級、DK7u後G1已相對穩定、且將來計劃替代CMS。
不一樣於其餘的分代回收算法、G1將堆空間劃分紅了互相獨立的區塊。每塊區域既有可能屬於O區、也有多是Y區,且每類區域空間能夠是不連續的(對比CMS的O區和Y區都必須是連續的)。區別以下:
- G1在壓縮空間方面有優點
就目前而言、CMS仍是默認首選的GC策略、可能在如下場景下G1更適合:
- 大量內存的系統提供一個保證GC低延遲的解決方案,也就是說堆內存在6GB及以上,穩定和可預測的暫停時間小於0.5秒。
G1堆由多個區(region)組成,每一個區大小1M~32M,邏輯上區有3種類型,包括(Eden、Survivor、Old),按分代劃分包括:年輕代(Young Generation)和老年代(Old Generation)。
若是從 ParallelOldGC 或者 CMS收集器遷移到 G1,可能會看到JVM進程佔用更多的內存(a larger JVM process size)。 這在很大程度上與 「accounting」 數據結構有關,如 Remembered Sets 和 Collection Sets。
Remembered Sets 簡稱 RSets。跟蹤指向某個heap區內的對象引用。 堆內存中的每一個區都有一個 RSet。 RSet 使heap區能並行獨立地進行垃圾集合。 RSets的整體影響小於5%。
Collection Sets 簡稱 CSets。收集集合, 在一次GC中將執行垃圾回收的heap區。GC時在CSet中的全部存活數據(live data)都會被轉移(複製/移動)。集合中的heap區能夠是 Eden, survivor, 和/或 old generation。CSets所佔用的JVM內存小於1%。
Young GC是stop-the-world活動,會致使整個應用線程的中止。其過程以下:
- 初始化標記(stop_the_world事件):這是一個stop_the_world的過程,是隨着年輕代GC作的,標記survivor區域(根區域),這些區域可能含有對老年代對象的引用。
-XX:+UseG1GC使用G1 GC
-XX:MaxGCPauseMillis=n設置一個暫停時間指望目標,這是一個軟目標,JVM會近可能的保證這個目標
-XX:InitiatingHeapOccupancyPercent=n內存佔用達到整個堆百分之多少的時候開啓一個GC週期,G1 GC會根據整個棧的佔用,而不是某個代的佔用狀況去觸發一個併發GC週期,0表示一直在GC,默認值是45
-XX:NewRatio=n年輕代和老年代大小的比例,默認是2
-XX:SurvivorRatio=n eden和survivor區域空間大小的比例,默認是8
-XX:MaxTenuringThreshold=n晉升的閾值,默認是15(譯者注:一個存活對象經歷多少次GC週期以後晉升到老年代)
-XX:ParallelGCThreads=n GC在並行處理階段試驗多少個線程,默認值和平臺有關。(譯者注:和程序一塊兒跑的時候,使用多少個線程)
-XX:ConcGCThreads=n併發收集的時候使用多少個線程,默認值和平臺有關。(譯者注:stop-the-world的時候,併發處理的時候使用多少個線程)
-XX:G1ReservePercent=n預留多少內存,防止晉升失敗的狀況,默認值是10
-XX:G1HeapRegionSize=n G1 GC的堆內存會分割成均勻大小的區域,這個值設置每一個劃分區域的大小,這個值的默認值是根據堆的大小決定的。最小值是1Mb,最大值是32Mb
轉載請註明出處,百度搜「成金之路」。
./jstat -gc pid 3s對pid GC每隔3s進行監控
另外,jstack -l long listings,會打印出額外的鎖信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況。
--
內存泄漏OOM,一般作法:
方法1. 首先配置JVM啓動參數,讓JVM在遇到OutOfMemoryError時自動生成Dump文件-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path
方法2. 使用上面第3步進行進行分析;
方法3. 使用eclipse的MAT分析工具對dump文件進行分析
- 發現問題
通常結合JMX分析,分析遠程tomcat狀態
修改遠程tomcat的catalina.sh配置文件,在其中增長(不走權限校驗。只是打開jmx端口):
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.122.128 -Dcom.sun.management.jmxremote.port=18999 -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"
--
若是鏈接的是公網上的Tomcat,那麼就要注意安全性了,接下來看看使用用戶名和密碼鏈接
JAVA_OPTS='-Xms128m -Xmx256m -XX:MaxPermSize=128m -Djava.rmi.server.hostname=10.10.23.10
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.password.file=/path/to/passwd/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=/path/to/passwd/jmxremote.access'
如下分別編輯jmxremote.password與jmxremote.access兩個文件
jmxremote.password
monitorRole 123456
controlRole 123456789
jmxremote.access
monitorRole readonly
controlRole readwrite
完成這兩個文件後修改jmxremote.password的權限chmod 600 jmxremote.password
本質上是減小Full GC的次數(Minor GC很快基本不會有影響)
若是是頻繁建立對象的應用,能夠適當增長新生代大小。常量較多能夠增長持久代大小。對於單例較多的對象能夠增長老生代大小。好比spring應用中。
GC選擇,在JDK5.0之後,JVM會根據當前系統配置進行判斷。通常執行-Server命令即可以。gc包括三種策略:串行,並行,併發(使用CMS收集器老年代)。
吞吐量大的應用,通常採用並行收集,開啓多個線程,加快gc的速率。
響應速度高的應用,通常採用併發收集,好比應用服務器。
年老代建議配置爲併發收集器,因爲併發收集器不會壓縮和整理磁盤碎片,所以建議配置:
-XX:+UseConcMarkSweepGC #併發收集年老代
-XX:CMSInitiatingOccupancyFraction=80 # 表示年老代空間到80%時就開始執行CMS
-XX:+UseCMSInitiatingOccupancyOnly #是閥值生效
-XX:+UseCMSCompactAtFullCollection#打開對年老代的壓縮。可能會影響性能,可是能夠消除內存碎片。
-XX:CMSFullGCsBeforeCompaction=10 # 因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此參數設置運行次FullGC之後對內存空間進行壓縮、整理。
若是以爲不錯,還請不吝推薦,您的承認是我分享的動力,同時也但願幫助更多的人走上本身「成金之路」。謝謝!轉載請註明出處,百度搜「成金之路」。