第4章 虚拟机性能监控和故障处理工具

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)));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值