Java虛擬機二:JVM性能調優

堆空間的劃分

Java中的堆是JVM所管理的最大的一塊內存空間,主要用於存放各種類的實列對象。這樣劃分的目的是爲了使JVM能夠更好的管理堆內存中的對象。堆的內存劃分如圖:

JVM å®æ´æ·±å¥è§£æ

Java堆的內存劃分如圖所示,分別爲年輕代、Old Memory(老年代)、Perm(永久代)。其中在Jdk1.8中,永久代被移除,使用MetaSpace代替。其中新生代被細分爲 Eden 和 兩個Survivor 區域,這兩個Survivor區域分別被命名爲 from 和 to ,以示區分。

  • 新生代(1/3堆空間)   老年代(2/3堆空間)
  • Eden :from :to  = 8 :1 :1
  • JVM 每次只會使用Eden 和 其中一塊Survivor 區域來爲對象服務,所以無論什麼時候,總是有一塊Survivor 區域是空閒的,因此新生代實際可用內存空間爲 9/10 的新生代空間。
  • 元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。

GC堆

java中的堆是GC垃圾回收的主要區域。前篇已經分析過垃圾收集需要完成兩件事:檢測出垃圾和回收垃圾,講解了如何判斷垃圾回收,和垃圾回收算法以及常用垃圾收集器。本部分針對垃圾收集過程進行主要說明。

新生代幾乎是所有Java對象出生的地方,即Java對象申請的內存空間以及存放都是在這個地方。java中的大部分對象通常不需要長久存活,具有朝生夕滅的性質。當一個對象被判定爲“死亡”的時候,GC就有責任來回收掉這部分對象的內存空間。新生代是GC收集垃圾的頻繁區域。GC分爲兩種:Minor GC 和 FullGC  。MinorGC 是針對新生代中的垃圾收集動作,所採用的是複製算法。當老年代內存空間不足時,將發生FulGC,採用標記清除/整理算法。Full GC指的是清理整個堆空間,包括年輕代和永久代。垃圾回收結果圖:

ä¸æ带你深å¥ç解JVM

GC過程:

①對象在Eden和一個FromSurvivor區域出生後,當Eden區域滿了,或者新創建的對象大小 > Eden所剩空間,將進行一次MinorGC。②如果對象還存活着,並且能被另一塊ToSurvivor區域容納,則將使用複製算法將這些仍存活的對象複製到另一塊ToSurvivor區域中,然後清理所使用過得Eden和Survivor區域,並且將這些對象的年齡設置爲1.③後續Eden區繼續生成對象,再次發生Minor gc的時候,會將存活的對象複製到From區,並將Eden區和To區清空回收,並將改對象的年齡+1.④部分對象會在From區域和To區域中複製來複制去,每熬過一次MinorGC,就將對象的年齡+1。當對象的年齡達到某個值時(默認15),最終如果還存活,就存入老年代。

注意:

  • 對於一些較大的對象,(即需要分配一塊比較大的連續的內存空間)則直接進入到老年代。
  • FullGc 發生的次數不會有MinorGc那麼頻繁,並且做一次FullGc要比進行一次MinorGc的時間更長。
  • MinorGc拷貝到老年代空間不足和大對象老年代空間也不足 觸發FullGC
  • Perm永久代空間不足會觸發Full GC,可以讓CMS清理永久代的空間。

發現虛擬機頻繁full GC時應該怎麼辦:可以用命令查看觸發GC的原因是什麼 jstat –gccause 進程id

什麼是空間分配擔保策略?

JVM在發生Minor GC之前,虛擬機會檢查老年代最大可用的連續空間是否大於新生代所有對象的總空間,如果大於,則此次Minor GC是安全的如果小於,則虛擬機會查看HandlePromotionFailure設置項的值是否允許擔保失敗。如果HandlePromotionFailure=true,那麼會繼續檢查老年代最大可用連續空間是否大於歷次晉升到老年代的對象的平均大小,如果大於則嘗試進行一次Minor GC,但這次Minor GC依然是有風險的;如果小於或者HandlePromotionFailure=false,則改爲進行一次Full GC。

動態年齡判斷

