我们都知道,在编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,所有的内存管理都由jvm来进行,但是由于代码书写不当会在某处出现内存泄露,在大多数情况下,一个OutOfMemoryError是内存泄漏的标志。

一.jvm gc原理学习心得:

jvm内存主要分为 java堆,永久代(perm),栈等。java堆主要分为 young 和 old两个区,总大小可以用-Xms(堆初始大小), -Xmx(堆上限)来设置;永久代主要用来保存java类的定义,如果在启动时出现 java.lang.OutOfMemoryError: PermGen space 就代表了永久代的内存溢出,理论上增加perm大小即可:-XX:MaxPermSize=256m。栈内存一般存储函数中定义的一些基本类型的变量和对象的引用变量,也就是类的地址。

jvm中的gc主要是针对java堆内存而言的,当然当perm满了,也就是所有的class本身动态加载量很大,而无法卸载的话,也会引起gc(full gc),甚至是oom。java堆内存分为young空间和old空间,gc也分为young gc(minor gc)和full gc,young空间用-Xmn 来配置大小,一般young空间为old空间的1/3左右大小。young空间(年轻代)被分为2个部分、3个板块,即“1个Eden区+2个Survivor区(S0,S1)”,所有通过new或者newInstance创建的新对象会先放入Eden区,当Eden区满时就会进行young gc。

young gc的流程是这样的:第一次young gc时会找出Eden中所有活着的对象放入S0活S1其中一个区,如果该区放不下,则会直接放入Old空间中,然后将Eden清空。第二次Eden区满时,将Eden中活着的对象 + S0中活着的对象放入S1中,同样如果放不下会放入Old区中,然后清空Eden和S0,后面依次类推,始终保持着S0或者S1是空,反反复复很多次仍然活着的对象也会放入Old区(默认15次)。

old区域又称老年代,当old区域满了时会进行full gc。由于old区域一般比较大,而且里面的对象大部分是活着的,在进行清理时要对全空间扫描遍历,所以开销很大,有时java程序会莫名卡住一下,一般就是在做full gc,真正好的程序在young gc时就清理了大部分垃圾,很长时间不会full gc,这也是进行内存管理后愿意看到的结果。

基于以上原理有几点心得:

1.写代码时尽量适应jvm gc方式,应用的程序跑的快而且局部内容较少比较好,当然这得因逻辑而定,如果需要使用内存cache,可以考虑加大survivor区解决gc问题。

2.较短生命周期的类不易太大,否则会因为survivor不够大而直接将这些类填入old区导致full gc,能在young gc时回收的就在young gc回收。而且如果从Eden准备存放入old时old不够大会先进行full gc之后再放入。

3.永久代(perm)会保存Class的加载,也就是除了类本身外,static数据也放在其中,还有static外面包装等。还有就是永久代有一块所谓的常量池区域,主要用于保存string中intern()的常量(使用equal()函数时使用的)。当perm满时也会进行full gc,但是就设计上讲,perm代表了程序中固定的部分,即可能估算出的大小,在分配时够用就好,不必太大。

4.执行System.gc()也会直接导致full gc

5.java使用-client启动时,默认使用的是串行gc(也可以配置 -XX+UseSerialGC),一般用于cpu数量少,内存小的情况(1G以下)。配置-server启动,就是默认使用串行多线程处理gc(配置-XX:+UseParallelGC,minorGC多线程,MajorGC单线程,配置-XX:+UseParallelOldGC,young,old都是多线程GC),一般1-2G内存使用。再大有一种CMS GC,即Concurrent Mark Sweep,并发的gc,具体参见  。

二.内存泄露排查

常见的java工具有:

1.jps ,在linux基本等同于 ps -ef|grep java 

2.jstat,是比较常用的方式,通过jstat -gc <pid>来显示出jvm中每个区域的情况。

例如:jstat -gcutil <pid> 1000 100 ,1000代表每1000ms采集一次,100代表采集一百次。

java JAVA_OPTS内存 java内存问题_java JAVA_OPTS内存

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 — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)




3.jmap: 经常使用方式为: jmap -heap<pid>  用于查看堆情况、jmap -histo<pid>用于查看活着的对象情况

jmap -dump:format=b,file=xxx/xxx.bin <pid> 用于导出堆情况的二进制文件,可以用mat进行查看。


4.jConsole: JConsole是jdk自带的图形化工具,比较好用,也支持远程监控,但是目前还不知道如何定位内存泄露的具体类的位置,

使用参见  http://swiftlet.net/archives/658


5.MAT:目前正在使用,是java专门用来分析内存的工具,不过分析之前先要用jmap dump出内存二进制文件,或者在jvm中配置-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:\testHeapDump  代表oom后自动将二进制内存文件输出

或者是配置 -XX:+HeapDumpBeforeFullGC  -XX:++HeapDumpAfterFullGC 让系统在gc前 gc后输出文件

安装eclipse插件 http://download.eclipse.org/mat/1.5/update-site/,然后直接把二进制文件拖进eclipse使用