现在的位置: 首页 > 综合 > 正文

JVM监控以及常见的内存异常原因

2013年06月30日 ⁄ 综合 ⁄ 共 8344字 ⁄ 字号 评论关闭

监控jvm状态可以在线监控和离线监控,第一部分讲解在线的方法,第二部分讲解离线的方法。

一、java监控命令

1、jps

列出所有的jvm实例

实例:

jps

列出本机所有的jvm实例

输出内容如下:

[root@master ~]# jps

4312 Resin

4461 Jps

下面所有的命令都需要用jps(或者ps –ef|grep java也行)得到的任务ID

2、jconsole

一个图形化界面,可以观察到java进程的gc,class,内存等信息。比较直观,但这个工具需要图形化界面的支持,在使用之前先输出export DISPLAY=:0.0;

否则无法启动图形化界面

3、jinfo

观察运行中的java程序的运行环境参数:参数包括Java System属性、JVM命令行参数、环境变量。

实例:jinfo 2083
其中2083就是java进程id号,可以用jps得到这个id号。

4、Jstack

可以观察到jvm中当前所有线程的运行情况和线程当前状态
jstack 2083

5、Jmap

观察运行中的jvm物理内存的占用情况。

还可以把当前jvm状态写文件heap.bin中(jmap -dump:format=b,file=heap.bin 2083)

参数如下:

-heap:打印jvm heap的情况

-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。

-histo:live :同上,但是只答应存活对象的情况

-permstat:打印permanent generation heap情况

jmap -heap 2083

可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况

jmap -histo:live 2083

可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。

Jstat。
这是jdk命令中比较重要,也是相当实用的一个命令,可以观察到classloader,compiler,gc相关信息
具体参数如下:
-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:不知道干什么的,一直没用过。

一般比较常用的几个参数是:
jstat -class 2083 1000 10 (每隔1秒监控一次,一共做10次)
输出内容含义如下:
Loaded    Number of classes loaded.
Bytes    Number of Kbytes loaded.
Unloaded    Number of classes unloaded.
Bytes    Number of Kbytes unloaded.
Time    Time spent performing class load and unload operations.

监控内存使用情况 参数 (查看内存溢出)

jstat -gcutil 2083 5000 10(每隔5秒监控一次,共10次)输出内容含义如下:

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:用于垃圾回收的总时间(单位秒)

 二、     离线分析jvm状态,用memory analyzer(MAT)

先用jmap把当前的jvm状态输出,然后再本地解决即可。

jmap -dump:format=b,file=heap.bin 线程ID

heap.bin就是jvm的当前状态的生成文件。

在xp下,我们用eclipse模拟一下。先找到eclipse的jvm设置处,打开jvm参数设定,其实这和java的参数设置是一样的,这里的参数会传给java进程的,只是参数格式稍稍不同。

在eclipse的jvm中设置参数如下

-Xmx128M -Xms128M -Xmn32M -Xss1M -XX:PermSize=32M -XX:MaxPermSize=64M -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log

其中:

-XX:+HeapDumpOnOutOfMemoryError:当jvm出现outofmemory的时候,把jvm状态记录下来,保存到java_pid[进程id].hprof

-verbose:gc -XX:+PrintGCDetails打印详细的gc日志。

-XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC

这4个选项打印更加详细的日志

-Xloggc:log/gc.log   这个是把日志输出到log,而不是stdout中。将GC日志输出到文件:不同JDK设置的参数不同,参考JDK官方文档
   SUN:-Xloggc:filename (例如:-Xloggc:gc.log)
   IBM:-Xverbosegc:file=filename 或 -Xverbosegclog:filename
   HP :-Xverbosegc=filename

这时,当java抛出outofmemory,就会在该项目的根目录下生成java_pid[进程ID].hprof。

从这里下载mat的压缩包,直接解压即可使用,也可以下载mat的eclipse插件。http://mirror.bit.edu.cn/eclipse/mat/1.2.1/rcp/MemoryAnalyzer-1.2.1.20121105-win32.win32.x86.zip

也可以下载mat的eclipse插件了。。。

解压后发现这个界面和eclipse一样,可以从正在运行的jvm中分析状态,也可以根据生成的文件分析状态。

点击File --- open heap dump ,选择dump文件就可以了,可以是这个eclipse本机生成的文件,也可以是服务器上jmap生成的文件。 

1、out of memory:heap space

1.1 准备内存溢出程序

import java.util.*;

import java.io.*;

public class T {

