JVM原理及調優


一、JVM空間說明

在JDK1.7及以前,HotSpot虛擬機將java類信息、常量池、靜態變量、即時編譯器編譯后的代碼等數據,存儲在Perm(永久帶)里(對於其他虛擬機如BEA JRockit、IBM J9等是不存在永久帶概念的),類的元數據和靜態變量在類加載的時候被分配到Perm里,當常量池回收或者類被卸載的時候,垃圾收集器會回收這一部分內存,但效果不太理想。

JDK1.8時,HotSpot虛擬機對JVM模型進行了改造,將類元數據放到了本地內存中,將常量池和靜態變量放到了Java堆里,HotSpot VM 將會為類的元數據明確的分配與釋放本地內存。

在這種架構下,類元數據就突破了-XX:MaxPermSize的限制,所以此配置已經失效,現在可以使用更多的本地內存。這樣一定程度上解決了原來在運行時生成大量的類,從而經常Full GC的問題——如運行時使用反射、代理等。

 

 

 

總結:可以發現最明顯的一個變化是元數據空間從虛擬機轉移到了本地內存。

默認情況下,元數據空間大小僅受限於本地內存, 這意味着以后不會因為永久代大小不夠而拋出OOM異常了。

JDK1.8以前,HotSpot VM將class和類的jar包數據存儲在PermGen里, PermGen大小是固定的,而且項目之間無法公用公有的class,所以很容易碰到OOM異常。

改成MateSpace后, 各個項目會共享同樣的class空間。比如多個項目都引用了apache-common包, 在MateSpace中只會存儲一份的apache-common的class,提高了內存的利用率,垃圾回收更有效。

 

二、GC(Garbage Collection)算法

在JVM里的內存空間,從大的層面划分,主要有新生代空間(Young)和老年代空間(Old),其中Young空間,又被分為2個部分和3個板塊,分別是1個Egen區,和2個Survivor區,看下圖: 

 

 

 

 

 

 OK,下面來具體看下,每部分都是干啥的 
(1)Eden區域是用來存放使用new或者newInstance等方式創建的對象,默認都是存放在Eden區,除非這個對象太大,或者超過了設定的閾值-XX:PretenureSizeThresold,這樣的對象會被直接分配到Old區域。 

(2)2個Survivor(幸存)區,一般稱S0,S1,理論上他們是一樣大的,解釋一下,他們是如何工作的: 
<1> 在不斷創建對象的過程中,Eden區會滿,這時候會開始做Young G也叫Minor GC。過程:找出Eden區中,幸存活着的對象,然后將這些對象,放到S0,或S1區中的其中一個, 假設第一次選擇了S0,它會逐步將活着的對象拷貝到S0區域,但是如果S0區域滿了,剩下活着的對象只能放old區域了,然后將Eden區域 清空,此時S1區域是空的。 

<2> 當第二次Eden區域滿的時候,就將Eden區域中活着的對象 + S0區域中活着的對象,遷移到S1中,如果S1放不下,就會將剩下的部分,放到Old區域中,只是這次對象來源區域增加了S0,最后會將Eden區+S0區域,清空。

<3> 第三次和第四次依次類推,始終保證S0和S1有一個是空的,用來存儲臨時對象,用於交換空間的目的。除了放不下放到old區域的,反復多次沒有被淘汰的對象,將會放入old區域中,默認是15次。具體的交換過程就和上圖中的信息相似。

<4> 直到Old區也快滿的時候,Eden區也快滿的時候,會對整個這一塊內存區域進行一次大清洗(FullGC),騰出內存,為之后的對象創建,程序運行騰地方。

注:

新生代GC(Minor GC):指發生在新生代的垃圾回收動作,因為java對象大多具備朝生夕滅的特征,所以Minor GC發生的特別頻繁, 一般回收速度也很快。 老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,至少會伴隨一次的MinorGC(但非絕對, 在Parallel Scavenge收集器的收集策略里就有直接進行Minor GC的策略選擇過程)。Major GC的速度一般比Minor GC慢。

 

三、JVM參數配置

在jdk1.8以前,生產環境一般有如下配置

-XX:PermSize=512M -XX:MaxPermSize=1024M

表示在JVM里存儲Java類信息,常量池和靜態變量的永久代區域初始大小為512M,最大為1024M。在項目啟動后,這個值是固定的,如果項目class過多,很可能遇到OutOfMemoryError: PermGen異常。

-XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M

MetaspaceSize如果不做配置,通過jinfo查看默認MetaspaceSize大小(約21M),MaxMetaspaceSize很大很大,前面說過MetaSpace只受本地內存大小限制。

jinfo -flag MetaspaceSize 1234 #結果為:-XX:MetaspaceSize=21807104
jinfo -flag MaxMetaspaceSize 1234 #結果為:-XX:MaxMetaspaceSize=18446744073709547520

總結: MetaspaceSize為觸發FullGC的閾值,默認約為21M,如做了配置,最小閾值為自定義配置大小。空間使用達到閾值,觸發FullGC,同時對該值擴大。當然如果元空間實際使用小於閾值,在GC的時候也會對該值縮小。
MaxMetaspaceSize為元空間的最大值,如果設置太小,可能會導致頻繁FullGC,甚至OOM。

 

四. JVM參數配置指南

前面三個部分對JVM進行了整體的了解,接下來是本文的重點。

-XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M -Xms256m -Xmx256m

 

文章看下來上面這段配置的意思很簡單,設置元空間的初始值和最大值,設置堆空間的初始值和最大值。

為什么MetaspaceSize要設置為128M?為什么堆內存初始值Xms設置為256M而不是512M?

按照Java官方的指導

 

 

 

Java堆大小設置,Xms 和 Xmx設置為老年代存活對象的3-4倍,即FullGC之后的老年代內存占用的3-4倍

  • MaxPermSize(元空間)設置為老年代存活對象的1.2-1.5倍。
  • 年輕代Xmn的設置為老年代存活對象的1-1.5倍。
  • 老年代的內存大小設置為老年代存活對象的2-3倍。

可以讓系統運行一段時間后查看系統的各個指標,然后在進行配置。如下用jstat工具查看jvm的情況:

jstat -gc 12345
###
S0C        S1C         S0U         S1U    EC             EU               OC               OU            MC             MU           CCSC      CCSU     YGC   YGCT   FGC FGCT    GCT
13824.0  22528.0  13377.0    0.0     548864.0   535257.2    113152.0     46189.3     73984.0     71119.8     9728.0    9196.2    14       0.259    3      0.287    0.546

OU表示老年代所占用的內存為 46189.3 K(大約45M);那么jvm相應的配置參數應該做如下修改:

堆內存是老年代的3~4倍:45*3=135,45*4=180

元數據空間是老年代的1.2~1.5倍:45*1.2=54,45*1.5=67.5

配置如下:

-XX:MetaspaceSize=64M     -XX:MaxMetaspaceSize=64M        -Xms180m     -Xmx180m


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM