探究JVM内存泄露 性能调优的基本知识和JDK调优
在性能调优之前,我们首先来了解一下性能是什么?关于性能,我想每个学习过?Java?的人都能列出几点,甚至可以夸夸其谈。在《?Java TM Platform Performance?》一书中,定义了如下五个方面来作为评判性能的标准:
1)?运算的性能——哪一个算法的执行性能最好?
2)?内存的分配——程序运行时需要耗费多少内存?
3)?启动的时间——程序启动需要多长时间?这在?Web?项目中的影响不大,但要注意部分程序需要部署或运行在客户端时的情形(比如?applet?程序)。
4)?程序的可伸缩性——在压力负载的情况下,程序的性能如何?
5)?性能的感知——用户在什么情况下会觉得程序的性能不好?
以上五个方面,在具体的使用场景可以有选择的去评判。至于这五方面的性能调优,在后续的章节中将会陆续的给以相应的性能调优策略。
我们只需要关心对我们程序有影响,可以察觉到的性能问题,而不是每一个类中的每一个方法我们都需要想方设法的提高性能。如果程序的性能没有达到我们所期望的要求,我们才需要考虑如何优化性能。同样的,晦涩的代码虽然提高了程序的性能,但同时可能带给我们的是维护的噩梦。我们需要折中的考虑以上两种情况,使得程序的代码是优美的,并且运行的足够快,达到客户所期望的性能要求。
优化代码甚至会导致不良的结果,?Donald Knuth?(一位比较牛比较有影响的人物,具体是谁,我也忘了,谁知道,可以告诉我一下,谢谢!)曾说过,“?Premature optimization is the root of all evil”?。在开始性能调优前,需要先指出不优化代码的一些理由。
1)?如果优化的代码已经正常工作,优化后可能会引入新的?bug?;
2)?优化代码趋向于使代码更难理解和维护;
3)?在一个平台上优化的代码,在另一个平台上可能更糟;
4)?花费很多时间在代码的优化上,提高了很少的性能,却导致了晦涩的代码。
确实,在优化前,我们必须认真的考虑是否值得去优化。
一般我们提高应用程序的性能划分为以下几个步骤:
1)?明确应用程序的性能指标,怎样才符合期望的性能需求;
2)?在目标平台进行测试;
3)?如果性能已经达到性能指标,?Stop?;
4)?查找性能瓶颈;
5)?修改性能瓶颈;
6)?返回到第?2?步。
不同版本的?JDK?,甚至不同厂家的?JDK?可能都存在着很大的差异,对于性能优化的程度不同。一般来说,尽可能选择最新发布的稳定的?JDK?版本。最新的稳定的?JDK?版本相对以前的?JDK?版本都会做一些?bug?的修改和性能的优化工作。
垃圾收集就是自动释放不再被程序所使用的对象的过程。当一个对象不再被程序所引用时,它所引用的堆空间可以被回收,以便被后续的新对象所使用。垃圾收集器必须能够断定哪些对象是不再被引用的,并且能够把它们所占据的堆空间释放出来。如果对象不再被使用,但还有被程序所引用,这时是不能被垃圾收集器所回收的,此时就是所谓的“内存泄漏”。监控应用程序是否发生了内存泄漏,有一个非常优秀的监控工具推荐给大家——Quest?公司的?JProbe?工具,使用它来观察程序运行期的内存变化,并可产生内存快照,从而分析并定位内存泄漏的确切位置,可以精确定位到源码内。这个工具的使用我在后续的章节中还会做具体介绍。
Java?堆是指在程序运行时分配给对象生存的空间。通过?-mx/-Xmx?和?-ms/-Xms?来设置起始堆的大小和最大堆的大小。根据自己?JDK?的版本和厂家决定使用?-mx?和?-ms?或?-Xmx?和?-Xms?。?Java?堆大小决定了垃圾回收的频度和速度,?Java?堆越大,垃圾回收的频度越低,速度越慢。同理,?Java?堆越小,垃圾回收的频度越高,速度越快。要想设置比较理想的参数,还是需要了解一些基础知识的。
Java?堆的最大值不能太大,这样会造成系统内存被频繁的交换和分页。所以最大内存必须低于物理内存减去其他应用程序和进程需要的内存。而且堆设置的太大,造成垃圾回收的时间过长,这样将得不偿失,极大的影响程序的性能。以下是一些经常使用的参数设置:
1)?设置?-Xms?等于?-XmX?的值;
2)?估计内存中存活对象所占的空间的大小,设置?-Xms?等于此值,?-Xmx?四倍于此值;
3)?设置?-Xms?等于?-Xmx?的?1/2?大小;
4)?设置?-Xms?介于?-Xmx?的?1/10?到?1/4?之间;
5)?使用默认的设置。
大家需要根据自己的运行程序的具体使用场景,来确定最适合自己的参数设置。
除了?-Xms?和?-Xmx?两个最重要的参数外,还有很多可能会用到的参数,这些参数通常强烈的依赖于垃圾收集的算法,所以可能因为?JDK?的版本和厂家而有所不同。但这些参数一般在?Web?开发中用的比较少,我就不做详细介绍了。在实际的应用中注意设置?-Xms?和?-Xmx?使其尽可能的优化应用程序就行了。对于性能要求很高的程序,就需要自己再多研究研究?Java?虚拟机和垃圾收集算法的机制了。可以看看曹晓钢翻译的《深入?Java?虚拟机》一书。
?
?
WEB?服务总是莫名其妙的运行一段时间后?JVM?直接?OutOfMemory?错误,内存泄漏的问题不容易查找,本文就一些查找内存泄露基本知识做个总结,未涉及到具体案例的分析。
?
Heap?
PSYoungGen?total 44928K, used 916K [0x4e3c0000, 0x50fe0000, 0x51b10000)?
eden space 44736K, 2% used [0x4e3c0000,0x4e4a5318,0x50f70000)?
from space 192K, 0% used [0x50f70000,0x50f70000,0x50fa0000)?
to?space 192K, 0% used [0x50fb0000,0x50fb0000,0x50fe0000)?
PSOldGen?total 453312K, used 125529K [0x32910000, 0x4e3c0000, 0x4e3c0000)
object space 453312K, 27% used [0x32910000,0x3a3a6498,0x4e3c0000)?
PSPermGen?total 65536K, used 65535K [0x2e910000, 0x32910000, 0x32910000)?
object space 65536K,?99% used?[0x2e910000,0x3290fff8,0x32910000)
permanent space?持久空间?:?用于类和方法对象的存储。?spring?在?AOP?时使用?CBLIB?会动态产生很多类,当类太多,超过?MaxPermSize?的时候,就会抛出此异常。?参数问题?可以设置?jvm?启动参数?: PermSize, MaxPermSize?。程序问题就要进行内存分析了,详见下文。
?
Heap
PSYoungGen?total 88320K, used 67673K [0x44880000, 0x4ba40000, 0x4ba40000)
eden space 61952K,?100% used?[0x44880000,0x48500000,0x48500000)
from space 26368K, 21% used [0x48500000,0x48a96490,0x49ec0000)
to space 24512K, 16% used [0x4a250000,0x4a6283e0,0x4ba40000)
PSOldGen?total 932096K, used 582090K [0x0ba40000, 0x44880000, 0x44880000)
object space 932096K, 62% used [0x0ba40000,0x2f2b2a78,0x44880000)
PSPermGen?total 131072K, used 35124K [0x03a40000, 0x0ba40000, 0x0ba40000)
object space 131072K, 26% used [0x03a40000,0x05c8d330,0x0ba40000)
eden space?使用率?100%?,总是被占满,参数问题?可以设置?jvm?启动参数?: Xms, Xmx?。程序问题就要进行内存分析了,详见下文。
jstat -gcutil pid 1000 20
异常情况的例子
jstat -gcutil pid 1000 20
?
S0?S1?E?O?P?YGC?YGCT?FGC?FGCT?GCT?
0.00?0.00?99.99?82.51?53.11?2409?1.205 10117 7250.393 7251.598
0.00?0.00?83.42?82.55?53.10?2409?1.205 10118 7252.650 7253.855
0.00?0.00?56.06?82.46?53.10?2410?1.205 10120 7254.467 7255.672
0.00?0.00?32.11?82.55?53.10?2411?1.205 10121 7256.673 7257.877
0.00?0.00?99.99?82.55?53.10?2412?1.205 10123 7257.026 7258.231
0.00?0.00?76.00?82.50?53.10?2412?1.205 10124 7259.241 7260.446
这个数据显示?Full GC?频繁发生。
?
正常情况的例子
?
S0?S1?E?O?P?YGC?YGCT?FGC?FGCT?GCT
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
0.00?0.00?0.24?55.39?99.60?171?0.667?1339?393.364?394.031
参数含义:?
S0?:?Heap?上的?Survivor space 0?段已使用空间的百分比?
S1?:?Heap?上的?Survivor space 1?段已使用空间的百分比?
E?:?Heap?上的?Eden space?段已使用空间的百分比?
O?:?Heap?上的?Old space?段已使用空间的百分比?
P?:?Perm space?已使用空间的百分比?
YGC?:从程序启动到采样时发生?Young GC?的次数?
YGCT?:?Young GC?所用的时间?(?单位秒?)?
FGC?:从程序启动到采样时发生?Full GC?的次数?
FGCT?:?Full GC?所用的时间?(?单位秒?)?
GCT?:用于垃圾回收的总时间?(?单位秒?)
?
在?windows?下,使用?tasklist
在?Linux?下,使用?ps –aux
可以通过命令:
?
jmap -dump:file=a.hprof pid
也可以通过?jconsole?的图形界面操作。
?
在命令行键入:?jconsole
Jconsole?打开后在造作下选择?dumpHeap,?输入参数?p0,p1;p0?表示?dump?出来的文件路径,后缀为?.hprof?;p1?设为?true?,表示只分析活着的对象。