    public static void main(String args[]) throws UnsupportedEncodingException {

        Map m=new HashMap();

       for(long i=0;i<Long.MAX_VALUE;i++){

           m.put(i, new Date());

       }

    }

}

m对象一直持有,而无法得到回收,所以随着i增长,m越来越大。

1.2把上面的程序异常后生成的文件,在mat中打开。

1.3下图是结果页面,detail中可以看到无法到达的对象,也就是会被gc回收的。

 

中间的饼图是内存使用情况。

Action中的histogram是每个类的使用情况,点开后是下图

 

可以发现 hashmap、date、long是耗费内存最大的,和程序相符。

在reports中的leak suspects可以看到系统给出的建议,如下图:

 

提示java.util.hashmap占用内存过大,这和我们的程序一致。

2、out of memory: perm space

------------------------------------------------------------------------------------

java堆内存分为young(又可细分为Survivor0、Survivor1、elden)、old。

按照生存期可分为young、old、Permanent Generation

JVM Spec中的Runtime Data Area 分为:pc register、java stack、native stack、java heap、method area

其中Permanent Generation包括了method area(类声明、方法)、常量区(String常量、int常量等各种常量)等等

所以要撑爆perm,可以加载足够多的类、或者足够多的常量,比如string。

---------------------------------------------------------------------------------------------------------------------------------------

   public static void main(String[] args) {

       List<String> list = new ArrayList<String>();

        for (int i = 0; i < Integer.MAX_VALUE; i++) {

            String t = String.valueOf(i).intern();

            list.add(t);

        }

}

这样就会报错了,intern的作用是把当前使用的string对象,从常量池中获取,没有则先设置在常量池中再获取(和直接使用“”引号声明是一样的;但是因为String.valueOf(i)使用的是引用的方法,所以默认情况下会是一个对象,和使用new String()一样;所以这用intern强制使用常量池中的内容,以撑爆perm space)。

然后把文件用mat打开再看一下吧。分析perm异常用mat不方便,还是在gc中打log比较靠谱。

如果删除掉关于list的部分,上面的代码在执行的是否发现打印出了gc日志如下:

[Full GC [Tenured: 340K->340K(4096K), 0.0197170 secs] 959K->340K(5056K), [Perm : 4096K->799K(4096K)], 0.0199235 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]

也就是说perm内存中的常量池部分是可以被回收的。

三:Java内存泄露的原因多种多样,大多数可以从这里查起:

1、静态集合类,(比如静态的HashMap、Vector等)最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

2、大量临时变量的使用(比如我们上面的例子,for循环N次不释放),没有及时将对象设置为null也可能导致内存的泄露

3、数据库的连接没有关闭情况,包括连接池方法连接数据库,如果没有关闭ResultSet等也都可能出现内存泄露的问题。

4、内部类和外部类的引用容易出现内存泄露的问题

5、监听器的使用,java中往往会使用到监听器,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

四、JVM参数设置

resin tomcat weblogic等服务器都是类似的配置,在java命令后加上如下参数即可修改jvm状态
-Xmx128M -Xms128M -Xmn32M -Xss1M -XX:PermSize=32M -XX:MaxPermSize=64M -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log

其中:

-XX:+HeapDumpOnOutOfMemoryError:当jvm出现outofmemory的时候,把jvm状态记录下来,保存到java_pid[进程id].hprof

-verbose:gc -XX:+PrintGCDetails打印详细的gc日志。

-XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC

这4个选项打印更加详细的日志

-Xloggc:log/gc.log   这个是把日志输出到log,而不是stdout中。将GC日志输出到文件:不同JDK设置的参数不同,参考JDK官方文档
   SUN:-Xloggc:filename (例如:-Xloggc:gc.log)
   IBM:-Xverbosegc:file=filename 或 -Xverbosegclog:filename
   HP :-Xverbosegc=filename

下面再一次给出java后面的参数列表

1、 heap size
a: -Xmx<n>
指定 jvm 的最大 heap 大小,如:-Xmx=2g
b: -Xms<n>
指定 jvm 的最小 heap 大小,如:-Xms=2g,高并发应用建议和-Xmx一样,防止因为内存收缩/突然增大带来的性能影响。
c: -Xmn<n>
指定 jvm 中 New Generation 的大小,如:-Xmn256m。 这个参数很影响性能,如果你的程序需要比较多的临时内存,建议设置到512M,如果用的少,尽量降低这个数值,一般来说128/256足以使用了。

