(友情提示:本博文章歡迎轉載,但請註明出處:hankchen,http://www.blogjava.net/hankchen)
一、高CPU佔用
一個應用佔用CPU很高,除了確實是計算密集型應用之外,通常原因都是出現了死循環。以我們最近出現的一個實際故障爲例,介紹怎麼定位和解決這類問題。
根據top命令,發現PID爲28555的Java進程佔用CPU高達200%,出現故障。
通過ps aux | grep PID命令,可以進一步確定是tomcat進程出現了問題。但是,怎麼定位到具體線程或者代碼呢?
1、首先顯示線程列表:
ps -mp pid -o THREAD,tid,time
找到了耗時最高的線程28802,佔用CPU時間快兩個小時了!
2、其次將需要的線程ID轉換爲16進制格式:
printf "%x\n" tid
3、最後打印線程的堆棧信息:
jstack pid |grep tid -A 30
找到出現問題的代碼了!
現在來分析下具體的代碼:ShortSocketIO.readBytes(ShortSocketIO.java:106)
ShortSocketIO是應用封裝的一個用短連接Socket通信的工具類。readBytes函數的代碼如下:
public byte[] readBytes(int length) throws IOException {
if ((this.socket == null) || (!this.socket.isConnected())) {
throw new IOException("++++ attempting to read from closed socket");
}4
byte[] result = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
if (this.recIndex >= length) {
bos.write(this.recBuf, 0, length);
byte[] newBuf = new byte[this.recBufSize];
if (this.recIndex > length) {
System.arraycopy(this.recBuf, length, newBuf, 0, this.recIndex - length);
}
this.recBuf = newBuf;
this.recIndex -= length;
} else {
int totalread = length;
if (this.recIndex > 0) {
totalread -= this.recIndex;
bos.write(this.recBuf, 0, this.recIndex);
this.recBuf = new byte[this.recBufSize];
this.recIndex = 0;
}
int readCount = 0;
while (totalread > 0) {
if ((readCount = this.in.read(this.recBuf)) > 0) {
if (totalread > readCount) {
bos.write(this.recBuf, 0, readCount);
this.recBuf = new byte[this.recBufSize];
this.recIndex = 0;
} else {
bos.write(this.recBuf, 0, totalread);
byte[] newBuf = new byte[this.recBufSize];
System.arraycopy(this.recBuf, totalread, newBuf, 0, readCount - totalread);
this.recBuf = newBuf;
this.recIndex = (readCount - totalread);
}
totalread -= readCount;
}
}
}
問題就出在標紅的代碼部分。如果this.in.read()返回的數據小於等於0時,循環就一直進行下去了。而這種情況在網絡擁塞的時候是可能發生的。
至於具體怎麼修改就看業務邏輯應該怎麼對待這種特殊情況了。
4、最後,總結下排查CPU故障的方法和技巧有哪些:
a、top命令:Linux命令。可以查看實時的CPU使用情況。也可以查看最近一段時間的CPU使用情況。
b、PS命令:linux命令。強大的進程狀態監控命令。可以查看進程以及進程中線程的當前CPU使用情況。屬於當前狀態的採樣數據。
c、jstack:Java提供的命令。可以查看某個進程的當前線程棧運行情況。根據這個命令的輸出可以定位某個進程的所有線程的當前運行狀態、運行代碼,以及是否死鎖等等。
d、pstack:Linux命令。可以查看某個進程的當前線程棧運行情況。
二、高內存佔用
搞Java開發的,經常會碰到下面兩種異常:
a、java.lang.OutOfMemoryError: PermGen space
b、java.lang.OutOfMemoryError: Java heap space
要詳細解釋這兩種異常,需要簡單重提下Java內存區域劃分。
在Java虛擬機中,內存分爲三個代:新生代(New)、老生代(Old)、永久代(Perm)。
(1)新生代New:新建的對象都存放這裏
(2)老生代Old:存放從新生代New中遷移過來的生命週期較久的對象。新生代New和老生代Old共同組成了堆內存。
(3)永久代Perm:是非堆內存的組成部分。主要存放加載的Class類級對象如class本身,method,field等等。
如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。原因有二:
(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。
(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)。
如果出現java.lang.OutOfMemoryError: PermGen space,說明是Java虛擬機對永久代Perm內存設置不夠。
一般出現這種情況,都是程序啓動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。
從代碼的角度,軟件開發人員主要關注java.lang.OutOfMemoryError: Java heap space異常,減少不必要的對象創建,同時避免內存泄漏。
現在以一個實際的例子分析內存佔用的故障排查。
通過top命令,發現PID爲9004的Java進程一直佔用比較高的內存不釋放(24.7%),出現高內存佔用的故障。
然後使用ps 命令 ps -mp 9004 -o THREAD,tid,time,rss,size,%mem ,看能否查看進程下線程的內存佔用情況(顯然根據理論知識是不可以的:進程是資源分配的最小單元,線程之間公用進程的內存)
顯然對於這種情況可以使用Java自身提供的內存監控工具:jmap(參見:https://blog.csdn.net/leehsiao/article/details/77646079)
通過jmap -histo:live [pid] 命令 可以查看進程下內存使用情況
從上圖可以看出,int數組、constMethodKlass、methodKlass、constantPoolKlass都佔用了大量的內存。
特別是佔用了大量內存的int數組,需要仔細檢查相關代碼。
最後,總結下排查內存故障的方法和技巧有哪些:
a、top命令:Linux命令。可以查看實時的內存使用情況。
b、jmap -histo:live [pid],然後分析具體的對象數目和佔用內存大小,從而定位代碼。
c、jmap -dump:live,format=b,file=xxx.xxx [pid],然後利用MAT工具分析是否存在內存泄漏等等。