Java内存占用分析教程
一、jstat查看 gc实时执行情况
jstat命令命令格式:
jstat [Options] vmid [interval] [count]
命令参数说明:
Options,一般使用 -gcutil 或 -gc 查看gc 情况
pid,当前运行的 java进程号
interval,间隔时间,单位为秒或者毫秒
count,打印次数,如果缺省则打印无数次
Options 参数如下:
-gc:统计 jdk gc时 heap信息,以使用空间字节数表示
-gcutil:统计 gc时, heap情况,以使用空间的百分比表示
-class:统计 class loader行为信息
-compile:统计编译行为信息
-gccapacity:统计不同 generations(新生代,老年代,持久代)的 heap容量情况
-gccause:统计引起 gc的事件
-gcnew:统计 gc时,新生代的情况
-gcnewcapacity:统计 gc时,新生代 heap容量
-gcold:统计 gc时,老年代的情况
-gcoldcapacity:统计 gc时,老年代 heap容量
-gcpermcapacity:统计 gc时, permanent区 heap容量
示例
$ jstat -gc 12538 5000
每5 秒一次显示进程号为 12538的 java进成的 GC情况,结果如下图:
image.png结果说明
标志 说明
S0C 年轻代中第一个survivor区的容量 (字节)
S1C 年轻代中第二个survivor区的容量 (字节)
S0U 年轻代中第一个survivor区目前已使用空间 (字节)
S1U 年轻代中第二个survivor区目前已使用空间 (字节)
EC 年轻代中Eden的容量 (字节)
EU 年轻代中Eden目前已使用空间 (字节)
OC Old代的容量 (字节)
OU Old代目前已使用空间 (字节)
PC Perm(持久代)的容量 (字节)
PU Perm(持久代)目前已使用空间 (字节)
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT 从应用程序启动到采样时gc用的总时间(s)
NGCMN 年轻代(young)中初始化(最小)的大小 (字节)
NGCMX 年轻代(young)的最大容量 (字节)
NGC 年轻代(young)中当前的容量 (字节)
OGCMN old代中初始化(最小)的大小 (字节)
OGCMX old代的最大容量 (字节)
OGC old代当前新生成的容量 (字节)
PGCMN perm代中初始化(最小)的大小 (字节)
PGCMX perm代的最大容量 (字节)
PGC perm代当前新生成的容量 (字节)
S0 年轻代中第一个survivor区已使用的占当前容量百分比
S1 年轻代中第二个survivor区已使用的占当前容量百分比
E 年轻代中Eden已使用的占当前容量百分比
O old代已使用的占当前容量百分比
P perm代已使用的占当前容量百分比
S0CMX 年轻代中第一个survivor区的最大容量 (字节)
S1CMX 年轻代中第二个survivor区的最大容量 (字节)
ECMX 年轻代中Eden的最大容量 (字节)
DSS 当前需要survivor区的容量 (字节)(Eden区已满)
TT 持有次数限制
MTT 最大持有次数限制
jstatd 启动jvm 监控服务
它是一个基于 rmi的应用,向远程机器提供本机 jvm应用程序的信息。默认端口 1099。
$ jstatd -J-Djava.security.policy=my.policy
my.policy文件需要自己建立,是安全策略文件,因为 jdk对 jvm做了 jaas的安全检测,所以我们必须设置一些策略,使 jstatd被允许作网络操作,内容如下:
二、jmap查看各个代的内存使用
jmap 可以从 core文件或进程中获得内存的具体匹配情况,包括 Heap size, Perm size等等。
jmap命令格式:
jmap [ option ] <pid> | <executable core> | <[server-id@]remote-hostname-or-IP>
1)参数说明
pid:java进程 id
executable:产生 core dump的 java可执行程序
core:core dump文件
remote-hostname-or-IP:远程 debug服务的主机名或 ip
server-id:远程 debug服务的 id
2) option参数:
-heap
打印heap的概要信息,GC 使用的算法,heap的配置及使用情况 .
-histo[:live]
打印jvm heap 的直方图。输出类名、每个类的实例数目、对象占用大小。 VM的内部类名字开头会加上前缀 ”*”.
如果加上live 则只统计活的对象数量。
-dump:[live,]format=b,file=<filename>
使用hprof二进制形式,导出heap 内容到文件filename。
假如指定live 选项,那么只输出活的对象到文件 .
-finalizerinfo
打印正等候回收的对象的信息 .
-permstat
打印classload 和jvm heap 持久代的信息。
包含每个classloader 的名字、是否存活、地址、父 classloade、加载的 class数量、内部 String的数量和占用内存数。
-F
当pid没有响应的时候,与-dump或者 -histo共同使用,强制生成 dump文件或 histo信息 . 在这个模式下 ,live子参数无效 .
-J
传递参数给启动jmap 的jvm.
64位机上使用需要使用如下方式: jmap -J-d64 -heap pid
使用 jmap -heap pid ,可以查看各个代的内存使用情况。
可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况
$ jmap -dump:format=b,file=heapdump.hprof <pid>
导出heap dump到文件heapdump.hprof
$ jmap -histo 2083 | jmap -histo:live 2083
可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。
jmap输出的 class name结果中:
[C is a char[]
[S is a short[]
[I is a int[]
[B is a byte[]
[[I is a int[][]
heap输出实例
$ jmap -heap 12264
JVM version is 25.65-b01
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 8388608000 (8000.0MB)
NewSize = 2006515712 (1913.5625MB)
MaxNewSize = 2006515712 (1913.5625MB)
OldSize = 90636288 (86.4375MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 1805910016 (1722.25MB)
used = 788045040 (751.5383148193359MB)
free = 1017864976 (970.7116851806641MB)
43.637004779755316% used
Eden Space:
capacity = 1605304320 (1530.9375MB)
used = 630378472 (601.1757583618164MB)
free = 974925848 (929.7617416381836MB)
39.26847166274367% used
From Space:
capacity = 200605696 (191.3125MB)
used = 157666568 (150.36255645751953MB)
free = 42939128 (40.94994354248047MB)
78.59525982751757% used
To Space:
capacity = 200605696 (191.3125MB)
used = 0 (0.0MB)
free = 200605696 (191.3125MB)
0.0% used
concurrent mark-sweep generation:
capacity = 6382092288 (6086.4375MB)
used = 4612472232 (4398.79630279541MB)
free = 1769620056 (1687.6411972045898MB)
72.27210174745753% used
18841 interned Strings occupying 1633048 bytes.
jmap 相关工具
jmap dump生成的 heap dump文件,可以使用 IBM HeapAnalyzer分析工具分析
下载:
运行:java –Xmx800m –jar ha456.jar
三、jstack和 jinfo分析java core文件
jstack工具可以用来获得 core文件的 java stack和 native stack的信息,从而可以知道 java程序是如何崩溃和在程序何处发生问题。
另外,jstack 工具还可以附属到正在运行的 java程序中,看到 java程序的 java stack和native stack 的信息,如果现在运行的 java程序呈现 hung的状态, jstack是非常有用的。
命令格式:$ jstack pid
jinfo可以从 core文件里面知道崩溃的 Java应用程序的配置信息。
命令额格式:$ jinfo pid
要详细解释这两种异常,需要简单重提下Java内存模型。Java内存模型是描述Java程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节。在Java虚拟机中,内存分为三个代:新生代(New)、老生代(Old)、永久代(Perm)。
-
新生代New:新建的对象都存放这里;
-
老生代Old:存放从新生代New中迁移过来的生命周期较久的对象。新生代New和老生代Old共同组成了堆内存;
-
永久代Perm:是非堆内存的组成部分。主要存放加载的Class类级对象如class本身,method,field等等;
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
-
Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整;
-
代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用);
如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。从代码的角度,软件开发人员主要关注java.lang.OutOfMemoryError: Java heap space异常,减少不必要的对象创建同时避免内存泄漏。现在以一个实际的例子分析内存占用的故障排查。
通过top命令,发现PID为9004的Java进程一直占用比较高的内存不释放(24.7%),出现高内存占用的故障。
ps -mp 9004 -o THREAD,tid,time,rss,size,%mem
遗憾的是,发现PS命令可以查到具体进程的CPU占用情况,但是不能查到一个进程下具体线程的内存占用情况。只好寻求其他方法了,幸好Java提供了一个很好的内存监控工具:jmap命令——jmap命令有下面几种常用的用法:
-
jmap [pid]
-
jmap -histo:live [pid] >a.log
-
jmap -dump:live,format=b,file=xxx.xxx [pid]
用得最多是后面两个。其中,jmap -histo:live [pid] 可以查看当前Java进程创建的活跃对象数目和占用内存大小。jmap -dump:live,format=b,file=xxx.xxx [pid] 则可以将当前Java进程的内存占用情况导出来,方便用专门的内存分析工具(例如:MAT)来分析。这个命令对于分析是否有内存泄漏很有帮助。具体怎么使用可以查看本博的另一篇文章:利用Eclipse Memory Analyzer Tool(MAT)分析内存泄漏
** 这里详细介绍下jmap -histo:live [pid] 命令:**
从上图可以看出,int数组、constMethodKlass、methodKlass、constantPoolKlass都占用了大量的内存。特别是占用了大量内存的int数组,需要仔细检查相关代码。
最后,总结下排查内存故障的方法和技巧有哪些:
- top命令:Linux命令。可以查看实时的内存使用情况。
- jmap -histo:live [pid],然后分析具体的对象数目和占用内存大小,从而定位代码。
- jmap -dump:live,format=b,file=xxx.xxx [pid],然后利用MAT工具分析是否存在内存泄漏等等