目前,有很多公司的WEB服务器会出现CPU、内存、IO告警,运维人员往往不能及时地获取JVM等相关信息,以便分析造成告警的原因,故本文将从几个方面来阐述如何进行JVM快照,如何分析dump的快照文件,以及Tomcat调优,JVM参数调优设置和程序代码书写需要注意的问题。
首先,我们来看一下JAVA中的内存模型:
图 1
这里以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,很可能会造成内存溢出、栈溢出等问题。
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虚拟机》