jstat详解java垃圾回收机制

分类:开发相关    发布时间:2018-01-31 11:46:37

使用Jstat发现内存溢出

 

Jstat JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于javabin目录下,

主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size(堆大小)和垃圾回收状况的监控。

 

如果是内存溢出,可进一步通过工具查看溢出对象到GC Roots的引用链,这样就比较容易确定发生溢出的代码位置。
如果不存在内存溢出,那就应当检查虚拟机堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

jstat 参数解释

Options — 选项,我们一般使用 -gcutil 查看gc情况

vmid — VM的进程号,即当前运行的java进程号

interval — 间隔时间,单位为秒或者毫秒

count — 打印次数,如果缺省则打印无数次

Options选项

-class:统计class loader行为信息
-compile:统计编译行为信息
-gc:统计jdk gc时heap信息
-gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况
-gccause:统计gc的情况,(同-gcutil)和引起gc的事件
-gcnew:统计gc时,新生代的情况
-gcnewcapacity:统计gc时,新生代heap容量
-gcold:统计gc时,老年区的情况
-gcoldcapacity:统计gc时,老年区heap容量
-gcpermcapacity:统计gc时,permanent区heap容量
-gcutil:统计gc时,heap情况
-printcompilation:在Java的方法被编译时,打印其跟踪信息

 

 

示例:监控内存使用情况 参数 (查看内存溢出相对有用)

jstat -gccause  2083 5000 50(每隔5秒监控一次)

2083进程号; 5000采样间隔时间为5秒; 50采样次数,如缺省则打印无数次

运行结果(典型的outofmemory先兆!)::


其中输出内容含义如下

S0 — Heap(堆)上的 Survivor space 0 (幸存者0区)已使用空间的百分比
S1 — Heap上的 Survivor space 1 (幸存者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 — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

 

JVM内存分配机制和GC机制:

Jvm把内存分为两大块,一个是none heap和 heap,也就是:永久存储区(Permanent Space)和堆空间(The Heap Space)。 其中堆空间又分为新生区(Young (New) generation space)和养老区(Tenure (Old) generation space),新生区又分为伊甸园(Eden space),幸存者0区(Survivor 0 space)和幸存者1区(Survivor 1 space)。具体分区如下图:


永久存储区(Permanent Space:永久存储区是JVM的驻留内存,用于存放JDK自身所携带的Class,Interface的元数据,应用服务器允许必须的 Class,Interface的元数据和Java程序运行时需要的Class和Interface的元数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放此区域所控制的内存。


堆空间(The Heap Space:是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。堆空间又分别按JAVA对象的创建和年龄特征分为养老区和新生区。


新生区(Young (New) generation space:新生区的作用包括JAVA对象的创建和从JAVA对象中筛选出能进入养老区的JAVA对象。


伊甸园(Eden space:JAVA对空间中的所有对象在此出生,该区的名字因此而得名。也即是说当你的JAVA程序运行时,需要创建新的对象,JVM将在该区为你创建一个指定的对象供程序使用。创建对象的依据即是永久存储区中的元数据。


幸存者0区(Survivor 0 space)和幸存者1区(Survivor1 space:当伊甸园的控件用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。同时将伊甸园中的还有其他对象引用的对象移动到幸存者0区。幸存者0区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。当将伊甸园中的还有其他对象引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时,JVM的垃圾回收器将对幸存者0区进行垃圾回收处理,将幸存者0区中不在有其他对象引用的JAVA对象进行销毁,将幸存者0区中还有其他对象引用的对象移动到幸存者1区。幸存者1区的作用就是用于存放幸存者0区垃圾回收处理所幸存下来的JAVA对象。


养老区(Tenure (Old) generation space:用于保存从新生区筛选出来的JAVA对象。


垃圾回收描述:

垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。


当一个URL被访问时,内存申请过程如下:

A. JVM会试图为相关Java对象在Eden中初始化一块内存区域
B. 当Eden空间足够时,内存申请结束。否则到下一步
C. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区
D. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
E. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)
F. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”


对象衰老过程:

1. young generation的内存,由一块Eden和两块Survivor Space构成。新创建的对象的内存都分配自eden。两块Survivor Space总有会一块是空闲的,用作copying collection的目标空间。Minor collection的过程就是将eden和在用survivor space中的活对象copy到空闲survivor space中。所谓survivor,也就是大部分对象在Eden出生后,根本活不过一次GC。对象在young generation里经历了一定次数的minor collection后,年纪大了,就会被移到old generation中,称为tenuring。

2. 剩余内存空间不足会触发GC,如eden空间不够了就要进行minor collection,old generation空间不够要进行major collection,permanent generation空间不足会引发Full GC。

那么如何从jstat的输出来诊断应用程序是否有内存泄露的问题呢?下面列出几个经验总结*(其实这才是本文的重点):*


如何判断应用程序是否有内存的问题:

1. Full GC的频率,时长和效果: 如果Full GC频率较高,比如数秒一次,那么此时程序可能就已经出问题了,因为jvm在Full GC的时候是不响应外部请求的。

如果Full GC时间较长,比如持续数秒,那么此时程序可能就已经出问题了,因为jvm在Full GC的时候是不响应外部请求的。

如果Full GC之后old 区内存没有显著增加,那么程序很可能有内存泄露问题,并且不久将来可能出现outofmemory异常。

如果young gc和full gc能够正常发生,且都能有效回收内存,常驻内存区变化不明显,则说明java内存释放情况正常,垃圾回收及时,java内存泄露的几率就会大大降低。但也不能说明一定没有内存泄露。

2. GC的频率,时长和效果: 如果JVM进行内存回收的频率非常高,比如几乎每数秒中就有一次,每次回收的时间为数秒钟;并且,通过输出还发现每次回收释放的内存非常有限,大多数对象都无法回收。这种现象很大程度上暗示着内存泄漏。(此时可以用“jmap”来获得当前的一个内存映象,看看哪些对象导致这个问题来找出原因)

如果每次GC时间特别长,比如说数十秒,那这种现象很大程度上暗示着内存泄漏。(内存中对象太多,导致遍历时间太长,有时候不好的缓存机制会造成这样的问题)

3. 常驻内存区(P)的使用率:  常驻内存如果在应用程序稳定运行较长一段时间后还在持续增长,或者在某段某几段时刻有突变,则有可能有内存问题。(当然很大可能是jvm/app server配置问题) 如果P始终停留在某个值,说明常驻内存没有突变,比较正常。

 

 

修改JAVA_OPTS的值

增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

 

 

JavaOutOfMemoryError(内存溢出)的三种情况及解决办法

第一种OutOfMemoryError: PermGen space

发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:

1. 增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行:

JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"

如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。

2. 清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。

 

第二种OutOfMemoryError:  Java heap space

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:

1. 检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。

我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Java heap space的内存溢出问题,后来通过修改程序得到了解决。

2. 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

 

第三种OutOfMemoryError:unable to create new native thread

这种错误在Java线程个数很多的情况下容易发生,我暂时还没遇到过,发生原意和解决办法可以参考:http://hi.baidu.com/hexiong/blog/item/16dc9e518fb10c2542a75b3c.html

 


标签: jvm调优 java

阅读(207)┆ 评论(0) ┆ (0) ┆ 返回博客首页


发表我的评论

欢迎您: | 退出登录


文章评论