在项目中遇到OOM(Out of Memory)的问题,为了分析内存和JVM的垃圾回收器GC问题,一并把JVM相关的一些工具也研究了一下:
- jps:Java进程查看工具,实际上它和Unix/Linux上面的ps命令的功能差不多
- jstat:Java内存使用情况监控工具
- jmap:输出JVM内存中对象的工具
这些工具位于JAVA_HOME/bin目录下
?
一,jps (Java Virtual Machine Process Status Tool) JVM进程状态工具
jps用来查看host上运行的所有java进程的pid(lvmid),一般情况下使用这个工具的目的只是为了找出运行的jvm进程ID,即lvmid,然后可以进一步使用其它的工具来监控和分析JVM,因此可以说这个工具并没有太多的功能。
如我本地只启动了visualVM,jps可以看到
1 2 | 2156 Jps 1932 Main |
常用的几个参数:
-l 输出java应用程序的main class的完整包
-q 仅显示pid,不显示其它任何相关信息
-m 输出传递给main方法的参数
-v 输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置
如使用 jps -l -v 命令可以输出如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | C:\Oracle\Middleware\jdk160_05\bin>jps -l -v 5780 sun.tools.jps.Jps -Dapplication.home=C:\Oracle\Middleware\jdk160_05 -Xms8m 1932 org/netbeans/Main -Xms24m -Xmx192m -Dnetbeans.accept_license_class=com.sun.tools.visualvm.modules.startup.AcceptLicense -Dsun.jvmstat.perdata.syncWaitMs=10000 -Djdk.home=C:/Oracle/Middleware/jdk160_05 -Dnetbeans.home=D:\Arone.Zhang\jvm\visualvm_111\platform9 -Dnetbeans.dirs=D:\Arone.Zhang\jvm\visualvm_111\visualvm;D:\Arone.Zhang\jvm\visualvm_111\profiler3 -Dnetbeans.user=C:\Users\Arone.Zhang\AppData\Roaming\.visualvm\1.1.1 -Dnetbeans.system_http_proxy=DIRECT -Dnetbeans.system_http_non_proxy_hosts= -Dsun.awt.keepWorkingSetOnMinimize=true |
?
jps的官方描述:http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jps.html
?
二,jstat (Java Virtual Machine Statistics Monitoring Tool) JVM统计信息监控工具
jstat根据lvmid来实时监控Java应用程序的资源和性能,包括jvm的heap size和GC的回收状况,比起jps工具jstat要强大得多,同时使用起来也较复杂。
?
jstat有如下的生成结果选项(通过 jstat –options 来查看),jstat需要列出lvmid的统计信息,因此需要和jps配合使用
-class :显示加载类和卸载类的数量、所占空间大小和加载类和卸载类所花的时间
-compiler:显示VM实时编译的统计信息,包括编译任务执行的数量、编译任务失败的数量、变为无效的编译任务数量、执行编译任务所花费的时间等等
-gc:显示GC的堆信息,包括存活区、新生代区、永久区、发生GC的次数和所花费的时间
-gccapacity:显示JVM内存池中三代(young、old、perm)对象的使用和占用能力
-gccause:这个和-gcutil选项一样都是显示GC统计信息的汇总信息,只是包括了最后GC事件和当前GC事件
-gcnew:显示新生代对象的统计信息
-gcnewcapacity:显示新生代对象的信息和占用量
-gcold:显示老一代对象的统计信息
-gcoldcapacity:显示老一代对象的信息和占用量
-gcpermcapacity:显示永久代对象的信息和占用量
-gcutil:显示GC统计信息的汇总信息
-printcompilation:打印当前VM执行的信息
?
此工具中涉及到的永久代、老一代和新生代的概念和jdk的GC算法有密切的关系,关于GC的内容请查看Tuning Garbage Collection with the 5.0 JavaTM Virtual Machine
jstat使用的除了上面生成结果选项之外,还有收集统计信息的时间间隔和统计的次数,同时还可以控制输出结果的列标题如何出现。
1 2 3 4 5 6 7 8 | C:\Oracle\Middleware\jdk160_05\bin>jstat -gcnew -t -h3 1932 30 5 Timestamp S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 26638.9 192.0 192.0 0.0 192.0 1 15 96.0 1728.0 1472.7 12737 17.803 26639.0 192.0 192.0 0.0 192.0 1 15 96.0 1728.0 1472.7 12737 17.803 26639.0 192.0 192.0 0.0 192.0 1 15 96.0 1728.0 1472.7 12737 17.803 Timestamp S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 26639.0 192.0 192.0 0.0 192.0 1 15 96.0 1728.0 1472.7 12737 17.803 26639.1 192.0 192.0 0.0 192.0 1 15 96.0 1728.0 1472.7 12737 17.803 |
?
上面的 -t 选项是用来打印timestamp,-hN选项是用来控制打印列标题,这个选项对于多屏输出结果的时候比较有用,因此上面的jstat -gcnew -t -h3 1932 30 5 代表针对lvmid为 1932 的Java进程收集新生代对象统计信息:包括打印时间戳、每隔3行打印一次标题、30ms统计一次、共统计5次
每个选项输出列的含义请查看官方文档,jstat的官方描述:http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstat.html
虽然jstat提供了监控GC状态的强大功能,但是由于它是命令行,在实际问题的分析过程中难度比较高,因此市场上面出现了很多基于此命令的可视化工具,开源工具visualVM就是其中的佼佼者,JDK1.6之后,它已经包括在jdk中了。
一些术语的中文解释:
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 : 最大持有次数限制
三,jmap (Memory Map) JVM内存对象打印工具
jmap根据lvmid来Java进程的内存所有对象
?
首先根据选项打印出Java对象堆的直方图信息,包括Java类、类实例对象数量、在内存中占有量的大小以及类的完整包名,下面是缩减版本的直方图信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | C:\Oracle\Middleware\jdk160_05\bin>jmap -histo 3432 num #instances #bytes class name ---------------------------------------------- 1: 14644 20883096 [I 2: 69176 7689640 3: 111994 6561320 [C 4: 69176 5542192 5: 1933 4825592 [Ljavax.swing.text.GapContent$MarkData; 6: 100440 4480456 7: 6506 3771008 8: 90461 3618440 org.openide.util.RequestProcessor$Item 9: 90239 3609560 org.openide.util.RequestProcessor$EnqueueTask ...... 3315: 1 8 sun.misc.Launcher$Factory 3316: 1 8 org.netbeans.core.windows.WindowSystemImpl 3317: 1 8 javax.swing.filechooser.WindowsFileSystemView 3318: 1 8 org.netbeans.ModuleFactory Total 1767075 103137272 |
jmap -heap pid
查看java 堆(heap)使用情况
using thread-local object allocation.
Parallel GC with 4 thread(s) //GC 方式
Heap Configuration: //堆内存初始化配置
MinHeapFreeRatio=40 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio=70 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize=512.0MB //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 1.0MB //对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
MaxNewSize =4095MB //对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
OldSize = 4.0MB //对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
NewRatio = 8 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 //对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize= 16.0MB //对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
MaxPermSize=64.0MB //对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小
Heap Usage: //堆内存分步
PS Young Generation
Eden Space: //Eden区内存分布
capacity = 20381696 (19.4375MB) //Eden区总容量
used = 20370032 (19.426376342773438MB) //Eden区已使用
free = 11664 (0.0111236572265625MB) //Eden区剩余容量
99.94277218147106% used //Eden区使用比率
From Space: //其中一个Survivor区的内存分布
capacity = 8519680 (8.125MB)
used = 32768 (0.03125MB)
free = 8486912 (8.09375MB)
0.38461538461538464% used
To Space: //另一个Survivor区的内存分布
capacity = 9306112 (8.875MB)
used = 0 (0.0MB)
free = 9306112 (8.875MB)
0.0% used
PS Old Generation //当前的Old区内存分布
capacity = 366280704 (349.3125MB)
used = 322179848 (307.25464630126953MB)
free = 44100856 (42.05785369873047MB)
87.95982001825573% used
PS Perm Generation //当前的 “永生代” 内存分布
capacity = 32243712 (30.75MB)
used = 28918584 (27.57891082763672MB)
free = 3325128 (3.1710891723632812MB)
89.68751488662348% used
3. jmap - dump pid
将内存使用的详细情况输出到文件
map -dump:format=b,file= m.dat pid
用jhat命令可以参看 jhat -port 5000 m.dat
在浏览器中访问:http://localhost:5000/ 查看详细信息
其次是将内存堆的信息dump成为一个二进制文件,供jhat这样的Java内存分析工具来使用,如下降Java内存对象堆dump为heap.bin文件:
1 2 3 | C:\Oracle\Middleware\jdk160_05\bin>jmap -dump:format=b,file=heap.bin 3432 Dumping heap to C:\Oracle\Middleware\jdk160_05\bin\heap.bin ... Heap dump file created |
命令运行完成之后,在同目录下会生成一个heap.bin二进制内存对象堆文件
?
此工具使用比较简单,但是它对于分析内存溢出问题非常有用,目前有很多用来分析Java内存对象的工具,如收费的工具有:jprofiler,YourKit Java Profiler;而像Eclipse MAT则是优秀的内存对象分析开源工具,后续将整理相关的文章。
另附:jvm启动参数
一:JVM启动参数共分为三类:
其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容;
其二是非标准参数(-X),指的是JVM底层的一些配置参数,这些参数在一般开发中默认即可,不需要任何配置。但是在生产环境中,并不保证所有jvm实现 都满足,所以为了提高性能,往往需要调整这些参数,以求系统达到最佳性能。另外这些参数不保证向后兼容,也即是说“如有变更,恕不在后续版本的JDK通 知”(这是官网上的原话);
其三是非Stable参数(-XX),这类参数在jvm中是不稳定的,不适合日常使用的,后续也是可能会在没有通知的情况下就直接取消了,需要慎重使用。
二:而JVM内存又可分为三个主要的域:
新域、旧域以及永久域。JVM生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便进入旧域。而在永久域中是用来存储JVM自己的反 射对象的,如class和method对象,而且GC(Garbage Collection)不会在主程序运行期对永久域进行清理。其中新域和旧域属于堆,永久域是一个独立域并且不认为是堆的一部分。
三:各主要参数的作用如下:
-Xms:设置jvm内存的初始大小
-Xmx:设置jvm内存的最大值
-Xmn:设置新域的大小(这个似乎只对 jdk1.4来说是有效的,后来就废弃了)
-Xss:设置每个线程的堆栈大小(也就是说,在相同物理内存下,减小这个值能生成更多的线程)
-XX:NewRatio :设置新域与旧域之比,如-XX:NewRatio = 4就表示新域与旧域之比为1:4
-XX:NewSize:设置新域的初始值
-XX:MaxNewSize :设置新域的最大值
-XX:MaxPermSize:设置永久域的最大值
-XX:SurvivorRatio=n:设置新域中Eden区与两个Survivor区的比值。(Eden区主要是用来存放新生的对象,而两个Survivor区则用来存放每次垃圾回收后存活下来的对象)