背景
在閱讀《Java效能調優指南》一書的最後,書中介紹了Serviceability Agent,並給出了一些排查問題的示例,我感覺看書不夠深刻,因此自己在macOs上進行了一些實驗。我的作業系統版本是:macOS Sierra 10.12.6,我的JDK版本是1.8.0_152。
例子程式
在Java開發中,常常遇到的一種問題是記憶體空間會越來越大,極端情況下會出現OOM——java.lang.OutOfMemoryError。產生記憶體不足錯誤的原因可能是:堆空間不足或永生代(java8中的元資料區)不足,並且這時候無法回收一些物件以釋放空間,也無法擴容Java對空間。
應用開發人員常犯的錯誤是在應用中隨意維護多個實際並不需要的快取和物件集合,不必要得增加了應用佔用的記憶體空間,從而導致記憶體空間不足的錯誤。下麵的這個例子程式比較極端,是為了快速模擬出OOM的錯誤。
import java.util.HashMap;
import java.util.Vector;
/**
* 作用: 演示記憶體上限溢位的錯誤,在實際開發中常常犯的一類錯誤:隨意快取(維護)一些實際不需要的物件的集合,導致記憶體上限溢位
* User: duqi
* Date: 2017/12/16
* Time: 14:31
*/
public class MemoryError {
static Vector employeesList;
public static void main(String[] args) {
//僱員物件被儲存在兩個集合物件中,一個HashMap一個Vector,
//都分別持有Employee和Address的取用物件,這樣這些物件就不會被GC當做垃圾回收
employeesList = new Vector();
HashMap employeesMap = new HashMap();
int i = 0;
while (true) {
Emplyee emp1 = new Emplyee("Ram", new Address("MG Road", "Bangalore",
123, "India"));
Emplyee emp2 = new Emplyee("Bob", new Address("House No. 4",
"SCA", 234, "USA"));
Emplyee emp3 = new Emplyee("John", new Address("Church Street",
"Bangalore", 569, "India"));
employeesMap.put(new Integer(i++), emp1);
employeesList.add(emp1);
employeesMap.put(new Integer(i++), emp2);
employeesList.add(emp2);
employeesMap.put(new Integer(i++), emp3);
employeesList.add(emp3);
emp2.addReports(emp1);
emp3.addReports(emp1);
emp3.addReports(emp2);
}
}
}
class Emplyee {
public String name;
public Address address;
public Vector directReports;
public Emplyee(String name, Address address) {
this.name = name;
this.address = address;
directReports = new Vector();
}
public void addReports(Emplyee emplyee) {
directReports.add(emplyee);
}
}
class Address {
public String addr;
public String city;
public int zip;
public String country;
public Address(String addr, String city, int zip, String country) {
this.addr = addr;
this.city = city;
this.zip = zip;
this.country = country;
}
}
在IDEA中執行這個程式,會得到如下的錯誤資訊:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at org.java.learn.jvm.gc.MemoryError.main(MemoryError.java:26)
Process finished with exit code 1
方式方法
在遇到OOM錯誤時,有多種方法可以分析這個錯誤:
-
用jmap工具,JConsole工具或者JVM啟動引數-XX:+HeapDumpOnOutOfMemorError生成Java堆的快照檔案,然後利用jhat或者VisualVM去分析;
-
利用SA工具連結到應用程式的行程上去獲取物件直方圖;
-
利用JVM引數-XX:OnOutOfMemoryError,在遇到OOM的時候自動生成core檔案,然後利用SA工具分析core檔案生成的物件直方圖。
我們這篇文章主要練習如何使用SA,因此不考慮第一種情況(而且這種方法相信大家都比較熟悉),第二種方法,不太符合生產環境的情況(我們不會讓你直接暫停線上的應用然後去分析問題),我這裡想使用第三種方法。
實踐
首先,我嘗試透過IDEA設定JVM引數,入下圖所示:
但是在執行程式碼後,遇到如下異常,導致無法生成core檔案:
搜了很多資料,明白應該是mac os預設是不能生成core檔案的,這點在終端中透過ulimit -a
可以看出:
~ ❯❯❯ ulimit -a
-t: cpu time (seconds) unlimited
-f: file size (blocks) unlimited
-d: data seg size (kbytes) unlimited
-s: stack size (kbytes) 8192
-c: core file size (blocks) 0
-v: address space (kbytes) unlimited
-l: locked-in-memory size (kbytes) unlimited
-u: processes 709
-n: file descriptors 4864
最後,我採用如下方式執行上面的例子程式:
-
開啟一個終端tab,去掉core檔案的大小限制:
ulimit -c unlimited
; -
使用
javac
命令編譯上述程式,生成class檔案; -
使用
sudo java -XX:OnOutOfMemoryError="gcore %p" MemoryError
命令執行上述程式,生成的core檔案在/core目錄下。 -
使用Serviceability Agent介紹中提到的方法,啟動SA HSDB,並開啟上述步驟生成的core檔案,使用物件直方圖工具生成該程式在發生OOM時候的物件直方圖:除去一些JDK自己的類之後,可以看到Address和Emplyee這兩個類對應的物件佔的空間特別大,這就可以說明這兩個物件的空間一直沒有被回收。
書上得來終覺淺,絕知此事要躬行。希望這篇文章,能夠給你帶來一定的幫助,動手試試吧。
參考資料
-
《Java效能調優指南》
-
mac os X生成core檔案
明天是渣科的球衣退役儀式了,還記得大學時候看湖人比賽的激情,後來渣科退役後,我也成了一名佛系籃球迷,看比賽不悲不喜。騰訊體育今天的一組圖片真是用心了,檢視原文可以看。
再分享一首歌《Because Of You》,感謝渣科,是你讓我愛上了籃球,是你讓我透過籃球結識了很多朋友。