堆分配參數:html
-XX:+PrintGC 使用該參數,虛擬機啓動後,只要遇到GC就會打印日誌;java
-XX:+UseSerialGC 配置串行回收器;算法
-XX:+PrintGCDeltails 能夠查看詳細信息,包括各個區的狀況安全
-Xms:設置Java程序啓動時初始堆的大小(主要參數)性能優化
-Xmx:設置Java程序能得到的最大堆大小(主要參數)數據結構
新生代的配置:多線程
-Xmn: 能夠設置新生代的大小,設置一個比較大的新生代會減小老年代的大小,這個設置對系統性能以及GC行爲有很大的影響,新生代大小通常會設置整個堆空間的1/3到1/4左右。併發
備註:在實際工做中,能夠直接將初始的堆大小與最大堆大小設置相等,這樣的好處是能夠減小程序運行時的垃圾回收次數,從而提升性能。佈局
常見異常:性能
java.lang.OutOfMemoryError.Java heap spacess---heap
JVM中若是98%的時間是用於GC且可用的Heap size不足2%的時候將拋出此異常信息。
-Xms -Xmx
java.lang.OutOfMemoryError.PermGen space ---no heap
-XX:PermSize
-XX:MaxPermSize
StrackOverflowError
Java虛擬機在運行時,調用方法時,都須要建立棧幀,當棧的空間不夠時就會產生StrackOverflowError
-Xss
JVM中的參數:
以-X開頭的都是非標準的(這些參數並不保證在全部的JVM上都被實現)。
-Xmx
-Xmn
-Xms
以-XX開頭的都是不穩定的而且不推薦在生產環境中使用,這些參數的改動也不會發布通知。
-XX:Permsize
-XX:MaxPermsize
JVM選項的說明:
布爾型參數選項:-XX+表示打開,-XX-表示關閉。(-XX.+PrintGCDetails)
數字型參數選項經過-XX=設定。
字符型參數選項經過-XX=設定,一般用來指定一個文件、路徑或者一個命令列表
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n 設置年輕代的大小
-XX:NewRatio=n 設置年輕代和老年代的比值。如3則表示年輕代和老年代的比值爲1:3
-XX:SurvivorRatio=n 年輕代中Eden與兩個Survivor區的比值。如爲3,則表示Eden:Survivor=3:2。一個Survivor佔整個年輕代的1/5.
-XX:MaxPermSize=n 設置永久代的大小
JVM GC
串行回收器(Serial Collector)
並行回收器(Parallel Collector)
並行合併回收器(Parallel Compacting Collection)
併發標記清除回收器(Concurrent Mark-Sweep Collector)應用普遍
G1垃圾收集器(Jdk7+)將來的主流
並行算法是用多線程進行垃圾回收,回收期間會暫停程序的執行,而併發算法,也是多線程回收,但期間不中止應用執行。因此,併發算法適用於交互性高的一些程序。通過觀察,併發算法會減小年輕代的大小,其實就是使用了一個大的年老代,這反過來跟並行算法相比吞吐量相對較低。
JVM垃圾回收時間:
當年輕代內存滿時,會引起一次普通GC,該GC僅回收年輕代。須要強調的是,年輕代盡是指Eden代滿,Survivor滿不會引起GC
當老年代滿時會引起Full GC,Full GC將會同時回收年輕代、年老代
當永久代滿時也會引起Full GC,會致使Class、Method元信息的卸載
常見問題:
Q:爲何崩潰前垃圾回收的時間愈來愈長?
A:根據內存模型和垃圾回收算法,垃圾回收分兩部分:內存標記、清除(複製),標記部分只要內存大小固定時間是不變的,變的是複製部分,由於每次垃圾回收都有一些回收不掉的內存,因此增長了複製量,致使時間延長。因此,垃圾回收的時間也能夠做爲判斷內存泄漏的依據
Q:爲何Full GC的次數愈來愈多?
A:由於內存的積累,逐漸耗盡了年老代的內存,致使新對象分配沒有更多的空間,從而致使頻繁的垃圾回收
Q:爲何年老代佔用的內存愈來愈大?
A:由於年輕代的內存沒法被回收,愈來愈多地被Copy到年老代
Q:什麼是串行回收和並行回收?
A:串行回收是指在同一時間段內只容許一件事情發生,即當多個CPU可用時,也只能有一個CPU用於執行垃圾回收操做,而且在執行垃圾回收時,程序中的工做線程將會被暫停。當垃圾回收工做完成後纔會恢復以前被暫停的工做線程,這就是串行回收。
並行回收是指能夠運用多個CPU同時執行垃圾回收,所以提高了應用程序的吞吐量,不過並行垃圾回收仍然使用了「Stop-the-World」機制和複製算法。
Q:什麼是併發和「Stop-the-World」機制?
當經過「Stop-the-World」機制回收垃圾時,垃圾收集器會在內存回收的過程當中暫停程序中的全部工做線程,直至完成內存回收工做後纔會恢復以前被暫停的工做線程。
併發回收是指在同一時間段內,應用程序的工做線程和垃圾回收線程將會同時運行或者交叉運行。
Q:什麼是快速分配策略?
A:基於線程安全的考慮,若是一個類在分配內存以前已經成功完成裝載步驟以後,JVM就會優先選擇在TLAB(Thread Local Allocation本地線程分配緩衝區)中爲對象實例分配內存空間,TLAB在Java堆區中是一塊線程私有的區域,包含在Eden空間內,除了能夠避免一系列的非線程安全問題外,同時還可以提高內存分配的吞吐量,所以咱們能夠將這種內存分配方式稱之爲快速分配策略。
Q:什麼是逃逸分析和棧上分配?
A:Java堆區已經再也不是對象分配內存的惟一選擇,若是但願下降GC的回收平率和提高GC的回收效率,那麼則可使用堆外存儲技術,目前最多見的堆外存儲技術就是利用逃逸分析技術篩選出未發生逃逸的對象,而後避開堆區而直接選擇在棧幀中分配內存空間。逃逸分析是JVM在執行性能優化以前的一種分析技術,它的具體目標就是分析出對象的做用域。即當一個對象被定義在方法體內部以後,它的受訪權限僅限於方法體內,一旦其引用被外部成員引用後,這個對象就發生了逃逸。反之若是定義在方法體內部的對象並無被任何外部成員引用時,JVM就會爲其在棧幀中分配內存空間。
HeapOutOfMemory
當堆上分配的對象大於指定堆的最大值時,拋出該錯。
可使用-XX:+HeapDumpOnOutOfMemoryError 查看內存快照進行分析
MethodArea OutOfMemory
方法區內存不足,存放類信息,常量,靜態變量,即時編譯後的代碼,檢查這幾個信息是否有異常 大多的緣由是由於動態產生過多的類。
ConstantPool OutOfMemory
常量池溢出,查看是否intern使用不當
DirectMemory OutOfMemory
本機直接內存溢出,容量可經過-XX:MaxDirectMemorySize指定,若是不指定,默認和堆最大值相同。這個溢出發生在系統進行直接內存分配。例如:unsafe.allocateMemory()
特徵爲:OOM後發現Dump問價你很小,程序中直接或間接使用了NIO
Stack OutOfMemory
擴展棧時沒法獲取足夠的內存空間,在建立線程時
解決方法之一:減小最大堆
Stack OverFlow
棧深度大於虛擬機所容許的深度,常常是因爲死循環的遞歸調用
第三章:垃圾收集器和內存分配策略
程序計數器、虛擬機棧、本地方法棧三個區域隨線程而生,隨線程而滅;棧中的棧幀隨着方法的進入和退出而有條不紊的執行着出棧和入棧操做。每個棧幀中分配多少內存基本是在類結構肯定下來時就已知的,所以這三個區域的內存分配和回收都具備肯定性,在這三個區域內不須要過多考慮回收的問題,由於方法結束或者線程結束時,內存天然就跟着回收了。而java堆和方法區不同,一個接口中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也不同,咱們只有在程序運行時才能知道會建立哪些對象,這部份內存的分配和回收是動態的,垃圾收集器所關注的是這部份內存。
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效計數器值就減1;任什麼時候刻計數器都爲0的對象就是不可能再被使用的。Java語言並無選用引用計數器算法來管理內存,其中最主要的緣由就是它很難解決對象之間相互循環引用的問題。
Java使用根搜索算法(GC Root Tracing)判斷對象是否存活。該算法的基本思路是:經過一系列的名爲「GC Root」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講就是從GC Roots到這個對象不可達)時,則證實此對象是不可引用的,因此它們將會被斷定爲可回收對象。在Java語言中可做爲GC Roots的對象包括下面幾種:
虛擬機棧(棧幀中的本地變量表)中的引用的對象;
方法區中的類靜態屬性引用的對象;
運行時常量池中的對象引用;
方法區中的常量引用的對象;
本地方法棧中JNI(即通常所說的native方法)引用的對象。
對象引用:不管是經過引用計數器算法判斷對象的引用數量,仍是經過根搜索算法判斷對象的引用鏈是否可達,判斷對象是否存活都與「引用」有關。JDK1.2以後,Java將引用分爲四種:強引用,軟引用,弱引用,虛引用。這四種引用強度依次逐漸減弱。
永久代(方法區)的垃圾收集主要回收兩部份內容:廢棄常量和無用的類。回收廢棄常量與回收Java堆中的對象很是類似。以常量池中的字面量的回收爲例,假如一個字符串「abc」已經進入了常量池中,可是當前系統沒有任何一個String對象叫作「abc」的,也沒有其餘地方引用了這個字面量,若是這個時候發生內存回收,並且必要的話,這個「abc」常量就會被系統「請」出常量池。常量池中的其餘類(接口)、方法、字段的符號引用也與此相似。判斷一個類是不是「無用的類」須要知足下面三個條件:
該類全部的實例都已經被回收,即Java堆中不存在該類的任何實例;
加載該類的ClassLoader已經被回收;
該類對應的java.lang.Class對象沒有在任何對方被引用,沒法在任何地方經過反射訪問該類的方法。
這是最基礎的收集算法。分爲兩個階段,標記和清除。首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。
缺點:效率低下;
空間問題,標記清除以後會產生大量不連續的內存碎片。
該算法將內存按容量劃分爲大小相等的兩塊區域,每次只使用其中的一塊。當一塊內存用完了,就將其中還存活的對象複製到另外一塊區域上,而後再將已經使用過的內存區域一次性清理掉。解決了內存碎片的問題。
說明:如今的商業虛擬機都是採用這種收集算法來回收新生代,IBM的專門研究代表,新生代中的對象98%是朝生夕死的,全部並不須要按照1:1的比例來劃份內存空間,而是將內存劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時候,將Eden和Survivor中還存活的對象一次性複製到另一塊Survivor空間上,最後清理掉Eden和剛剛用過的Survivor的空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1:1。
缺點:在對象存活率較高時須要執行較多的複製操做,效率將會變低,老年代不能使用這種算法。
根據老年代的特色,有人提出了「標記-整理」算法。其中標記過程和「標記-清除」算法同樣,可是後續步驟不是直接對可回收的對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
當前商業虛擬機的垃圾回收都是採用「分代收集」算法,根據對象的存活週期的不一樣將內存劃分爲幾塊。通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適合的收集算法。在新生代中,每次垃圾收集時都會發現有大量對象死去,只有少許對象存活,那就選擇複製算法;而老年代中由於對象存活率較高,沒有額外空間對它進行分配擔保,就必須採用「標記-清除」或者「標記-整理」算法來進行回收。
Serial收集器是最基本、歷史最悠久的收集器。該收集器是一個單線程的收集器,即在進行垃圾收集時候,必須暫停其餘全部的工做線程,直到它收集結束。到目前爲止,它依然是虛擬機運行在Client模式下的默認新生代收集器。優勢是簡單而高效。目前停頓時間能夠控制在幾十毫秒最多一百多毫秒之內。
G1(Garbage first)收集器是當前收集器技術發展的最前沿成果。G1收集器是垃圾收集器理論進一步發展的產物,它與CMS收集器相比有兩個顯著的改進:一是G1收集器是基於「標記-整理」算法實現的收集器;二是它能夠很是精準地控制停頓,既能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒。
G1收集器能夠實如今基本不犧牲吞吐量的前提下完成低停頓的內存回收,這是因爲它可以極力的避免全區域的垃圾收集,G1收集器將整個Java堆(包括新生代、老年代)劃分爲多個大小固定的獨立區域(Region),而且跟蹤這些區域裏面的垃圾堆積程度,在後臺維護一個優先列表,每次根據容許的收集時間,優先回收垃圾最多的區域(這就是G1名稱的由來)。
垃圾收集器比較
垃圾收集器 |
算法 |
方式 |
堆區域 |
機制 |
Serial收集器 |
複製算法 |
串行 |
新生代 |
Stop-the-World |
Serial Old收集器 |
標記-壓縮算法 |
串行 |
老年代 |
Stop-the-World |
ParNew收集器 |
複製算法 |
並行 |
新生代 |
Stop-the-World |
Parallel收集器 |
複製算法 |
並行 |
新生代 |
Stop-the-World |
Parallel Old收集 |
標記-壓縮算法 |
並行 |
老年代 |
Stop-the-World |
CMS收集器 |
標記-清除算法 |
並行 |
老年代 |
Stop-the-World/併發 |
G1收集器 |
|
|
整個堆區 |
|
內存選項配置
選項 |
描述 |
備註 |
-Xms |
設置Java堆區的初始內存 |
當可用的Java堆區內存小於40%時,JVM就會將內存調整到選項-Xmx所容許的最大值 |
-Xmx |
設置Java堆區的最大內存 |
當可用的Java堆區內存大於70%時,JVM就會將內存調整到選項-Xms所指定的初始值 |
-Xmn |
設置新生代(YoungGen)的內存 |
-Xmn的內存大小爲Eden+2個Surivivor空間的值,官方建議配置爲整個堆的3/8 |
-XX:NewSize |
設置新生代(YoungGen)的初始內存 |
和選項-Xmn等價,可是推薦使用-Xmn,至關於一次性設定了NewSize/Max-NewSize的內存大小 |
-XX:MaxNewSize |
設置新生代(YoungGen)的最大內存 |
|
-XX:NewRatio |
新生代(Eden+2個Surivivor空間)與老年代的比值,不包括永久代 |
選項-XX:NewRatio=4時,表示新生代與老年代所佔的比值爲1:4。若是已經設置了選項-Xmn,則無需設置該選項 |
-XX:PermSize |
設置方法區的初始內存 |
|
-XX:MaxPermSize |
設置方法區的最大內存 |
|
-XX:SurivivorRatio |
Eden空間與2個Surivivor空間的比值大小 |
Eden空間和另外2個Surivivor空間缺省所佔的比值爲8:1 |
-XX:TLABWasteTargetPercent |
設置TLAB空間所佔用Eden空間的百分比大小 |
|
GC組合配置
GC組合 |
Minor GC |
Full GC |
描述 |
-XX:+UserSerialGC |
Serial收集器串行回收 |
Serial Old收集器串行回收 |
選項-XX:UseSerialGC 能夠手動指定使用Serial收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParNewGC |
ParNew收集器並行回收 |
Serial Old收集器串行回收 |
選項-XX:UseParNewGC能夠手動指定使用ParNew收集器+Seral Old收集器組合執行內存回收 |
-XX:+UseParallelGC |
Parallel收集器並行回收 |
Serial Old收集器串行回收 |
經過-XX:+UseParallelGC能夠手動指定使用Parallel收集器和Serial Old收集器組合執行內存回收 |
-XX:+UseParallelOldGC |
Parallel收集器並行回收 |
Parallel Old收集器並行回收 |
經過-XX:+UseParallelOldGC能夠手動指定使用Parallel收集器和Parallel Old收集器組合執行內存回收 |
-XX:+UseConcMarkSweepGC |
ParNew收集器並行回收 |
缺省使用CMS收集器併發執行回收,備用採用Serial Old收集器串行回收 |
使用-XX:+UseConcMarkSweepGC能夠手動指定使用ParNew收集器+CMS收集器+Serial Old收集器組合執行內存回收。優先使用ParNew+CMS組合,當出現ConcurrentNode Failure或者Promotion Failed時,則採用ParNew+Serial Old組合。 |
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC |
Serial 收集器串行回收 |
||
-XX:+UseG1GC |
G1收集器併發、並行的內存回收 |
|
第七章:虛擬機類加載機制
虛擬機把描述類的數據從Class文件加載到內存中,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括了:加載、校驗、準備、解析、初始化、使用和卸載七個階段。其中校驗/驗證、準備和解析三個部分統稱爲鏈接。
虛擬機規範嚴格規定了有且只有四種狀況必須當即對類進行初始化(而加載、校驗、準備和解析天然須要在初始化以前開始):
1)遇到new、getstatic、putstatic、或者這4條字節碼指令時,invokestatic若是類沒有進行初始化,則須要先觸發其初始化,生成這4條指令最多見的Java代碼場景是:使用new關鍵字實例化對象的時候、讀取或者設置一個類的靜態字段(被final修飾、已經在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候;
2)使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化;
3)當初始化一個子類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化;
4)當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main方法的那個類),虛擬機會先初始化這個主類。
對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中定義的靜態字段,只會觸發其父類的初始化而不會觸發子類的初始化。
在加載階段,虛擬機須要完成如下三件事情:
經過一個類的全限定名來獲取定義此類的二進制字節流;
將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;
在Java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口。
加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中。而後在java堆中實例化一個java.lang.Class對象,這個對象將做爲程序訪問方法區中的這些類型數據的外部接口,加載階段和鏈接階段的部份內容(如一部分字節碼文件格式驗證動做)是交叉進行的,加載階段還沒有完成,鏈接階段可能已經開始,可是這些夾在加載階段之中的動做,仍然屬於鏈接階段的內容,這兩個階段的開始時間仍然保持着固定的前後順序。
驗證是鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。
虛擬機驗證過程的四個階段:
文件格式驗證;
元數據驗證;
字節碼驗證;
符號引用驗證。
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配。這個階段中有兩個容易產生混淆的概念,首先是這時候進行內存分配的僅包含類變量(被static修飾的變量),而不包含實例變量,實例變量將會在對象實例化時隨着對象一塊兒分配在Java堆中。其次是這裏所說的初始值「一般狀況」下是數據類型的零值,假設一個類變量的定義爲:public static int value=123;那麼變量value在準備階段事後的初始值爲0,而不是123。
解析階段是虛擬機將常量池內的符號引用替換成直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義的定位到目標便可。符號引用與虛擬機實現的內存佈局無關。引用的目標並不必定已經加載到內存中。
直接引用:直接引用能夠是直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存佈局相關的,同一符號引用在不一樣虛擬機實例上翻譯出來的直接引用通常不會相同。
虛擬機設計團隊把類加載階段中的「經過一個類的全限定名來獲取此類的二進制字節流」這個動做放到Java虛擬機外部去實現,以便讓應用程序本身決定如何去獲取所修要的類。實現這個動做的代碼模塊被稱爲「類加載器」。
類加載器雖然只用於實現類的加載動做,但它在Java程序中起到的做用卻遠遠不限於類加載階段。對於任意一個類,都須要由加載它的類加載器和這個類自己一同肯定其在Java虛擬機中的惟一性。
絕大部分Java程序都會使用到如下三種系統提供的類加載器:
1)啓動類加載器(BootStrap ClassLoader):在HotSpot虛擬機中這個類加載器由C++語言實現,是虛擬機自身的一部分。它負責將存放在<JAVA_HOME>\lib目錄中的,而且是虛擬機標識的(僅按照文件名識別,如rt.jar,名稱不符合的類庫即便放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。啓動類加載器沒法被Java程序直接引用。
2)擴展類加載器(Extention ClassLoader):這個加載器有sun.misc.Launcher$ExtClassLoader實現。它負責加載<JAVA_HOME>/lib/ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。
3)應用程序類加載器(Application ClassLoader):這個類加載器由sun.misc.Launcher$AppClassLoader來實現。因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫。若是應用程序沒有指定自定義的類加載器,通常狀況下這個就是程序中默認的類加載器。
咱們的應用程序都是由這三個類加載器互相配合進行加載的。
雙親委派模型的工做過程:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委託給父類加載器去完成。每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳遞到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需加載的類)時,子加載器纔會本身嘗試去加載。
當一個Java程序響應很慢時如何查找問題