?
目前有很多用来分析?Java?内存对象的工具,如收费的工具有?jprofiler,?而像?Eclipse MAT?则是优秀的内存对象分析开源工具?.?它们对于分析内存溢出问题非常有用。以下是一个安装使用?Eclipse MAT?的简单例子。
http://download.eclipse.org/technology/mat/latest/update-site/
?

?
?

Java?进程查看工具,实际上它和?Unix/Linux?上面的?ps?命令的功能差不多
jmap?是一个可以输出所有内存中对象的工具?.
* -dump:format=b,file=<filename>?转存堆内存到本地文件。?
* -histo?打印堆里每个类的情况,包含内存占用大小、对象数量及完整类名。?VM?的内部类以?"*"?开头。
例子:
?
jmap -histo pid>a.log
jmap -dump: file=a.hprof pid
查看?a.log
?
num?#instances?#bytes class name
--------------------------------------
1:?427398?14458448 [I
2:?178798?6830216 [C
3:?50278?6668512 <constMethodKlass>
4:?179924?4318176 java.lang.String
5:?50278?4026648 <methodKlass>
6:?15244?3894200 [B
7:?47809?1773776 [Ljava.lang.Object;
...
Total 1645187?81806088
说明:
#instance?是对象的实例个数?
#bytes?是总占用的字节数?
class name?对应的就是?Class?文件里的?class?的标识?
B?代表?byte
C?代表?char
D?代表?double
F?代表?float
I?代表?int
J?代表?long
Z?代表?boolean
前边有?[?代表数组,?[I?就相当于?int[]
对象用?[L+?类名表示
jstat?是?vm?的状态监控工具,监控的内容有类加载、运行时编译及?GC?。
使用时,需加上查看进程的进程?id?,和所选参数。以下详细介绍各个参数的意义。?
jstat -class pid:?显示加载?class?的数量,及所占空间等信息。?
jstat -compiler pid:?显示?VM?实时编译的数量等信息。?
jstat -gc pid:?可以显示?gc?的信息,查看?gc?的次数,及时间。其中最后五项,分别是?young gc?的次数,young gc?的时间,?full gc?的次数,?full gc?的时间,?gc?的总时间。?
jstat -gccapacity:?可以显示,?VM?内存中三代(?young,old,perm?)对象的使用和占用大小,如:?PGCMN显示的是最小?perm?的内存使用量,?PGCMX?显示的是?perm?的内存最大使用量,?PGC?是当前新生成的?perm?内存占用量,?PC?是但前?perm?内存占用量。其他的可以根据这个类推,?OC?是?old?内纯的占用量。?
jstat -gcnew pid:new?对象的信息。?
jstat -gcnewcapacity pid:new?对象的信息及其占用量。?
jstat -gcold pid:old?对象的信息。?
jstat -gcoldcapacity pid:old?对象的信息及其占用量。?
jstat -gcpermcapacity pid: perm?对象的信息及其占用量。?
jstat -util pid:?统计?gc?信息统计。?
jstat -printcompilation pid:?当前?VM?执行的信息。?
除了以上一个参数外,还可以同时加上?两个数字,如:?jstat -printcompilation 3024 250 6?是每?250毫秒打印一次,一共打印?6次,还可以加上?-h3?每三行显示一下标题。?
例子:
?
jstat -gcutil pid 1000 20
一个?java GUI?监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器?VM?。