天天看點

《從零開始帶你成為JVM實戰高手》 筆記七

一、第五十課 jstat指令

1、指令

jstat -gc [PID] 間隔時間 次數       每隔多少時間列印一次JVM記憶體狀況,總共列印指定次數

二、第五十一課 jmap、jhat指令分析大對象

1、使用jmap

jmap -dump:live,format=b,file=dump.hprof [PID]    将堆記憶體快照放到dump.hprof檔案中,這個是二進制檔案,不能直接打開

2、使用jhat

jhat dump.hprof -port 7000    啟動jhat伺服器,指定端口7000,就可以通過圖形化的方式去分析堆記憶體中的對象分布情況了

三、第五十九課 案例分析,中繼資料區不斷加載類導緻頻繁FULL GC

1、JVM啟動時新加兩個參數,用來追蹤類加載和解除安裝的情況

-XX:TraceClassLoading -XX:TraceClassUnloading
           

 2、設定後發現,輸出的日志中有類似以下的内容

《從零開始帶你成為JVM實戰高手》 筆記七

這個類是Java反射時,通過類似getDeclaredMethod方法産生的。在使用反射時,JVM在反射調用15次後,會動态生成一些軟引用對象(ReflectionData),并把類資訊放到Metadata中(具體邏輯需要研究反射源碼)。軟引用對象在GC是否要被回收是基于如下公式判斷的

clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
           

clock - timestamp:一個軟引用對象多久沒被通路過了

freespace:JVM中的空閑記憶體空間

SoftRefLRUPolicyMSPerMB:每一MB的空閑記憶體空間可以允許SoftReference對象存活多久

3、産生頻繁FULL GC的原始,就是将SoftRefLRUPolicyMSPerMB設定為了0。

一般情況下,軟引用的對象隻有在快發送OOM時才會回收,而設定為0後,軟引用的對象會立刻被young gc回收掉一些,然後反射就會繼續建立軟引用對象,并往Metaspace中存放類資訊,是以會導緻Metaspace滿了

四、案例分析 其他導緻頻繁FULL GC的原因

1、記憶體配置設定不合理,導緻對象頻繁進入老年代

2、存在記憶體洩漏問題,老年代裡存放了大量對象無法被回收,比如使用本地緩存

3、永久代裡太多,比如上面那個案例

4、産生了大對象,比如SQL查詢傳回了一個幾十萬資料的對象

5、主動調用了System.gc

備注:系統建立大量線程,頻繁進行FULL GC,這兩種情況會導緻CPU負載過高

五、反射導緻方法區占滿的原因 

1、getDeclaredMethod的流程

《從零開始帶你成為JVM實戰高手》 筆記七

2、上一步擷取Method對象後,調用Method.invoke的流程 

《從零開始帶你成為JVM實戰高手》 筆記七

3、方法區占滿原因

 調用Class.getDeclaredMethod方法時,每個Class對象内部有個ReflectionData對象,它是個軟引用,如果是null,就會去jvm中取出類資訊并指派。後續調用就可以直接從這個ReflectionData對象中擷取,提高效率。

從ReflectionData中擷取到Method對象後,就開始調用invoke方法了,這裡實際執行invoke方法的有2個實作類,NativeMethodAccessorImpl和GeneratedMethodAccessorXXX,第一次調用時,預設會建立NativeMethodAccessorImpl類。NativeMethodAccessorImpl的invoke方法會有一個判斷,當調用不滿15次時,使用自身,當調用滿15次後,會建立GeneratedMethodAccessorXXX對象,而為了建立這個對象,會先建立DelegatingClassLoader這個類加載器,它會加載所需的類資訊到方法區中,後續再調用這個Method的invoke方法時,就會直接使用GeneratedMethodAccessorXXX,這樣可以提高效率。

但建立GeneratedMethodAccessorXXX會往方法區中寫入類資訊,而當ReflectionData被回收了,那麼下次調用getDeclaredMethod方法,又會重寫上面的步驟,又會往方法區中寫入類資訊。并且NativeMethodAccessorImpl的invoke方法是不加鎖的,是以在高并發下可能有好幾個線程同時建立了GeneratedMethodAccessorXXX對象,雖然最後隻會有一個,但卻會往方法區寫入n次類資訊。進而導緻方法區被占滿

使用Enhancer時,有個方法,設定是否使用緩存,預設都是true。如果設定成false,就可能導緻方法區溢出

enhancer.setUseCache(true);
           

因為如果使用緩存,Enhancer的create方法會從緩存中取類資訊,否則會調用類加載往方法區寫入類資訊,是以如果不斷建立Enhancer而又無法回收,就會導緻方法區溢出