4.2 JDK命令行工具
用工具进行处理数据,数据包括:运行日志、异常堆栈、GC日志、线程快照、堆转储快照等。
工具位置一般在bin目录下
JDK监控和故障处理工具
名称 | 主要作用 |
---|---|
jps | 显示指定系统内所有Hotspot进程 |
jstat | 收集Hotspot虚拟机各方面运行数据 |
jinfo | 显示虚拟机配置信息 |
jmap | 生成虚拟机内存转储快照(heapdump文件) |
jhat | 用于分析heapdump,建立一个http服务器,可在浏览器上分析结果 |
jstack | 显示虚拟机的线程快照 |
4.2.1 jps:虚拟机进程状态工具
和ps命令累死,可以列出正在运行的虚拟机进程,显示虚拟机执行主类名称,以及这些进程的本地虚拟机唯一ID。但如果同时启动了多个虚拟机进程。只有jps [option]功能才能区分。
命令格式
jps [options] [hostid]
hostid是RMI注册表中注册的主机名。
选项 | 作用 |
---|---|
-q | 只输出LVMID,省略主类的名称 |
-m | 输出虚拟机进程启动时传给主类main函数的参数 |
-l | 输出主类的全名,进程执行的是jar包,输出jar路径 |
-v | 输出虚拟机进程启动时JVM参数 |
4.2.2 jstat:虚拟机统计信息监视工具
用于监视虚拟机各种运行状态信息的命令行工具。
可以显示本地或远程虚拟机中类装载、内存、垃圾收集、JIT编译等数据。
格式:
jstat [option vmid [interval [s|ms] [count]] ]
注意,如果是本地虚拟机LVMID和VMID是一致的
参数interval和count代表查询间隔和次数,如果省略只能查询一次。
假设每250毫秒查询一次进程2764,一共查询20次,命令是:
jstat -gc 2764 250 20
jstat工具主要选项
秒为单位
- S0C:第一个幸存区的大小Survivor区中的S0
- S1C:第二个幸存区的大小Survivor区中的S1
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- PC: 永久代perm的大小
- PU: 永久代perm的使用大小
- MC:方法区大小
- MU:方法区使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
4.2.3 jinfo:Java配置信息工具
作用是实时查看和调整虚拟机各项参数。
JDK1.6以上可以用jinfo -flag,还可以用-sysprops将虚拟机进程内容打印出来
使用**-XX:PrintFlagsFinal查询参数默认值也是很好的选择
可以在虚拟机运行期修改参数的能力,-flag name value格式
jinfo [option] pid
动态修改虚拟机参数:
jinfo -flag CMSInitiatingOccupancyFraction 1444
-XX:CMSInitiatingOccupancyFraction=85
4.2.4 jmap:Java内存映像工具
用于生成堆转储快照,查询finalize执行队列、Java堆和永久代详细信息,如空间使用率,使用哪种收集器等。
jmap命令格式:
jmap [option] vmid
选项
选项 | 作用 |
---|---|
-dump | 生成堆转储快照,格式:d-dump:[live,]format=b,file=,live子参数是否只dump出存活的对象 |
-finalizerinfo | 显示F-Queue中等待finalizer线程执行finalize方法的对象 |
-heap | 显示Java堆详细信息,哪种回收器、参数配置、分代状况等 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstat | 以ClassLoader为统计口径显示永久代内存状态 |
-F | 虚拟机进程对-dump没有响应,可使用该选项强制生成dump快照 |
jmap生成正运行的eclipse的dump快照文件,3500是进程id
jmap -dump:format=b,file=eclipse.bin 3500
4.2.6 jstack:Java堆栈跟踪工具
用于生成虚拟机当前时刻的线程快照。
线程快照就是虚拟机内每一条线程正在执行的方法堆栈的集合
主要目的是定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部资源长时间等待等。
出现线程停顿时就调用jstack,可以知道没有响应的线程到底在做什么
格式:
jstack [option] vmid
选项
选项 | 作用 |
---|---|
-F | 当正常输出请求不被响应,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用到本地方法,可以显示C/C++的堆栈 |
4.3 JDK可视化工具
4.3.1 JConsole:Java监控和管理控制台
基于JMX的可视化监视、管理工具。
内存监控
“内存按钮”,相当于jstat命令,监视虚拟机堆和永久代变化趋势。
public class TestObject {
static class OOMObejct {
public byte[] placeholder = new byte[64 * 1024];
}
public static void fillHeap(int num) throws InterruptedException {
List<OOMObejct> list = new ArrayList<OOMObejct>();
for(int i=0;i<num;i++){
Thread.sleep(50);
list.add(new OOMObejct());
}
System.gc();
}
public static void main(String[] args) throws Exception {
fillHeap(1000);
}
}
程序开始运行,内存池中Eden区呈上升的平滑曲线,在执行1000次循环后,启动GC,虽然Eden和survivor基本都被清空了,但是老年代仍然保持峰值不被回收,说明老年代中的对象仍然存活。
疑问:
1. JConsole只能看出Eden的最大值,可以推断出整个新生代的大小么?
答:Eden空间是204800KB,没有设置-XX:SurvivorRatio参数,所以是默认的8:1,整个新生代大约是204800*125%=256000K
2. 为什么执行GC后,老年代仍是峰值状态,对象并没有被回收?如果才能回收老年代的对象?
答:因为执行GC后,List对象仍然存活,该对象在GC之后仍然处于作用域之内,你把fillHeap()方法中的System.gc()移到方法之外调用就能回收全部内存。
线程监控
“线程”页签的功能相当于jstack,遇到线程停顿可以使用这个页签。
线程长时间停顿主要原因有:等待外部资源、死循环、锁等待。
下列各个原因演示代码
public class JConsoleTest {
//线程死循环
public static void createBusyThread(){
Thread thread = new Thread(new Runnable() {
public void run() {
while(true){
//第7行
}
}
},"testBusyThread");
thread.start();
}
//线程锁等待演示
public static void createLockThread(final Object lock){
Thread thread = new Thread(new Runnable() {
public void run() {
synchronized (lock){
try{
lock.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
},"testLockThread");
thread.start();
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.readLine();
createBusyThread();
br.readLine();
Object obj = new Object();
createLockThread(obj);
}
}
在线程列表可以找到createBusyThread和createLockThread,
在createBusyThread线程跟踪里,可以找到消耗较多资源的位置,比如说while循环的第7行。
createLockThread进行wait等待,只有notify或notifyAll方法被调用才能继续进行。
死锁代码
public class DeadLockTest {
//线程死锁等待演示
static class SynAddRunnable implements Runnable {
int a,b;
public SynAddRunnable(int a,int b){
this.a = a;
this.b = b;
}
public void run() {
synchronized (Integer.valueOf(a)){
synchronized (Integer.valueOf(b)){
System.out.println(a + b);
}
}
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(new SynAddRunnable(1,2)).start();
new Thread(new SynAddRunnable(2,1)).start();
}
}
}
程序开200个线程去计算1+2和2+1,通过for多次循环就会碰到死锁情况。
Integer.valueOf()方法基于减少对象创建次数和节省内存考虑,-128到127之间的数都会被缓存。也就是调用200次valueOf方法只有两个不同的对象。
假如某个线程的两个synchronized块发生线程切换的话,就会出现线程A和线程B互相等待的情况,造成死锁。
4.3.2 VisualVM:多合一故障处理工具
VisualVM可以做到的:
- 显示虚拟机进程及进程的配置、环境信息(jps,jinfo)
- 监视应用程序的CPU GC 堆 方法区及线程的信息(jstat,jstack)
- dump以及分析堆转储快照(jmap,jhat)
- 方法级程序运行分析,找出被调用最多、运行时间最长的方法
- 离线程序快照:收集程序运行时配置、线程dump、内存dump等信息建立一个快照
生成、浏览堆转储快照
生成dump文件有两种方式,可以执行下列任一操作
1. 左上角应用程序中点击“堆dump”
2. 点击“应用程序”窗口打开程序标签,“监视”标签,右侧“堆dump”
面板中有摘要、类、实例、OOL控制台等信息
实例选项不能直接使用,在要先选你关心的某个类,然后进入该类的实例,显示此类的500个实例的属性信息。
分析程序性能(抽样器)
Profiler页签提供了程序运行期间方法级CPU执行时间以及内存分析,该分析影响性能,不要在生产环境做该分析。
选择“CPU”和“内存”中的一个。
如果是“CPU”分析,会统计每个方法的执行次数、耗时;
如果是“内存”分析,会统计每个方法关联的对象数以及这些对象所占空间。
可以根据实际业务复杂程度与方法的时间、调用次数比较,找到最有优化价值的方法。
BTrace动态日志跟踪
本身也是可独立运行的程序,作用是在不停止程序运行前提下,动态加入原本并不存在的调试代码。
如果程序遇到问题,但查错误的必要信息,如方法参数、返回值等,在开发时没打印到日志中,以至于停掉服务,来加入日志代码解决问题的。就可以用BTrace解决。
安装BTrace后,右键点击某个程序,“Trace Application…”,即可编写动态查询代码。
BTrace跟踪演示
public class BTraceTest {
public int add(int a,int b){
return a+b;
}
public static void main(String[] args){
BTraceTest test = new BTraceTest();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for(int i=0;i<10;i++){
br.readLine();
int a = (int)Math.round(Math.random() * 1000);
int b = (int)Math.round(Math.random() * 1000);
System.out.println(test.add(a,b));
}
}
}
BTrace调试代码
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
@OnMethod(
clazz = "com.test.BTraceTest",
method = "add",
location = @Location(Kind.RETURN)
)
public static void func(@Self com.test.BTraceTest instance,int a,int b,@Return int result){
println("调用堆栈");
jstack();
println(strcat("方法参数A",str(a)));
println(strcat("方法参数B",str(b)));
println(strcat("方法结果",str(result)));
}
}