在我们的日常开发中,当程序出现bug的时候我们通常做的就是debug在控制台看异常信息,但是对于线上的问题以及一些更深层次的比如虚拟机的问题,其实了解了jdk常用的命令行工具,更有利于我们找到程序发生的问题。复制代码
常用命令
jps:虚拟机进程状态工具
简介:jps(Java Virtual Machine Process Status Tool)是jdk自带的命令工具,用来显示java进程的一些情况。复制代码
使用
jps
jps -q 只输出进程的pid
jps -l 输出类的全名,如果执行的是jar包,则执行jar包的路径
下面我们给TestJpaCommandDemo的main函数String[] args在启动时添加参数 test jps
public class TestJpaCommandDemo { public static void main(String[] args) { while ( true ) { try { Thread.sleep(1000); System.out.println("jps"); } catch (InterruptedException e) { e.printStackTrace(); } } }}复制代码运行程序(这里我们只关注TestJpaCommandDemo的main方法),可以看到我们配置的参数
jps -m 输出虚拟机程序启动时传递给main函数的方法
jps -v 输出虚拟机陈谷启动时jvm的参数
jinfo:java配置信息工具
简介:jinfo的作用是实时的查看和调整虚拟机运行的参数。前面我们知道jps-v也可以查看虚拟机参数,但是那个参数是显示的指定的,一些默认的参数我们是看不出来的。jinfo 系列的命令可以查看,而且还可以在程序运行期间修改允许被修改的参数。复制代码
使用
jinfo pid 查看所有的参数 由于篇幅较长一些运行的jar包完整路径就不截取了
下面举几个例子
jinfo -flag MaxHeapSize pid
jinfo -flag MaxTenuringThreshold 22224( 新生代对象晋升到老年代对象的最大年龄)
jinfo -flag PrintGCDetails 22224(是否打印gc详细信息)
通过命令修改开启打印GC信息
可以看到变为开启的状态了jstat: 虚拟机统计信息监视工具
简介:jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[1]虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟 机性能问题的首选工具。复制代码
使用
参数解释:- option 代表我们所要查询的虚拟机细信息 主要包括:类装载,垃圾回收,运行期编译情况
- vmid 若是运行在本地的虚拟机进程那就是程序运行的pid,若是远程虚拟机,格式应该是 [protocol:][//]lvmid[@hostname[:port]/servername]
- interval 查询间隔
- count 查询次数
忽略interval和count参数,那么命令只会执行一次 比如 jstat -gc 3776 200 5 表示每200ms查询一次3776进程的垃圾回收情况,一共执行5次
常见的option如图:
jstat -class pid :监视类装载,卸载数量,总空间以及类装载多需要的时间。
loaded:已装载的类的数量; Bytes:装在类的字节数; Unloaded:卸载类的数量; Bytes:卸载类的字节数;Time:装载以及卸载类所花费时间jstat -compiler pid :输出JIT编译器编译过的方法,耗时等信息。
compiled:编译任务执行数量;failed:编译失败的数量;Invalid:变异任务执行失效数量;Time:编译时间; FailedType:最后一个编译失败的类型;FailedMethod:最后一个编译失败的任务所在的类和方法jstat -printcompilation pid :输出已经被JIT编译器编译过的方法。
Complied:编译任务的数量;size:发放生成的字节码的大小;Type:编译类型;Method:类名和方法名。jstat -gc pid :监视java堆情况,包括Eden区,两个survivor区,老年代,永久代等容量,已用时间,gc合计时间等
S0C: 年轻代中第一个survivor(幸存区)的容量(字节); S1C:年轻代中第二个survivor(幸存区)的容量; S0U:年轻代中第一个survivor(幸存区)已使用空间; S1U:年轻代中第二个survivor(幸存区)已使用空间; EC:年轻代中Eden(伊甸园)的容量 (字节); EU:年轻代中Eden(伊甸园)的已使用的空间 (字节); OC:老年代(old)的容量(字节); OU:老年代(old)已使用空间; PC:Perm(持久代)的容量 (字节); PU:Perm(持久代)的已使用空间 (字节); YGC:从应用程序启动到执行命令时年轻代中gc次数; YGCT:从应用程序启动到执行命令时年轻代中gc所占用时间; FGC:从应用程序启动到执行命令时老年代gc次数; FGCT:从应用程序启动到执行命令时老年代gc占用时间; GCT:从应用程序启动到执行命令时总gc占用时间复制代码
jstat -gccapacity pid :监视内容与 jstat -gc 基本相同,但输出主要关注java堆各个区域用到的最大最小空间
NGCMN:年轻代(young)中初始化(最小)的大小(字节); NGCMX:年轻代(young)的最大容量 (字节) ; NGC:年轻代(young)中当前的容量 (字节) ; S0C:年轻代中第一个survivor(幸存区)的容量 (字节) ; S1C:年轻代中第二个survivor(幸存区)的容量 (字节) ; OGCMN:old代中初始化(最小)的大小 (字节) ; OGCMX:old代最大容量(字节); OGC:old代当前新生成的容量 (字节) ; OC:old代的容量(字节); PGCMN:perm代中初始化(最小)的大小 (字节) ; PGCMX:perm代的最大容量 (字节) PC:Perm(持久代)的容量 (字节) ; YGC:从应用程序启动到执行命令时年轻代中gc次数; FGC: 从应用程序启动到执行命令时old代(全gc)gc次数复制代码
jstat -gccause pid :与jstat -gcutil 一样,但是会额外输出上一次导致GC产生的原因。
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比;S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比;E:年轻代中Eden(伊甸园)已使用的占当前容量百分比;O:old代已使用的占当前容量百分比;P:perm代已使用的占当前容量百分比;YGC:从应用程序启动到执行命令时年轻代中gc次数;YGCT:从应用程序启动到执行命令时年轻代中gc所占用时间;FGC:从应用程序启动到执行命令时老年代gc次数;FGCT:从应用程序启动到执行命令时老年代gc占用时间;GCT:从应用程序启动到执行命令时总gc占用时间复制代码
jstat -gcnew pid :监视新生代GC的情况
S0C:年轻代中第一个survivor(幸存区)的容量 (字节); S1C:年轻代中第二个survivor(幸存区)的容量 (字节); S0U:年轻代中第一个survicor(幸存区)已用空间 (字节); S1U:年轻代中第二个survicor(幸存区)已用空间 (字节); TT:对象在新生代存活的次数; MTT:对象在新生代存活的最大次数; DSS:期望的幸存区大小; EC:年轻代中Eden(伊甸园)的容量 (字节); EU:年轻代中Eden(伊甸园)的使用空间 (字节); YGC:年轻代垃圾回收次数; YGCT:年轻代垃圾回收所用时间。复制代码
jstat -gcnewcapacity pid :监视内容与jstat -gcnew基本相同,但是主要关注使用到的最大最小空间。
NGCMN:年轻代(young)中初始化(最小)的大小(字节); NGCMX:年轻代(young)的最大容量; NGC:年轻代(young)中当前的容量 (字节); S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节); S0C:年轻代中第一个survivor(幸存区)的容量 (字节); S1CMX:年轻代中第二个survivor(幸存区)的最大容量 (字节); S1C:年轻代中第二个survivor(幸存区)的容量 (字节); ECMX:年轻代中Eden(伊甸园)的最大容量 (字节); EC:年轻代中Eden(伊甸园)的容量 (字节); YGC:从应用程序启动到执行命令时年轻代执行GC的次数; YGCT:从应用程序启动到执行命令时年轻代执行GC的时间;复制代码
jstat -gcold pid :监视老年代GC情况。
PC:Perm(持久代)的容量 (字节);PU:Perm(持久代)目前已使用空间 (字节) ;OC:Old代的容量 (字节);OU:Old代目前已使用空间 (字节); YGC:从应用程序启动到执行命令时年轻代中gc次数; FGC:从应用程序启动到执行命令时old代(全gc)gc次数;FGCT:从应用程序启动到执行命令时old代(全gc)gc所用时间(s); GCT:从应用程序启动到执行命令时gc用的总时间(s)。复制代码
jstat -gcoldcapacity pid :与jstat -gcold 基本相同,但是主要关注的是使用到的最大最小空间。
OGCMN: old代中初始化(最小)的大小 (字节); OGCMX:old代的最大容量(字节);OGC:old代当前新生成的容量 (字节) ;OC:Old代的容量 (字节) ;YGC:从应用程序启动到执行命令时年轻代中gc次数 FGC:从应用程序启动到执行命令时old代(全gc)gc次数FGCT:从应用程序启动到执行命令时old代(全gc)gc所用时间(s) GCT:从应用程序启动到执行命令执行gc总时间。复制代码
jstat -gcpermcapacity pid :输出永久代用到的最大最小空间
PGCMN:perm代中初始化(最小)的大小 PGCMX:perm代的最大容量 (字节)PGC:perm代当前新生成的容量 (字节) PC:Perm(持久代)的容量 (字节);YGC:从应用程序启动到执行命令时年轻代中gc次数 FGC:从应用程序启动到执行命令时old代(全gc)gc次数FGCT:从应用程序启动到执行命令时old代(全gc)gc所用时间(s)GCT:从应用程序启动到命令执行所用的gc总时间。复制代码
介绍下面的命令之前让我们先了解几个概念:
javaDump:Java虚拟机的运行时快照,将Java虚拟机运行时的状态和信息保存到文件。 线程Dump,包含所有线程的运行状态。纯文本格式。 堆Dump,主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。二进制格式。复制代码
jmap: java内存映像工具
简介:jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文 件)。如果不使用jmap命令,要想获取Java 堆转储快照,可以用-XX:+HeapDumpOnOutOfMemoryError参数,在虚拟机在OOM异常出 现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break] 键让虚拟机生成dump文件,又或者在Linux系统下通过Kill-3命令发送进程退出信号生成dump文件。 jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永 久代的详细信息,如空间使用率、当前用的是哪种收集器等。当虚拟机出现内存不足或者GC异常时,此时就可以查看heapdump来分析原因。常见的内存错误包括: outOfMemoryError 年老代内存不足。 outOfMemoryError:PermGen Space 永久代内存不足。 outOfMemoryError:GC overhead limit exceed 垃圾回收时间占用系统运行时间的98%或以上复制代码
使用
其中参数意义如下:option:选项参数是互斥的(不可同时使用)。想要使用选项参数,直接跟在命令名称后即可。pid:需要打印配置信息的进程ID。该进程必须是一个Java进程。想要获取运行的Java进程列表,你可以使用jp。executable :产生核心dump的Java可执行文件。core :需要打印配置信息的核心文件。remote-hostname-or-IP :远程调试服务器的(请查看jsadebugd)主机名或IP地址。server-id:可选的唯一id,如果相同的远程主机上运行了多台调试服务器,用此选项参数标识服务器。复制代码
jmap -dump pid :生成java堆快照 dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名
然后用jhat命令可以参看 jhat -port 500 heapDump 在浏览器中访问:http://localhost:5000/ 查看详细信息 这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用。jmap -heap pid :查看java 堆(heap)使用情况
这里直接参考Hollis的博客结果 :【http://www.hollischuang.com/archives/303】
Attaching to process ID 31846, please wait...Debugger attached successfully.Server compiler detected.JVM version is 24.71-b01using thread-local object allocation.Parallel GC with 4 thread(s)//GC 方式Heap Configuration: //堆内存初始化配置 MinHeapFreeRatio = 0 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40) MaxHeapFreeRatio = 100 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70) MaxHeapSize = 2082471936 (1986.0MB) //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小 NewSize = 1310720 (1.25MB)//对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小 MaxNewSize = 17592186044415 MB//对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小 OldSize = 5439488 (5.1875MB)//对应jvm启动参数-XX:OldSize=:设置JVM堆的‘老生代’的大小 NewRatio = 2 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率 SurvivorRatio = 8 //对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值 PermSize = 21757952 (20.75MB) //对应jvm启动参数-XX:PermSize= :设置JVM堆的‘永生代’的初始大小 MaxPermSize = 85983232 (82.0MB)//对应jvm启动参数-XX:MaxPermSize= :设置JVM堆的‘永生代’的最大大小 G1HeapRegionSize = 0 (0.0MB)Heap Usage://堆内存使用情况PS Young GenerationEden Space://Eden区内存分布 capacity = 33030144 (31.5MB)//Eden区总容量 used = 1524040 (1.4534378051757812MB) //Eden区已使用 free = 31506104 (30.04656219482422MB) //Eden区剩余容量 4.614088270399305% used //Eden区使用比率From Space: //其中一个Survivor区的内存分布 capacity = 5242880 (5.0MB) used = 0 (0.0MB) free = 5242880 (5.0MB) 0.0% usedTo Space: //另一个Survivor区的内存分布 capacity = 5242880 (5.0MB) used = 0 (0.0MB) free = 5242880 (5.0MB) 0.0% usedPS Old Generation //当前的Old区内存分布 capacity = 86507520 (82.5MB) used = 0 (0.0MB) free = 86507520 (82.5MB) 0.0% usedPS Perm Generation//当前的 “永生代” 内存分布 capacity = 22020096 (21.0MB) used = 2496528 (2.3808746337890625MB) free = 19523568 (18.619125366210938MB) 11.337498256138392% used670 interned Strings occupying 43720 bytes.复制代码
jmap -histo pid :查看堆内存中的数量及大小
jmap -histo:live pid :和jmap -histo pid查询信息相同,但是JVM会先触发gc,然后再统计信息。
由于数量太多我们只打印一部分。jhat:虚拟机堆转储快照分析工具
简介:jhat(JVM Heap Analysis Tool))命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看。复制代码在浏览器中输入入http://localhost:5000/ 显示如下图
由于VisualVM可以更直观的显示堆dump的情况,这里不再过多的研究。复制代码
jstack:Java堆栈跟踪工具
简介:jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为 threaddump或者javacore文件)。线程快照就是当前线程执行的每一条方法的集合, 生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循 环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。 线程出现停顿 的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些 什么事情,或者等待着什么资源。 复制代码
使用
jstack[option]vmid
现在我们来复习一下线程先关的知识点:
线程状态
- NEW,未启动的。不会出现在Dump中。
- RUNNABLE,在虚拟机正在运行的。
- BLOCKED,受阻塞并等待监视器锁。
- WATING,无限期等待另一个线程执行特定操作。
- TIMED_WATING,有时限的等待另一个线程的特定操作。
- TERMINATED,已退出的。
monitor
Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。monitor可以理解为每一个对象的锁。复制代码
- 进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则迚入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。
- 拥有者(The Owner):表示某一线程成功竞争到对象锁。
- 等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。
例如:在多线程环境下,synchronized块中的方法获取了lock实例的monitor,类似于代码:
public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } }}复制代码
或者直接作用于方法的synchroized,相当于获取Thread1的monitor,
public class Thread1 implements Runnable { public synchronized void run() { ..do something }}复制代码
调用修饰
表示线程在方法调用时,额外的重要的操作复制代码
- locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。
-
waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在进入区等待。
-
waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁幵在等待区等待。
- parking to wait for <地址> 目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞
线程动作
- runnable:状态一般为RUNNABLE。
- in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING。
- waiting for monitor entry:进入区等待,状态为BLOCKED。
- waiting on condition:等待区等待、被park。
- sleeping:休眠的线程,调用了Thread.sleep()。
模拟了数据库表的死锁,用jstack -l 27992查看:
可以看到这里发生了数据库死锁
下面我们通过程序死锁的例子看一下命令如何使用:
public class TestJstackCommandDemo { public static void main(String[] args) { Thread t1 = new Thread(new DeadLockclass(true));//建立一个线程 Thread t2 = new Thread(new DeadLockclass(false));//建立另一个线程 t1.start();//启动一个线程 t2.start();//启动另一个线程 } } class DeadLockclass implements Runnable { public boolean falg;// 控制线程 DeadLockclass(boolean falg) { this.falg = falg; } public void run() { /** * 如果falg的值为true则调用t1线程 */ if (falg) { while (true) { synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); } } } } /** * 如果falg的值为false则调用t2线程 */ else { while (true) { synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); } } } } } } class Suo { static Object o1 = new Object(); static Object o2 = new Object(); }复制代码
可以看到控制台输出:
此时通过jstack 29400查看线程堆栈信息: 可以很清楚的看出来,两个线程被阻塞,并且发现了一个死锁: 可以看出Thread1在执行第45行的时候,<0x00000007abe3ed98>被锁住,于是它等在这个资源;Thread0在执行第32行时,也在等待资源<0x00000007abe3ed98>,但是<0x00000007abe3ed98>被锁住,由此发生死锁,就看的很清晰了。以上就是JDK常用的命令行工具,有的时候通过这些命令可以很清晰的找到问题,需要多练习熟悉,共勉。
本文参考: 《深入理解java虚拟机》
[Java命令学习系列](http://www.hollischuang.com/archives/110)
可点击底部看其他命令复制代码
[Java中的多线程你只要看这一篇就够了](http://www.importnew.com/21089.html)