根據對象年齡有另外一個策略也會讓對象進入老年代,不用等待15次GC之後進入老年代,他的大致規則就是,假如當前放對象到Survivor區域,一批對象的總大小大於這塊Survivor內存的50%,那麼大於這批對象年齡的對象,就可以直接進入老年代了。

GC時爲什麼要停頓所有Java線程?

設置STW機制,因爲GC先進行可達性分析。可達性分析是判斷GC Root對象到其他對象是否可達,假如分析過程中對象的引用關係在不斷變化,分析結果的準確性就無法得到保證。

JVM參數設置

-Xms 堆初始值 如:-Xms256m

-Xmx

堆最大可用值  如:-Xms512m
-Xmn 新生代堆最大可用值, 通常爲Xmx1/3
-Xss JDK1.5+ 每個線程堆棧大小爲1M 
-XX:NewRatio

配置新生代與老年代佔比 1:2   如:-XX:NewRatio =2 則 新生代佔用整個堆空間1/3,老年代2/3

-XX:SurvivorRatio

新生代中eden空間和from/to空間的比例 默認值爲8.

即Eden佔用新生代8/10  from/to 1/10

-XX:PermSize 永久代的初始化大小
-XX:MaxPermSize 永久代的最大值
-XX:+PrintGCDetails 打印GC信息
-XX:+HeapDumpOnOutOfMemoryError 讓虛擬機在發生內存溢出時,Dump出當前的內存堆轉存儲快照,以便分析用。

 

 

 

 

 

 

 

 

 

 

 

 

使用示例:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

當前堆最大內存 20M,初始化堆內存 20M,新生代最大可用內存 1M,Eden 區域和 form、to 區域的比例是 2:1:1,打印 GC 日誌,使用串行回收

內存溢出與內存泄漏區別

內存溢出:申請空間超出系統能夠提供的空間大小     內存泄露:內存泄露是指程序中間動態分配了內存,但在程序結束時沒有釋放這部分內存,從而造成那部分內存不可用的情況,最終導致內存溢出

JVM性能調優

調優的最終目的都是爲了令應用程序使用最小的硬件消耗來承載更大的吞吐,JVM也不列外,JVM性能調優的目的:減少Full GC的次數,提高JVM性能。

1、性能定義

要查找和評估器性能瓶頸,首先要知道性能定義,對於jvm調優來說,我們需要知道以下三個定義屬性,依作爲評估基礎:這三個屬性中,其中一個任何一個屬性性能的提高,幾乎都是以另外一個或者兩個屬性性能的損失作代價,不可兼得。

  • 吞吐量(性能):重要指標之一,是指不考慮垃圾收集引起的停頓時間或內存消耗,垃圾收集器能支撐應用達到的最高性能指標。
  • 延遲(停頓時間):其度量標準是縮短由於垃圾啊收集引起的停頓時間或者完全消除因垃圾收集所引起的停頓,避免應用運行時發生抖動。
  • 內存佔用:垃圾收集器流暢運行所需要 的內存數量。

2、性能調優原則

在調優過程中,我們應該謹記以下3個原則,以便幫助我們更輕鬆的完成垃圾收集的調優,從而達到應用程序的性能要求。

① MinorGC回收原則: 每次minor GC 都要儘可能多的收集垃圾對象。以減少應用程序發生Full GC的頻率。

②GC內存最大化原則:處理吞吐量和延遲問題時候,垃圾處理器能使用的內存越大,垃圾收集的效果越好,應用程序也會越來越流暢。

③ GC調優3選2原則: 在性能屬性裏面,吞吐量、延遲、內存佔用,我們只能選擇其中兩個進行調優,不可三者兼得。

JVM性能調優方式:系統估算法和GC日誌分析法

一、系統估算法以實際案列進行說明:億級流量電商系統JVM參數設置優化

以標準的4核8G機器爲例說明,首先系統預留4G,其他4G按如下規則分配 :

  • 堆內存:3g
  • 新生代:1.5g
  • 新生代Eden區:1228m
  • 新生代Survivor區:153m
  • 方法區:256m
  • 虛擬機棧:1m/thread

設置參數:-Xms3072m   -Xmx3072m  -Xmn1536m  -Xss=1m  -XX:PermSize=256m  -XX:MaxPermSize=256m   -XX:HandlePromotionFailure -XX:SurvivorRatio=8

