JVM监控工具
Java的安装包自带了不少优秀的工具,善用这些工具对于监控和调试Java程序很是有帮助。经常使用工具以下:html
jps
用途:jps用来查看JVM里面全部进程的具体状态, 包括进程ID,进程启动的路径等等。java
经常使用参数:算法
-l: 输出完成的包名称;apache
-m: 输出程序的命令行输入参数;bootstrap
-v: 输出完整的JVM参数。api
jstack
用途:1)查看java程序崩溃生成core文件,得到core文件的java stack和native stack的信息;数组
2)查看正在运行的java程序的java stack和native stack的信息:a) 查看运行的java程序呈现hung的状态;b) 跟踪Java的调用栈,剖析程序。缓存
jinfo
用途:jinfo可观察运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数;也可从core文件里面知道崩溃的Java应用程序的配置信息。tomcat
jstat
用途:jstat利用了JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收情况的监控等等。bash
语法结构:
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
参数解释:
Options — 选项,咱们通常使用 -gcutil 查看gc状况
vmid — VM的进程号,即当前运行的java进程号
interval– 间隔时间,单位为秒或者毫秒
count — 打印次数,若是缺省则打印无数次
具体option参数以下:
-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状况
输出内容含义以下:
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 — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
示例
实例使用1:
[root@localhost bin]# jstat -gcutil 25444
S0 S1 E O P YGC YGCT FGC FGCT GCT
11.63 0.00 56.46 66.92 98.49 162 0.248 6 0.331 0.579
实例使用2:
[root@localhost bin]# jstat -gcutil 25444 1000 5
S0 S1 E O P YGC YGCT FGC FGCT GCT
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
73.54 0.00 99.04 67.52 98.49 166 0.252 6 0.331 0.583
咱们能够看到,5次young gc以后,垃圾内存被从Eden space区(E)放入了Old space区(O),并引发了百分比的变化,致使Survivor space使用的百分比从73.54%(S0)降到0%(S1)。有效释放了内存空间。绿框中,咱们能够看到,一次full gc以后,Old space区(O)的内存被回收,从99.05%降到67.52%。
图中同时打印了young gc和full gc的总次数、总耗时。而,每次young gc消耗的时间,能够用相间隔的两行YGCT相减获得。每次full gc消耗的时间,能够用相隔的两行FGCT相减获得。例如红框中表示的第一行、第二行之间发生了1次young gc,消耗的时间为0.252-0.252=0.0秒。
常驻内存区(P)的使用率,始终停留在98.49%左右,说明常驻内存没有突变,比较正常。
若是young gc和full gc可以正常发生,并且都能有效回收内存,常驻内存区变化不明显,则说明java内存释放状况正常,垃圾回收及时,java内存泄露的概率就会大大下降。但也不能说明必定没有内存泄露。
GCT 是YGCT 和FGCT的时间总和。
以上,介绍了Jstat按百分比查看gc状况的功能。其实,它还有功能,例如加载类信息统计功能、内存池信息统计功能等,那些是以绝对值的形式打印出来的,比较少用,在此就不作介绍。
[root@localhost bin]# ps -ef | grep java
root 25917 1 2 23:23 pts/2 00:00:05 /usr/local/jdk1.5/bin/java -Djava.endorsed.dirs=/usr/local/jakarta-tomcat-5.0.30/common/endorsed -classpath /usr/local/jdk1.5/lib/tools.jar:/usr/local/jakarta-tomcat-5.0.30/bin/bootstrap.jar:/usr/local/jakarta-tomcat-5.0.30/bin/commons-logging-api.jar -Dcatalina.base=/usr/local/jakarta-tomcat-5.0.30 -Dcatalina.home=/usr/local/jakarta-tomcat-5.0.30 -Djava.io.tmpdir=/usr/local/jakarta-tomcat-5.0.30/temp org.apache.catalina.startup.Bootstrap start
jstat -class pid:显示加载class的数量,及所占空间等信息。
实例使用3:
[root@localhost bin]# jstat -class 25917
Loaded Bytes Unloaded Bytes Time
2629 2916.8 29 24.6 0.90
jstat -compiler pid:显示VM实时编译的数量等信息。
实例使用4:
[root@localhost bin]# jstat -compiler 25917
Compiled Failed Invalid Time FailedType FailedMethod
768 0 0 0.70 0
jstat –gccapacity :能够显示,VM内存中三代(young,old,perm)对象的使用和占用大小,如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其余的能够根据这个类推, OC是old内纯的占用量。
[root@localhost bin]# jstat -gccapacity 25917
NGCMN 640.0
NGCMX 4992.0
NGC 832.0
S0C 64.0
S1C 64.0
EC 704.0
OGCMN 1408.0
OGCMX 60544.0
OGC 9504.0
OC 9504.0 OC是old内纯的占用量
PGCMN 8192.0 PGCMN显示的是最小perm的内存使用量
PGCMX 65536.0 PGCMX显示的是perm的内存最大使用量
PGC 12800.0 PGC是当前新生成的perm内存占用量
PC 12800.0 PC是但前perm内存占用量
YGC 164
FGC 6
jstat -gcnew pid: new对象的信息
[root@localhost bin]# jstat -gcnew 25917
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
64.0 64.0 47.4 0.0 2 15 32.0 704.0 145.7 168 0.254
jstat -gcnewcapacity pid: new对象的信息及其占用量
[root@localhost bin]# jstat -gcnewcapacity 25917
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
640.0 4992.0 832.0 64.0 448.0 448.0 64.0 4096.0 704.0 168 6
jstat -gcold pid: old对象的信息。
[root@localhost bin]# jstat -gcold 25917
PC PU OC OU YGC FGC FGCT GCT
12800.0 12617.6 9504.0 6561.3 169 6 0.335 0.591
jstat -gcoldcapacity pid:old对象的信息及其占用量。
[root@localhost bin]# jstat -gcoldcapacity 25917
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
1408.0 60544.0 9504.0 9504.0 169 6 0.335 0.591
jstat -gcpermcapacity pid: perm对象的信息及其占用量。
[root@localhost bin]# jstat -gcpermcapacity 25917
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
8192.0 65536.0 12800.0 12800.0 169 6 0.335 0.591
jstat -printcompilation pid:当前VM执行的信息。
[root@localhost bin]# jstat -printcompilation -h3 25917 1000 5
每1000毫秒打印一次,一共打印5次,还能够加上-h3每三行显示一下标题。
Compiled Size Type Method
788 73 1 java/io/File <init>
788 73 1 java/io/File <init>
788 73 1 java/io/File <init>
Compiled Size Type Method
788 73 1 java/io/File <init>
788 73 1 java/io/File <init>
jmap
用途:观察运行中的jvm物理内存的占用状况,包括Heap size, Perm size等等。
参数以下:
-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 2083 | jmap -histo:live 2083 ---- 观察heap中全部对象的状况(heap中全部生存的对象的状况)。包括对象数量和所占空间大小。
jmap -dump:live,format=b,file=heap.bin 2083 ---- dump java heap in hprof binary format。输出文件可用于进一步分析。
class name对应的就是Class文件里的class的标识
B表明byte
C表明char
D表明double
F表明float
I表明int
J表明long
Z表明boolean
前边有[表明数组,[I 就至关于int[]
对象用[L+类名表示
BaseType Character | Type | Interpretation |
B | byte | signed byte |
C | char | Unicode character |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L<classname>; | reference | an instance of class de><classname>de> |
S | short | signed short |
Z | boolean | de>truede> or de>falsede> |
[ | reference | one array dimension |
Java调优
使用Memory Analyzer tool(MAT)分析内存泄漏
JVM常见参数及其默认值:
(详见:JVM系列三:JVM参数设置、分析)
参数名称 | 含义 | 默认值 | |
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数能够调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数能够调整)空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不一样的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减少年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 |
|
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | ||
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | |
-Xss | 每一个线程的堆栈大小 | JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减少这个值能生成更多的线程.可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右 通常小的应用, 若是栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,须要严格的测试。(校长) 和threadstacksize选项解释很相似,官方文档彷佛没有解释,在论坛中有这样一句话:"” -Xss is translated in a VM flag named ThreadStackSize” 通常设置这个值就能够了。 |
JVM配置示例:
JAVA_OPTS="-server -XX:+UseConcMarkSweepGC -XX:PermSize=100M -XX:MaxPermSize=300M -Xms8g -Xmx8g -Xmn4g -Xss256k -XX:MaxNewSize=4g -XX:NewSize=4g -XX:SurvivorRatio=2 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=92 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintHeapAtGC -Xloggc:/data/resys/zhenjing/tomcat-solr-slave/logs/gc.log -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Duser.timezone=Asia/Shanghai -XX:MaxDirectMemorySize=7g -Dcom.tc.productkey.path=/data/conf/terracotta-license.key"
机器配置:4CPU,16G内存
-server -Xms8g -Xmx8g -XX:PermSize=256m -XX:MaxPermSize=256m -Xmn4g -XX:MaxDirectMemorySize=512m -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000 -XX:ParallelGCThreads=4 -Xloggc:/data/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/java.hprof -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=30000 -Dfile.encoding=UTF-8 -Ddubbo.application.logger=slf4j -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8082 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.2.113.82
说明:
# 各个堆栈区域空间分配 -server -XX:PermSize=100M -XX:MaxPermSize=300M -Xms8g -Xmx8g -Xmn4g -Xss256k -XX:MaxNewSize=4g -XX:NewSize=4g -XX:SurvivorRatio=2 # eden = from+to # 回收算法相关。该配置以知足响应实时性为目的。 -Xnoclassgc #禁用类垃圾回收,性能会高一点; -XX:+DisableExplicitGC #禁止System.gc() -XX:+UseConcMarkSweepGC #老年代回收采用CMS算法 -XX:+UseParNewGC #年轻代回收采用并行算法 -XX:ParallelGCThreads=8 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=92 -XX:SoftRefLRUPolicyMSPerMB=0 # gc日志相关。 采用 jstat -gcutil PID 10000 监控更简洁直观。 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintHeapAtGC -Xloggc:logs/gc.log -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Duser.timezone=Asia/Shanghai # BigMemory for Ehcache 相关配置,启用本地缓存,避免触发gc。 -XX:MaxDirectMemorySize=7g -Dcom.tc.productkey.path=/data/conf/terracotta-license.key
几点说明:
Young GC: 通常状况下,当生成新对象,触发Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到空的Survivor区(to Survivor)。而后整理上次存放数据的Survivor(from Survivor),将依然存活的对象放入新的to Survivor。若屡次整理后,对象依然存活,且达到阈值(由 InitialTenuringThreshold 和 MaxTenuringThreshold 控制,默认值为7和15),则将对象放入老年代。
Full GC: 主要是回收老年代的失效对象,通常同时触发整理年轻代。
几个参考数据:gc的核心工做是:清除无效对象;移动有效对象。若须要清除的无效对象不少,或须要移动的有效对象不少,则gc过程耗时。对于4G的年轻代,2G的eden代,一次Young GC大概耗时100~200ms。 若实际应用中,Young GC超过200ms,通常须要调优JVM。调优的目标:1)下降单次Young GC时间; 2) 下降Full GC次数。 Young GC 的次数和程序的负载和对象使用方式(代码实现方式)有关,优化空间不大。
测试过 -XX:+AggressiveHeap 选项,对gc影响不大。测试过 -XX:+UseFastAccessorMethods -XX:+AggressiveOpts -XX:+UseBiasedLocking 选项,对gc影响不大。
对于-XX:LargePageSizeInBytes=128m选项,有些麻烦,且可能致使Full GC变长,没试用。参见:http://hllvm.group.iteye.com/group/wiki/2870-JVM
Java调试辅助工具
连接的jar库可能包含重复的class,致使行为错误。
findclass.sh 用于查找特定类名出如今哪些jar包。
#! /bin/bash if [ $# -lt 2 ]; then echo "Usage: $0 classname jar1 jar2..." exit -1 fi classname=$1 echo "Find Class: $classname" shift for file in $* do # echo $file suffix=${file##*.} if [ "x$suffix" == "xjar" ]; then grepret=`jar -tf $file | grep $classname` # echo $grepret if [ "x$grepret" != "x" ]; then echo "$file" fi fi done
jps_kill.sh 用于kill指定类名的的java进程。JVM很耗资源,当启动的JVM达到几百个时,系统负载很重,会致使jps运行变慢(一、负载重;二、进程多)。
#! /bin/bash TMP=/tmp/jps_kill.log if [ $# -lt 1 ] then echo "Usage: $0 prog_name" exit -1 fi jps | grep -i $1 > $TMP while read pid other do echo $pid #echo $other kill -9 $pid done < $TMP
gcstat.sh 长期监控JVM的gc情况。
#!/bin/bash PID=-1 INTERVAL=1000 NUM=60000000 if [[ $# -lt 1 ]] then echo "Usage: $0 <vmid> [<interval(ms)> [<count>]]" exit 1 fi PID=$1 if [[ $# -gt 1 ]] then INTERVAL=$2 fi if [[ $# -gt 2 ]] then NUM=$3 fi echo "$0 Start test...`date`" echo jstat -gcutil -t -h50 $PID $INTERVAL $NUM echo "$0 Finish test.`date`"
jmap_stat.sh 长期监控jmap情况。
#!/bin/bash PID=-1 INTERVAL=3 NUM=60 if [[ $# -lt 1 ]] then echo "Usage: $0 <vmid> [<interval> [<count>]]" exit 1 fi PID=$1 if [[ $# -gt 1 ]] then INTERVAL=$2 fi if [[ $# -gt 2 ]] then NUM=$3 fi echo "$0 Start test...`date`" echo i=0 while [[ i -lt $NUM ]] do echo `date` jmap -histo:live $PID | head -n 20 echo sleep $INTERVAL ((i++)) done echo "$0 Finish test.`date`"
Java调试辅助函数
调试Java程序常常须要知道Java进程PID、当前目录、Java Home、内存使用状况、执行特定命令等等。JavaDebug收集了上述这些经常使用的辅助调试函数。
import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.io.File; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Iterator; import java.util.Map; import java.util.Properties; public class JavaDebug{ public static int getPid() { RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); String name = runtime.getName(); // format: "pid@hostname" try { return Integer.parseInt(name.substring(0, name.indexOf('@'))); } catch (Exception e) { System.err.println(e.getMessage()); return -1; } } public static void printEnv(){ Map<String,String> map = System.getenv(); Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator(); Map.Entry<String, String> entry; while (iter.hasNext()) { entry = iter.next(); String name = entry.getKey(); String value = entry.getValue(); System.out.println(name + ": "+ value); } } public static void printProperties(){ Properties pros = System.getProperties(); pros.list(System.out); } public static String getPWD(){ return System.getProperty("user.dir"); } public static void printSimpleMemoryInfo(){ Runtime runTime = Runtime.getRuntime(); System.out.println("Total Mem:" + runTime.totalMemory() + " Max Mem:" +runTime.maxMemory() + " Free mem:" + runTime.freeMemory() ); } public static void printMemoryInfo(String info){ if( info != null){ System.out.println(info+ " JavaDebug:printMemoryInfo"); } printSimpleMemoryInfo(); ExecCommand("jmap " + getPid()); } public static void ExecCommand(String cmd){ System.out.println("Exec command: "+cmd); try{ Process pro = null; Runtime runTime = Runtime.getRuntime(); if (runTime == null) { System.err.println("Create runtime false!"); throw new RuntimeException("Create runtime false!"); } pro = runTime.exec(cmd); BufferedReader input = new BufferedReader(new InputStreamReader(pro.getInputStream())); PrintWriter output = new PrintWriter(new OutputStreamWriter(pro.getOutputStream())); String line; while ((line = input.readLine()) != null) { System.out.println(line); } input.close(); output.close(); pro.destroy(); } catch(IOException e) { System.err.println(e.getMessage()); e.printStackTrace(); throw new RuntimeException("run command error!"); } System.out.println("Exec command: "+cmd + " finish."); } public static void main(String[] args) throws Exception { printProperties(); //printEnv(); System.out.println("========================"); int pid = getPid(); System.out.println("pid: " + pid); System.out.println("pwd: " + getPWD()); printMemoryInfo(null); ExecCommand("ls -l"); ExecCommand("jmap " + pid); printMemoryInfo(null); } }