JVM基礎知識和調優基礎原理
JVM原理
什麼是jvm
java虛擬機器,就是個應用程式,工作在使用者態
詳解
JVM是按照執行時資料的儲存結構來劃分記憶體結構的,JVM在執行java程式時,將它們劃分成幾種不同格式的資料,分別儲存在不同的區域,這些資料統一稱為執行時資料。執行時資料包括java程式本身的資料資訊和JVM執行java需要的額外資料資訊。
JVM執行時資料區
- 程式計數器–執行緒私有
行號,指示程式執行到哪個位置
- Java虛擬機器棧–執行緒私有
- 本地方法棧–執行緒私有
作業系統底層的方法
- Java堆–執行緒公用
JVM記憶體分配
棧記憶體分配 -xss 預設1M
儲存引數、區域性變數、中間計算過程和其他資料。退出方法的時候,修改棧頂指標就可以把棧幀中的內容銷燬。
- 棧的優點:存取速度比堆塊,僅次於暫存器,棧資料可以共享。
- 棧的缺點:存在棧中的資料大小、生存期是在編譯時就確定的,導致其缺乏靈活性。
stack out of memory
一般情況下不會溢位,方法不會寫那麼大堆記憶體分配:
儲存物件 - 堆的優點:動態分配記憶體大小,生存期不必事先告訴編譯器,它是在執行期動態分配的,垃圾回收器會自動收走不再使用的空間區域。
- 堆的缺點:執行時動態分配記憶體,在分配和銷燬時都要佔用時間,因此堆的效率較低。
堆結構:
- Young:E區,S0,S1
- Old:
- Permanent:
JVM堆配置引數:
概述
- -Xms 初始堆大小
預設實體記憶體的1/64(<1GB)
- -Xmx最大堆大小
預設實體記憶體的1/4(<1GB),實際中建議不大於4GB
- 一般建議設定 -Xms=-Xmx
好處是避免每次在gc後,調整堆的大小,減少系統記憶體分配開銷
- 整個堆大小=年輕代大小+年老代大小+持久代大小
新生代:
- 新生代=1個eden區+2個survivor區
- -Xmn 年輕代大小(1.4 or later)
-XX:NewSize,-XX:MaxNewSize(設定年輕代大小,1.4之前)
- -XX:NewRatio
年輕代(包括E區和兩個S區)與年老代的比值(除去持久代)
一般情況下設定了Xms=Xmx並且設定了Xmn的情況下,該引數不需要設定。 - -XX:ServivorRatio
1個S區與E區大小的比值,預設設定為8,則1個S區佔整個年輕代的1/10
- 新生代用來存放JVM剛分配的Java物件
老年代:
- 老年代=整個堆-年輕代大小-持久代大小
- 年輕代中經過垃圾回收沒有回收掉的物件被複制到年老代
- 老年代儲存物件比年輕代年齡大的多,而且不乏大物件(快取)
- 新建的物件也有可能直接進入老年代
- 大物件,可通過啟動引數設定-XX:PretnureSizeThreshold=1024(單位為位元組,預設為0)來代表超過多大時就不在新生代分配,而是直接在老年代分配。
- 大的陣列物件,切陣列中無引用外部物件
- 老年代大小無配置引數
持久代:
- 持久代=整個堆-年輕代大小-老年代大小
- -XX:PermSize -XX:MaxPermSize
設定持久代的大小,一般情況推薦把-XX:PermSize設定成-XX:MaxPermSize的值為相同的值,因為持久代大小的調整也會導致堆記憶體需要觸發fgc。
- 存放Class、Method元資訊,其大小與專案的規模、類、方法的數量有關。一般設定為128M就足夠,設定原則是預留30%的空間。
- 持久代的回收方式
- 常量池中的常量,無用的類資訊,常量的回收很簡單,沒有引用了就可以被回收
- 對於無用的類進行回收,必須保證3點:
- 類的所有例項都已經被回收
- 載入類的ClassLoader已經被回收
- 類物件Class物件沒有被引用(即沒有通過反射引用該類的地方)
JVM記憶體垃圾回收:
垃圾收集演算法:
- 引用計數演算法(瀕臨被拋棄
- 根搜尋演算法:
從GC Roots開始向下搜尋,搜尋所走過的路徑稱為引用鏈。當一個物件到GC Roots沒有任何引用鏈相連時,則證明物件是不可用的。即不可達物件。
在Java語言中,GC Roots包括: - 虛擬機器棧中引用的物件。(大部分被回收的)
- 方法區中靜態屬性實體引用的物件。
- 方法區中常量引用的物件。
- 本地方法棧中JNI引用的物件。
垃圾回收演算法:
複製演算法(Copying)
當空間存活的物件比較少時,極為高效,此演算法用於新生代記憶體回收,從E區回收到S0或S1
標記清除演算法(Mark-Sweep)
產生碎片,適合老年代垃圾回收。
標記整理壓縮演算法(Mark-Compac)
稍慢,適合老年代垃圾回收,解決碎片問題,物件連續,成本更高
名詞解釋:
- 序列回收:gc單執行緒記憶體回收、會暫停所有使用者執行緒,用於client端
- 並行回收:收集是指多個GC執行緒並行工作,但此時使用者執行緒是暫停的
- 併發回收:是指使用者執行緒與GC執行緒同時執行(不一定是並行,可能交替,但總體上是同時執行的),不需要停頓使用者執行緒(其實CMS中使用者執行緒還是需要停頓的,只是非常短,GC執行緒在另一個CPU上執行)
JVM常見的垃圾回收器:
Serial回收器(序列回收器)
是一個單執行緒的收集器,只能使用一個CPU或者一條執行緒去完成垃圾收集,在進行垃圾收集時,必須暫停所有其他工作執行緒,直到收集完成
- -XX:+UseSerialGC來開啟(新生代和老年代都開啟)
- 使用複製演算法(新生代)標記-壓縮演算法(老年代)
- 序列的、獨佔式的垃圾回收器
- 缺點:Stop-The-World
ParNew回收器(並行回收器)
也是獨佔式回收器,在收集過程中,應用程式全部暫停。如果是單CPU上或者併發能力較弱的系統上,還不如序列回收器效能好。
- -XX:+UseParNewGC開啟
- -XX:ParallelGCThreads指定執行緒數,預設最好與CPU數量相當
新生代Parallel Scavenge回收器
吞吐量優先回收器
- 關注CPU吞吐量,即執行使用者程式碼的時間/總時間,適合執行後臺運算
- -XX:+UserParallelGC開啟,這也是在Server模式下的預設值
- -XX:GCTimeRatio
- -XX:MaxGCPauseMillis
老年代ParallelOld回收器
- -XX:+UseParallelOldGC開啟
CMS(併發標記清除)回收器
用的最廣泛,標記和重新標記兩個階段仍然需要停止使用者執行緒,但時間很快
初始標記
併發標記
重新標記
併發清除
- 標記-清除演算法:同時它又是一個使用多執行緒併發回收的垃圾收集器
- -XX:ParallelCMSThreads:手工設定CMS執行緒數量,CMS預設啟動的執行緒數是(ParallelGCThreads+3)/4
- -XX:+UseConcMarkSweepGC開啟
- -XX:CMSInitialtingOccupancyFraction
設定CMS收集器在老年代空間被使用多少後觸發垃圾收集,預設值為68%,僅在CMS收集器時有效,-XX:CMSInitiatingOccupancyFraction=70- -XX:+UseCMSCompactAtFullCollection
由於CMS收集器會產生碎片,此引數設定在垃圾收集器後是否需要一次記憶體碎片整理過程,僅在CMS收集器時有效。- -XX:+CMSFullGCBeforeCompaction
設定CMS收集器在進行若干次垃圾收集後再進行一次記憶體碎片整理過程,通常與UseCMSCompactAtFullCollection引數一起使用- -XX:CMSInitiatingPermOccupancyFraction
設定持久代
GC效能指標
吞吐量
應用花在非GC上的時間百分比
GC負荷
花在GC時間百分比
暫停時間(看GClog)
應用劃在GC stop-the-world的時間
GC頻率
反應速度
從一個物件變成垃圾到這個物件被回收的時間
小結
- 一個互動式的應用要求暫停時間越少越好,然而,一個非互動式的應用,希望GC負荷越低越好
- 一個實時系統對暫停時間和GC負荷要求,都是越低越好
記憶體容量配置原則
年輕代大小選擇
- 響應時間優先的應用
儘可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇),在此情況下,年輕代收集發生的頻率也是最小的,同時減少到達老年代的物件
- 吞吐量優先的應用
儘可能設定大,可能到達Gbit的程度,因為對響應時間沒有要求,垃圾收集可以並行進行,一般適合8CPU以上的應用
避免設定過小,當新生代設定過小時會導致 - YGC次數更加頻繁
- 可能導致YGC物件直接進入老年代,如果此時老年代滿了,會觸發FGC
老年代大小選擇
- 響應時間優先的應用
使用併發垃圾收集器(CMS)設定小了會造成記憶體碎片,高回收頻率以及應用暫停而使用傳統的標記清除方式,如果堆大了,需要較長的收集時間,最優化的方案,一般參考以下資料獲得:
併發垃圾收集資訊、持久代併發收集次數、傳統GC資訊、花在年輕代和年老代回收上的時間比例 - 吞吐量優先的應用
一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以儘可能回收掉大部分短期物件,減少中期的物件,而年老代儘量存放長期存活物件。
java排障
使用jps獲取java程序的pid
1
|
# jps -lvm
|
匯出CPU佔用高程序的執行緒棧
1
|
jstack `$pid` >> java.txt
|
檢視對應程序的哪個執行緒佔用CPU過高
1
|
# top -H -p 22056
|
將執行緒的pid轉換為16進位制
1
|
# echo "obase=16;`$pid`"|bc
|