排查java進程cpu100%的大致過程
之前遇到過
之前也遇到過cpu 100%的問題,原因是while循環,死循環了,一直占有cpu。
cpu為什么會100%
我們都知道cpu是時分(time division)的,操作系統里有很多線程,每個線程的運行時間由cpu決定,cpu會分給每個線程一個時間片,時間片是一個很短的時間長度,
如果在時間片內,線程一直占有,則是100%;
我們應該意識到,cpu運行速度很快(主頻非常高),除非密集型耗費cpu的運算,其它類型任務都會在小於時間片的時間內結束。
cpu 100%大致排查過程
排查java cpu100%的問題,大致步驟是固定的,
首先找到占用cpu的進程,
如果是java進程,
則繼續查看是哪個線程占用cpu,
然后根據線程id從線程棧中找到對應線程棧,
到這里,問題基本也就解決了。
故事背景
今天后台管理系統出現cpu 100%,這個問題間歇性出現,后台管理系統使用ssm(spring+springmvc+mybatis)+shiro實現,用戶量很小,所以可以排除高並發導致。接下來,我們按照前述排查步驟,進行排查。
找到cpu 100%的進程
登錄linux服務器找到占用cpu的進程,使用top
top
找出服務器的所有java進程,
ps -ef | grep java
或者使用
jps
經對比,占用cpu的進程是java進程,繼續挖,找出占用CPU的線程
top -H -p pid
-H
表示以線程的維度展示,默認以進程維度展示。
一共4個占用cpu的線程id 2944-2947,需要將線程id從十進制轉為十六進制,因為java線程棧文件中的線程id是十六進制。十進制 轉十六進制的命令是
echo "obase=16;number" | bc
obase(output base)是輸出的進制,number是輸入值,默認十進制,bc(An arbitrary precision calculator language)是任意進制轉換語言。
執行
echo "obase=16;2944" | bc
結果是
B80
導出棧
將java進程的線程棧導出,
jstack pid > pid.tdump
pid.tdump文件后綴名隨意,通常以tdump結尾。
在pid.tdump中找到nid=0XB80
的線程,這4個線程都是gc線程。一般的cpu 100%問題到這就結束了,但是這次不一樣,因為這4個線程是gc 線程。
gc線程忙碌表示內存不夠用了,要進行內存回收,第一反應是java內存回收不了,導致一直gc。
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fa49c01f000 nid=0xb80 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fa49c020800 nid=0xb81 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fa49c022800 nid=0xb82 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fa49c024000 nid=0xb83 runnable
導出堆
首先看一下堆的使用情況
jstat -gcutil pid
查看Java內存占用情況,發現老年代和伊甸區,使用率都90%多,但是存活區分配的很小大約500KB,並且基本沒有使用;
伊甸園區本來應該復制到存活區,但存活區過小,所以直接復制到老年代,但老年代已經沒有空間了,
所以,伊甸區越積累越多。那么到底是什么占着老年代不釋放呢?
沒辦法,導出java 堆來看看吧。如下將堆內存導出,只導出live的對象:
jmap -dump:live,format=b,file=pid.hprof pid
同樣的 文件后綴名可以是任意的,因為它也是二進制的,不過通常以hprof結尾。
jvisualvm分析快照
使用JAVA_HOME/bin/jvisualvm.exe
,載入快照(文件----->載入—>文件類型(堆))
按照大小排序,找出占用內存最大的類別,居然是字節數組,右擊在實例圖中顯示,
我們發現字節數組的值都很規律,前半部分的字節數組基本都相同,我就想能不能把字節數組轉為字符串,這樣就能 知道字節數組是什么內容了,恰好左下角有個將字節數組另存為二進制文件的選項。
將二進制文件轉為字符串,發現這些字節數組是 當前用戶的信息 ,如下
File file = new File("C:\\Users\\DELL\\Desktop\\heap.bin");
final FileInputStream fileInputStream = new FileInputStream(file);
byte[] buffer = new byte[300];
int len = -1;
while((len=fileInputStream.read(buffer))!=-1){
System.out.println(new String(buffer,0,len , "utf8"));
}
結合項目分析
當前用戶信息放在session中,而session又放在ehcache和redis,check shiro的sessionDAO發現,
session銷毀時,只將redis的session刪除,而未將ehcache中的也刪除,
另外查看ehcache中關於session cache配置,內存中元素個數是0,也就是不限制,這個很危險,並且配置的也不正確。
為了重現這個問題,重啟了tomcat,然后jmeter壓測登錄,發現老年區和伊甸區又滿了,在等待了3個小時(最大存活時間)后,cpu仍然100%。
RedisSessionDAO .java
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
@Override
public void doDelete(Session session) {
//將redis中的session清除
}
}
ehcache.xml,shiro session ehcache配置可以參考shiro session設置了過期時間不起作用、無效;
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="0"
eternal="false"
timeToIdleSeconds="10800" //3小時
timeToLiveSeconds="10800" //3小時
overflowToDisk="false"/>
session過期時間為3小時,時間太長了
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="10800000"></property>
</bean>
改正
改正,session銷毀時,將ehcache中的session也銷毀
RedisSessionDAO .java
public class RedisSessionDAO extends EnterpriseCacheSessionDAO { @Override public void doDelete(Session session) { //將echache中的session也刪除 getActiveSessionsCache().remove(sessionId); //將redis中的session清除 } }
ehcache.xml
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="100"
eternal="true"
timeToIdleSeconds="0" timeToLiveSeconds="0"
overflowToDisk="true"/>
去掉globalSessionTimeout,默認是30分鍾
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
</bean>
觀察
jmeter壓測一段時間后,老年代上升了,但過了1個小時(session調度器的默認執行間隔)后,old區,降下去了。到此為止,問題基本解決了。
必知必會java內存分析命令
上文提到的jstack、jmap、jstat、jvisualvm都位於 JAVA_HOME/bin 目錄下,這個目錄下有很多有用的工具。
疑問
雖然ehcache配置錯誤,但配了最大存活時間和最大空閑時間為3個小時,即使內存不限制數量,但到了3個小時ehcache應該會刪除才是,為什么沒刪除呢?
這個由於時間限制加上本身配置就是錯誤的應該配置為不限制,所以沒繼續追蹤下去。
上述原文:https://blog.csdn.net/wangjun5159/article/details/90414097
綜上: “ 由一個CUP占用率過高100% 的問題, 去理解Java垃圾回收機制 ”
當java堆內存,得不到釋放時,越積越多,然后就會不斷觸發垃圾回收,
( 操作系統里有很多線程,每個線程的運行時間由cpu決定,cpu會分給每個線程一個時間片,時間片是一個很短的時間長度, 如果在時間片內,線程一直占有,則是100%; )