背景
目前,有很多公司的WEB服務器會出現CPU、內存、IO告警,運維人員往往不能及時地獲取JVM等相關信息,以便分析造成告警的原因,故本文將從幾個方面來闡述如何進行JVM快照,如何分析dump的快照文件,以及Tomcat調優,JVM參數調優設置和程序代碼書寫需要注意的問題。
JVM學習
首先,我們來看一下JAVA中的內存模型:
圖 1
Dump JVM快照及分析
Java VisualVM工具
Jmap自動化獲取快照
性能調優
Tomcat容器調優
這里以Tomcat7舉例說明,Tomcat7容器調優主要是在server.xml文件中對connector進行調優,添加相關屬性,實現高并發。
server.xml配置
<Connector port="8080"protocol="HTTP/1.1" maxThreads="30000"
minSpareThreads="512" maxSpareThreads="2048" enableLookups="false"
redirectPort="8443" acceptCount="35000" debug="0" connectionTimeout="40000"
disableUploadTimeout="true" URIEncoding="UTF-8" />
參數說明:
connectionTimeout
網絡連接超時,單位:毫秒。設置為0表示永不超時,這樣設置有隱患的。通常可設置為30000毫秒。
keepAliveTimeout
長連接最大保持時間(毫秒)。此處為15秒。
maxKeepAliveRequests
最大長連接個數(1表示禁用,-1表示不限制個數,默認100個。一般設置在100~200之間)
maxHttpHeaderSize
http 請求頭信息的最大程度,超過此長度的部分不予處理。一般8K。
URIEncoding
指定Tomcat 容器的URL 編碼格式。
acceptCount
指定當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求將不予處理,默認為10個。
disableUploadTimeout
上傳時是否使用超時機制
enableLookups
是否反查域名,取值為:true 或false。為了提高處理能力,應設置為false
maxSpareThreads
最大空閑連接數,一旦創建的線程超過這個值,Tomcat 就會關閉不再需要的socket線程The default value is 50.
maxThreads
最多同時處理的連接數,Tomcat 使用線程來處理接收的每個請求。這個值表示Tomcat 可創建的最大的線程數。
minSpareThreads
最小空閑線程數,Tomcat 初始化時創建的線程數.
minProcessors
最小空閑連接線程數,用于提高系統處理性能,默認值為10。
maxProcessors
最大連接線程數,即:并發處理的最大請求數,默認值為75
JVM調優
程序開發注意事項
程序開發時,如果不理解JVM,很可能會造成內存溢出、棧溢出等問題。
堆溢出
public class HeapOOM {
static class OOMObject{}
/**
* @param args
*/
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
分析:
我們上面看到堆主要是存放對象的,所以我們如果想讓堆出現OOM的話,可以開一個死循環,然后產生新的對象就可以了。然后再將堆的大小調小點。
加上JVM參數
-verbose:gc -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError,
就能很快報出OOM:
Exception in thread "main"
java.lang.OutOfMemoryError: Java heap space。
棧溢出
package com.cutesource;
public class StackOOM {
/**
* @param args
*/
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable{
// TODO Auto-generated method stub
StackOOM oom = new StackOOM();
try{
oom.stackLeak();
}catch(Throwable err){
System.out.println("Stack length:" + oom.stackLength);
throw err;
}
}
}
分析:
我們知道棧中存放的方法執行的過程中需要的空間,所以我們可以下一個循環遞歸,這樣方法棧就會出現OOM的異常了。
設置JVM參數:-Xss128k,報出異常:
Exception in thread "main" java.lang.StackOverflowError
打印出Stack length:1007,這里可以看出,在我的機器上128k的棧容量能承載深度為1007的方法調用。
方法區溢出
public class MethodAreaOOM {
static class OOMOjbect{}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
while(true){
Enhancer eh = new Enhancer();
eh.setSuperclass(OOMOjbect.class);
eh.setUseCache(false);
eh.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object arg0, Method arg1,
Object[] arg2, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
return arg3.invokeSuper(arg0, arg2);
}
});
eh.create();
}
}
}
分析:
我們知道方法區是存放一些類的信息等,所以我們可以使用類加載無限循環加載class,這樣就會出現方法區的OOM異常。
手動將棧的大小調小點
加上JVM參數:-XX:PermSize=10M -XX:MaxPermSize=10M,運行后會報如下異常:
Exception in thread "main"
java.lang.OutOfMemoryError: PermGen space。
常量池溢出
public class ConstantOOM {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
分析:
我們知道常量池中存放的是運行過程中的常量,同時我們知道String類型的intern方法是將字符串的值放到常量池中的。所以上面弄可以開一個死循環將字符串的值都放到常量池中,這樣常量池就會出現OOM異常了。因為常量池本身就是方法區的一部分,所以我們也可以手動地調節一下棧的大小。
推薦:
《深入理解Java虛擬機》