1. 性能测试瓶颈分析
在性能测试过程中,瓶颈犹如功能测试的bug,瓶颈的分析犹如bug的定位。性能测试工程师好比医生,看到病象,定位病因。性能瓶颈的定位更像庖丁解牛,层层解剖,最后定位问题之所在。下面分享一个内存泄漏的瓶颈分析。
病象:TPS波动非常大;狂打超时日志;偶尔有500错误。
看到这个现象,其实说明不了什么问题,就象人咳嗽,不一定是感冒,可能是上火,嗓子发炎。但是看到这个现象至少说明系统是有性能问题存在,我们就要进一步进行分析,看看问题到底在哪?用jconsole监控内存,发现内存使用如图1
图1:内存使用情况图
从图1中,我们可以很清晰的看到内存使用不正常,FGC非常频繁,差不多5分钟进行一次,而且内存回收不彻底,每次回收在1G左右徘徊。到这里我们已经可以定位是内存问题,导致了我们看到的TPS波动大,FGC频繁,超时严重等等一系列现象。
那么是谁吃了我的内存???
用简单的jstat命令查看系统GC情况,看到情况如图2所示
图2
在图2的绿色框标注,我们可以很清晰的看到进行一次FGC,内存只回收12%左右,回收很不彻底,而且FGC的时间持续5秒。内存回收不彻底,肯定是有些方法霸占了内存不释放,导致系统频繁FGC来进行回收。
那么谁是真正的凶手呢??
用jmap 命令对内存使用进行分析,发现情况如图3所示
图3
通过FGC前后的内存使用进行比对,发现这三个方法快速占用内存从最少到最多,而且回收不掉,始终霸占着前几位。再通过其他工具分析,看看这三个是不是真正的凶手。
用MAT分析工具进行分析,图4所示
图4
这三个方法各占了内存使用的14%,那么问题就很清晰明朗了。这三个方法就是真正的凶手,调优就从这三个方法入手。
性能瓶颈的分析,犹如庖丁解牛,层层剖析。最终定位问题之所在。
VN:F [1.9.7_1111]
转载务必注明出处Taobao QA Team,原文地址:http://qa.taobao.com/?p=8641
2. JAVA-OPTS引发的思考
我们在性能测试过程中,经常要修改应用的JAVA-OPTS参数。修改这些参数,不单单是修改这些数字,本着知其所以然的态度,我们要知道这些参数背后的意义。
常见的JAVA-OPTS的配置项。
JAVA_OPTS=”-server -Xms1536m -Xmx1536m -XX:NewSize=320m -XX:MaxNewSize=320m -XX:PermSize=96m -XX:MaxPermSize=256m -Xmn500m -XX:MaxTenuringThreshold=5″
-Xms:设置堆内存池的最小值
-Xmx:设置堆内存池的最大值
-XX:NewSize:设置新对象生产堆内存
-XX:MaxNewSize:设置最大新对象生产堆内存
-XX:PermSize:设置Perm区的大小
XX:MaxPermSize:设置Perm区的最大值
-Xmn:设置Young区的大小
-XX:MaxTenuringThreshold:设置垃圾最大年龄。
更加详细的说明,请参考图1
图1
看到这里,或许你会迷糊什么是堆内存,什么Perm区?这样就引出了第二个思考:JAVA内存模型。
JAVA内存模型如图2所示。
图2
JAVA的内存模型可以分为3个代:Young、Tenured、Perm。有的版本又叫:New、Old、Perm。中文叫:年轻代、终生代、永久代。或许中文还有其他的叫法,但是表示的意思是一样的。
Young和Tenured共同组成了堆内存(heap)。
Young(年轻代)还可以分为Eden区和两个Survivor区(from和to,这两个Survivor区大小严格一至)。新的对象实例总是首先放在Eden区,Survivor区作为Eden区和 Tenured(终生代)的缓冲,可以向 Tenure(终生代)转移活动的对象实例。如图3所示。
图3
在明白了JAVA内存模型后,就引出了第三个思考:Jconsole在性能测试中的使用。Jconsole是我们在性能测试中常用的工具,打开Jconsole后,可以看到很多监控图表。当你明白JAVA内存模型后,也就很容易明白Jconsole的监控图表了。如图4所示。
图4
在Jconsole图中,我们可以很明显看到内存可以分为堆内存和非堆在堆内存。在堆的上面有三个柱子,分别代别的是:Eden、Survivor、Old。Old区就是Tenured区。
在了解上述知识后,我们在使用Jconsole来监控应用的内存时,会更加清晰。
VN:F [1.9.7_1111]
转载务必注明出处Taobao QA Team,原文地址:http://qa.taobao.com/?p=6621
3. 谈一谈JVM内存JAVA_OPTS参数
最近几个月,做的性能测试项目中,发现了一些内存方面的问题,其中有涉及到对JBOSS里的JAVA_OPTS配置,例如一下所示;
JAVA_OPTS=”-server -Xms1536m -Xmx1536m -XX:NewSize=320m -XX:MaxNewSize=320m -XX:PermSize=96m -XX:MaxPermSize=256m -Xmn500m -XX:MaxTenuringThreshold=5″
JAVA_OPTS并不是已成不变的,不同的应用、软硬件环境下,要想充分发挥应用的性能,这些参数里边的设置可是非常有技巧和具有经验积累的。
经过查找资料,先看下JAVA_OPTS参数表示的意义。
-server:一定要作为第一个参数,在多个CPU时性能佳
-Xms:初始Heap大小,使用的最小内存,cpu性能高时此值应设的大一些
-Xmx:java heap最大值,使用的最大内存
上面两个值是分配JVM的最小和最大内存,取决于硬件物理内存的大小,建议均设为物理内存的一半。-XX:PermSize:设定内存的永久保存区域
-XX:MaxPermSize:设定最大内存的永久保存区域
-XX:MaxNewSize:
-Xss 15120 这使得JBoss每增加一个线程(thread)就会立即消耗15M内存,而最佳值应该是128K,默认值好像是512k.
+XX:AggressiveHeap 会使得 Xms没有意义。这个参数让jvm忽略Xmx参数,疯狂地吃完一个G物理内存,再吃尽一个G的swap。
-Xss:每个线程的Stack大小
-verbose:gc 现实垃圾收集信息
-Xloggc:gc.log 指定垃圾收集日志文件
-Xmn:young generation的heap大小,一般设置为Xmx的3、4分之一
-XX:+UseParNewGC :缩短minor收集的时间
-XX:+UseConcMarkSweepGC :缩短major收集的时间
提示:此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。
稳定的开发架构环境下,建议出一份有实践、经验论证的JAVA_OPTS配置,能够非常切合实际的服务于当前开发、测试的软件流程。
VN:F [1.9.7_1111]
谈一谈JVM内存JAVA_OPTS参数,9.0out of10based on5ratings转载务必注明出处Taobao QA Team,原文地址:http://qa.taobao.com/?p=3395
4. JAVA内存泄露
最近在写性能宝典,正好内存泄露是其中非常重要的主题,记录下来,欢迎大家参与讨论,给出建议,谢谢!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
JAVA中的内存溢出的导致原因很多,最主要的可能有以下几种:
A. 由于JVM堆内存设置过小,可以通过-Xms -Xmm设置。
B. JVM堆内存是足够的,但只是没有连续的内存空间导致,比如申请连续内存空间的数组:String[] array = new String[10000]。
C.由于导入较多的依赖jar包以及项目本身引用的class太多。
D. 测试过程中生成太多的对象。
E. 缓存池载入太多的等待队列。
F. 还有可能是不断的内存泄露导致最后内存不足溢出
内存泄漏的慨念
1.c/c++是程序员自己治理内存,Java内存是由GC自动回收的。
我虽然不是很熟悉C++,不过这个应该没有犯常识性错误吧。
2.什么是内存泄露?
内存泄露是指系统中存在无法回收的内存,有时候会造成内存不足或系统崩溃。
在C/C++中分配了内存不释放的情况就是内存泄露。
3.Java存在内存泄露
我们必须先承认这个,才可以接着讨论。虽然Java存在内存泄露,但是基本上不用很关心它,非凡是那些对代码本身就不讲究的就更不要去关心这个了。
Java中的内存泄露当然是指:存在无用但是垃圾回收器无法回收的对象。而且即使有内存泄露问题存在,也不一定会表现出来。
4.Java中参数都是传值的。
对于基本类型,大家基本上没有异议,但是对于引用类型我们也不能有异议。
Java内存泄露情况
JVM回收算法是很复杂的,我也不知道他们怎么实现的,但是我只知道他们要实现的就是:对于没有被引用的对象是可以回收的。所以你要造成内存泄露就要做到:
持有对无用对象的引用!
不要以为这个很轻易做到,既然无用,你怎么还会持有它的引用? 既然你还持有它,它怎么会是无用的呢?
以下以堆栈更经典这个经典的例子来剖析。
public class Stack {
private Object[] elements=new Object[10];
private int size = 0;
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if( size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity(){
if(elements.length == size){
Object[] ldElements = elements;
elements = new Object[2 * elements.length+1];
System.arraycopy(oldElements,0, elements, 0, size);
}
}
}
上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无用,无法回收。
但是就是存在这样的东西也不一定会导致什么样的后果,假如这个堆栈用的比较少,也就浪费了几个K内存而已,反正我们的内存都上G了,哪里会有什么影响,再说这个东西很快就会被回收的,有什么关系。下面看两个例子。
例子1
public class Bad{
public static Stack s=Stack();
static{
s.push(new Object());
s.pop(); //这里有一个对象发生内存泄露
s.push(new Object()); //上面的对象可以被回收了,等于是自愈了
}
}
因为是static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说假如你的Stack最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很轻易理解,Stack内部持有100个引用,最坏的情况就是他们都是无用的,因为我们一旦放新的进取,以前的引用自然消失!
例子2
public class NotTooBad{
public void doSomething(){
Stack s=new Stack();
s.push(new Object());
//other code
s.pop();//这里同样导致对象无法回收,内存泄露.
}//退出方法,s自动无效,s可以被回收,Stack内部的引用自然没了,所以
//这里也可以自愈,而且可以说这个方法不存在内存泄露问题,不过是晚一点
//交给GC而已,因为它是封闭的,对外不开放,可以说上面的代码99.9999%的
//情况是不会造成任何影响的,当然你写这样的代码不会有什么坏的影响,但是
//绝对可以说是垃圾代码!没有矛盾吧,我在里面加一个空的for循环也不会有
//什么太大的影响吧,你会这么做吗?
}
VN:F [1.9.7_1111]
转载务必注明出处Taobao QA Team,原文地址:http://qa.taobao.com/?p=3625
5. jmap -dump:live为啥会触发Full GC
昨天组里的新人小朋友问是不是每次执行jmap -dump:live都会触发一次Full GC,因为当时他在做性能测试时某应用已经好几个小时没有一次FGC了,结果他执行了下dump就增加了次FGC。
我当时模糊回答应该会,以前看过哪篇文章好像提过^-^,不过本着严谨不误导新人小朋友的原则,还是找时间抽空验证实践了把:
测试环境:linux , sun jdk 1.6.07 , 32位
测试结果: jmap -dump:live 以及 jmap -histo:live都会触发Full GC,即使加上JVM参数-XX:+DisableExplicitGC也不影响结果
那么为什么呢? 其实大概猜也能知道,live选项的,如果FGC后,看到的活的对象比没有FGC的自然更精确。
我们来通过源码验证学习一下:
入口自然是 $j2se/src/share/classes/sun/tools/JMap.java
关键点是
VirtualMachine vm = attach(pid);
InputStream in = ((HotSpotVirtualMachine)vm).
heapHisto(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION);
以及
InputStream in = ((HotSpotVirtualMachine)vm).
dumpHeap((Object)filename,
(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION));
于是找到这里
HotSpotVirtualMachine.java:
public InputStream dumpHeap(Object … args) throws IOException {
return executeCommand(“dumpheap”, args);
}
和
public InputStream heapHisto(Object … args) throws IOException {
return executeCommand(“inspectheap”, args);
}
接着看到
LinuxVirtualMachine.java:
先是创建UNIX socket,然后连到target VM,把dumpheap或inspectheap命令通过socket发过去。
那么具体的inspectheap在sun hotspot核心代码里是如何处理的呢?
看这里 $hotspot/src/share/vm/services/attachListener.cpp
heap_inspection函数有如下关键代码:
VM_GC_HeapInspection heapop(out, live_objects_only /* request full gc */, true /* need_prologue */);
同样dumpheap在hotspot里也是这个文件里处理的:
jint dump_heap(AttachOperation* op, outputStream* out) {
…
// Request a full GC before heap dump if live_objects_only = true
// This helps reduces the amount of unreachable objects in the dump
// and makes it easier to browse.
HeapDumper dumper(live_objects_only /* request GC */);
int res = dumper.dump(op->arg(0));
…
这下我们就可以明白了,与实验结果也对上号了
参考资料:http://forums.java.net/jive/message.jspa?messageID=115907
VN:F [1.9.7_1111]
转载务必注明出处Taobao QA Team,原文地址:http://qa.taobao.com/?p=10010