d: -XX:PermSize=<n>
指定 jvm 中 Perm Generation 的最小值,如:-XX:PermSize=32m。 这个参数需要看你的实际情况,可以通过jmap 命令看看到底需要多少。
e: -XX:MaxPermSize=<n>
指定 Perm Generation 的最大值,如:-XX:MaxPermSize=64m
f: -Xss<n>
指定线程桟大小,如:-Xss128k,一般来说,webx框架下的应用需要256K。 如果你的程序有大规模的递归行为,请考虑设置到512K/1M。 这个需要全面的测试才能知道。不过,256K已经很大了。 这个参数对性能的影响比较大的。

g: -XX:NewRatio=<n>
指定 jvm 中 Old Generation heap size 与 New Generation 的比例,在使用 CMS GC 的情况下此参数失效,如:-XX:NewRatio=2
h: -XX:SurvivorRatio=<n>
指定 New Generation 中 Eden Space 与一个 Survivor Space 的 heap size 比例,-XX:SurvivorRatio=8,那么在总共New Generation为10m 的情况下,Eden Space为8m
i: -XX:MinHeapFreeRatio=<n>
指定 jvm heap在使用率小 n的情况下,heap进行收缩,Xmx==Xms 的情况下无效,如:-XX:MinHeapFreeRatio=30
j: -XX:MaxHeapFreeRatio=<n>
指定 jvm heap在使用率大于n的情况下,heap进行扩张,Xmx==Xms 的情况下无效,如:-XX:MaxHeapFreeRatio=70
k: -XX:LargePageSizeInBytes=<n>
指定 Java heap 的分页页面大小,如:-XX:LargePageSizeInBytes=128m
2: garbage collector
a: -XX:+UseParallelGC
指 定在 New Generation 使用 parallel collector,并行收集,暂停app threads,同时启动多个垃圾回收thread,不能和 CMS gc 一起使用。系统吨吐量优先,但是会有较长长时间的app pause,后台系统任务可以使用此gc

b: -XX:ParallelGCThreads=<n>
指定 parallel collection 时启动的 thread 个数,默认是物理 processor 的个数。
c: -XX:+UseParallelOldGC
指定在 Old Generation 使用 parallel collector。
d: -XX:+UseParNewGC
指定在 New Generation 使用 parallel collector,是 UseParallelGC 的 gc 的升级版本,有更好的性能或者优点,可以和 CMS gc 一起使用。
e: -XX:+CMSParallelRemarkEnabled
在使用 UseParNewGC 的情况下,尽量减少 mark 的时间。
f: -XX:+UseConcMarkSweepGC
指 定在 Old Generation 使用 concurrent cmark sweep gc,gc thread 和 app thread 并行 ( 在 init-mark 和 remark 时 pause app thread)。app pause 时间较短,适合交互性强的系统,如 web server。
g: -XX:+UseCMSCompactAtFullCollection
在使用 concurrent gc 的情况下,防止 memory fragmention,对 live object 进行整理,使 memory 碎片减少。
h: -XX:CMSInitiatingOccupancyFraction=<n>
指示在 old generation 在使用了 n% 的比例后,启动 concurrent collector,默认值是 68,如:-XX:CMSInitiatingOccupancyFraction=70。
i: -XX:+UseCMSInitiatingOccupancyOnly
指示只有在 old generation 在使用了初始化的比例后 concurrent collector 启动收集。
3、others
a: -XX:MaxTenuringThreshold=<n>
指 定一个 object 在经历了 n 次 young gc 后转移到 old generation 区,在 linux64 的 java6 下默认值是 15,此参数对于 throughput collector 无效,如:-XX:MaxTenuringThreshold=31。
b: -XX:+DisableExplicitGC
禁止 java 程序中的 full gc,如 System.gc() 的调用. 最好加上么,防止程序在代码里误用了。对性能造成冲击。
c: -XX:+UseFastAccessorMethods
get,set 方法转成本地代码。
d: -XX:+PrintGCDetails

打应垃圾收集的情况如: [GC 15610.466: [ParNew: 229689K->20221K(235968K),0.0194460 secs] 1159829K->953935K(2070976K),0.0196420 secs]。
e: -XX:+PrintGCTimeStamps
打应垃圾收集的时间情况,如: [Times: user=0.09 sys=0.00,real=0.02 secs]。
f: -XX:+PrintGCApplicationStoppedTime
打应垃圾收集时,系统的停顿时间,如: Total time for which application threads were stopped: 0.0225920 seconds。

抱歉!评论已关闭.