é¢è¯å®ï¼è¯´ä¸ä¸ä½ ä»¬çº¿ä¸JVMæ¯æä¹ä¼åçï¼

1、估算系統每秒佔用內存數量

在優化JVM之前,要先估算要系統每秒佔用的內存數量,如有個億級流量電商系統,日活用戶200萬,付費轉化率10%,那麼每日下單量在20w左右,這些單正常在三四個小時內產生,平均每秒幾十單。在大促活動中這些單會在幾分鐘內產生,假如每秒處理2400單,部署在三臺服務器上,那麼每臺訂單服務每秒大概會有800個請求,然後粗略的估算下每個請求佔用多少內存,計算出每秒要花費多少內存。

假設是每秒800個請求,每個訂單對象鎖定1k,首先下單還設計其他對象,如庫存、優惠券、積分等,現將請求對象放大10倍。然後,下單過程還涉及其他查詢等操作在放大10倍,最後確定每個請求需要分配100k的空間,那1秒需要分配大約80m的內存。1秒後變爲垃圾對象(1秒內方法執行完)

2、計算下多長時間觸發一次Minor GC

按照之前的估算1秒需要分配大約80m的內存的話,Eden區的空間是1228m那平均每15秒就要執行一次Minor GC。

3、檢查下Survivor區是否足夠

按照上面的模型,每15秒就要執行一次Minor GC,GC執行期間並不能回收掉所有的新生代中的對象,那每次GC執行期間還會剩下大約80m無法回收的對象會進入Survivor區,但是別忘記JVM有動態年齡判斷機制,80M大於Survivor的50%,直接進入老年代。老年代分配1536m,大概需要4.8分鐘填滿,此時就會發生FullGc。很明顯FullGC頻率就高,JVM性能調優的目的就是爲了減少FullGC頻率。

解決:從上面分析來看,這樣設置下來Survivor的空間明顯小了一點,所以將新生代設置2048m,才能避免觸發動態年齡判斷,這樣就可以避免頻繁FullGc。

二、GC日誌分析法

想要進行JVM性能調優,首先要了解現有JVM運行情況,我們可以通過觀察應用的GC日誌,特別是Full GC 日誌,對現有系統情況進行了解。

GC日誌指令: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>

GC日誌是收集調優所需信息的最好途徑,即便是在生產環境,也可以開啓GC日誌來定位問題,開啓GC日誌對性能的影響極小,卻可以提供豐富數據。

必須得有FullGC 日誌,如果沒有的話,可以採用監控工具強制調用一次,或者採用以下命令,亦可以觸發

jmap -histo:live pid

在穩定階段觸發了FullGC我們一般會拿到如下信息:

å¦ä½åççè§åjvmæ§è½è°ä¼

從以上gc日誌中,我們大概可以分析到,在發生fullGC之時,整個應用的堆佔用以及GC時間,當然了,爲了更加精確,應該多收集幾次,獲取一個平均值。或者是採用耗時最長的一次FullGC來進行估算。

在上圖中,fullGC之後,老年代空間佔用在93168kb(約93MB),我們以此定爲老年代空間的活躍數據。

其他堆空間的分配,基於以下規則來進行

å¦ä½åççè§åjvmæ§è½è°ä¼

基於以上規則和上圖中的FullGC信息,我們現在可以規劃的該應用堆空間爲:

java 堆空間: 373Mb (=老年代空間93168kb*4)

新生代空間:140Mb(=老年代空間93168kb*1.5)

永久代空間:5Mb(=永久代空間3135kb*1.5)

老年代空間: 233Mb=堆空間-新生代看空間=373Mb-140Mb

對應的應用啓動參數應該爲:

java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m

提升吞吐量的性能調優的目標就是就是儘可能避免或者很少發生FullGC ,儘量在MinorGC 階段回收更多的對象,避免對象提升過快到老年代。

 

文章參考:

https://www.toutiao.com/a6659169929876472331/

https://www.toutiao.com/a6758453803709628942/

https://www.toutiao.com/a6777892950434120204/

https://www.toutiao.com/a6750187070641144324/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章