JVM2-性能监控故障处理工具

给一个系统定位问题的时候,知识、经验是关键的基础,数据是依据,工具是运用知识处理数据的手段。这里的数据包括但是不限于异常堆栈、虚拟机运行日志、垃圾收集器、线程快照、堆转存储快照等。

四、虚拟机性能监控、故障处理工具

4.1、基础故障处理工具

JDK 的bin目录下面存储着java.exe,javac.exe两个命令行工具。除了编译和运行程序外,打包、部署、签名、调试、监控、运维等各个场景都可能会用到他们。

据软件可用性和授权的不同,可以把Jdk中的工具分为三类:(商业授权工具,主要是JMC(JAVA Mission Control)及它要使用到的JFR(Java Flight Recorder),JMC这个原本来自JRockit的运维监控工套件从JDK7就被集成到了OracleJdk中,JDK11之前无需单独下载,但是在商业环境中使用它则是要付费的) ,正式工具,还有实验性工具

在bin目录下,每个工具体积基本稳定在21KB左右,它们的真正功能代码实现是在JDK的工具类库中,JDK9之前,这些代码实现在jdk\lib\tools.jar中,jdk以及之后在jdk\jmods中实现。如果是JDK5,需要监控JVM,需要在程序启动的时候添加参数“-Dcom.sun.management.jmxremote”,开启JMX管理功能,否则大部分工具都是基于JMX,这样它们都将无法使用。如果是JDK6或者以上版本,JMX默认是开启的

4.2.1、jps:虚拟机进程状况工具

        jps(JVM Process Status Tool),Jdk很多工具都参考了UNIX的命名方式,它的功能也和ps命令类似,可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class ,main()函数所做的类名称已经这些进程在本地虚拟机的唯一Id).这个工具虽然功能单一,但是它绝对是使用频率最高的JDK命令行工具,因为其他JDK工具大多数都需要输入它查询到的LVMID来确定监控的是哪一个虚拟机进程。对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID Process Identifier)是一致的,使用windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID,但是如果同时启动多个虚拟机进程,无法根据进程名称定为时,那就必须依赖jdp命令显示主类的功能才能区分了。

      jps 命令格式:jps [options] [hostid]

     

执行这段代码
public class JpsTest {
	public static void main(String[] args) throws InterruptedException {
		System.out.println("Hello World!");
		Thread.sleep(500000);
	}
}

通过cmd执行 jps -m ,就可以显示虚拟机进程启动时传递给主类main()函数的参数和main函数所在的类

                 

jps 还可以通过RMI协议查询了开启RMI服务的远程虚拟机进程状态,参数hostid为RMI注册表中注册的主机名如何使用 jps+jstatd 访问远程服务器上的jvm进程

参数详解:

 -q:只输出LVMID,省略主类的名称,-m输出虚拟机进程启动时传递给主类的main()函数参数

 -l:输出主类的全名,如果执行的是jar包,则输出jar的路径, -v输出虚拟机进程启动时的JVM参数

4.2.1、jstat:虚拟机统计信息监视工具

jstat可以很方便地建立远程RMI服务器,jps可以通过jstat查看远程虚拟机的LVMID。jstat可以显示本地或者远程虚拟机进程中类加载,内存,垃圾收集,即时编译等运行时数据。jstat工具在纯文本状态下监视虚拟机状态的变化,在用户体验上没有JMC、VIsualVM等可视化工具直观。

4.2.3、jinfo:java配置信息工具

jinfo的作用是实时查看和调整虚拟机各项参数,使用jps -v可以查看虚拟机启动时显示指定的参数列表,jinfo可以查看未被显示指定的参数系统默认值。

比如说查询CMSinitatingOccupancyFraction参数值

4.2.4 jmap:Java内存映像工具

      jmap 命令用于生产堆转储快照(heapdump或者dump文件),如果不适用jmap命令,想要获取dump文件,也可以添加虚拟机参数-XX:+HeapDumpOnOutOfMemoryError参数,可以让jvm在内存溢出之后生成dump文件,也可以使用【Ctrl】+【Break】键让虚拟机生成dump文件,又或者再linux系统上通过Kill -3获得。

      jmap的作用不仅仅是为了获取dump,还可以查询finalize执行队列、java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。

导出的dump日志可以通过jvisualvm进行分析

4.2.5 jhat:虚拟机dump日志分析工具

不推荐用jhat来分析dump日志,一般是通过VisualVM,还有Eclipse Memory Analyzer、IBM HeapAnalyzer等工具来分析。

4.2.6、jstack:java堆栈跟踪工具

jstack 命令用于生成虚拟机当前时刻的线程快照,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程出现死锁、死循环、请求外部资源导致长时间挂起等。

[root@localhost bin]# ./jstack -L 40139
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message
[root@localhost bin]# ./jstack -l 40139
2020-05-13 19:40:32
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.212-b10 mixed mode):

"Attach Listener" #8 daemon prio=9 os_prio=0 tid=0x00007f6d30001000 nid=0x9d2d waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f6d580db000 nid=0x9cd3 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f6d580d8000 nid=0x9cd2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f6d580d6000 nid=0x9cd1 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f6d580d4800 nid=0x9cd0 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f6d580a1800 nid=0x9ccf in Object.wait() [0x00007f6d5d861000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000f1a08ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000f1a08ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

   Locked ownable synchronizers:
        - None

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f6d5809e800 nid=0x9cce in Object.wait() [0x00007f6d5d962000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000f1a06bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000f1a06bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"main" #1 prio=5 os_prio=0 tid=0x00007f6d58009800 nid=0x9ccc waiting on condition [0x00007f6d61297000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at erwan.jvm.JpsTest.main(JpsTest.java:10)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=0 tid=0x00007f6d58095000 nid=0x9ccd runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f6d580de000 nid=0x9cd4 waiting on condition 

JNI global references: 5

从上面可以看到  java.lang.Thread.State: TIMED_WAITING (sleeping)  ,是由于随眠导致的TIMED_WAITING 

4.3、可视化故障处理工具

JDK除了附带大量的命令行工具外,还提供了几个功能集成度更高的可视化工具,包括JConsole、JHSDB、VisualVM、JMC四个。

4.3.1、JHSDB:基于服务性代理的调试工具

JHSDB,是JDK9的功能。整合了几乎所有前面提到的基础工具,它支持命令行模式,也支持图形模式,JHSDB是一款基于服务性代理实现的进程外调试工具,服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言(含少量JNI)代码实现的API集合。通过服务性代理的API,可以在独立的java虚拟机进程里分析其他HotSpot虚拟机的内部数据,或者从HotSpot虚拟机进程内存中dump出来的快照还原出它的运行状态细节

4.3.2、JConsole:Java监视与管理控制台

JConsole是一款基于JMX的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进行信息的收集和参数的动态调整。

JMX支持跨服务器的管理,通过远程进程功能来连接远程服务器。JMX支持跨服务器的管理jconsole链接远程springboot,查看JVM信息,进入JConsole,可以看到主界面包括"概述","内存","线程","类","VM摘要","MBean"六个页签。

1、内存监控

“内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存(被收集器直接管理的Java堆和被间接管理的方法区)的变化趋势。

// -Xms100m -Xmx100m -XX:UseSerialGC
public class OOMObjectTest {
	static class OOMObject {
		public byte[] placeholder = new byte[64 * 1024];
	}

	public static void fillheap(int num) throws InterruptedException {
		List<OOMObject>list=new ArrayList<OOMObject>();
		for(int i=0;i<num;i++){
			//稍作延时,令监视曲线的变化更加明显
			Thread.sleep(50);
			list.add(new OOMObject());
		}
		System.gc();
		
	}
	public static void main(String[] args) throws InterruptedException {
		fillheap(1000);
		while(true);
	}
}

上面代码的作用是以64kb/50ms的速度向java堆中填充数据,一共填充1000次,从下图中可以看到,曲线是一致以平滑上升的,在1000次循环后,运行了System.gc()后,虽然整个新生代Eden和Survivor被请客,但是老年代依然没变,说明这些对象都存活。

1)虚拟机只限制了Java堆100MB,但是没有用-Xmn参数指定新生代大小,但是可以通过图标C的EdenSpace的已提交,默认Eden:Survivor为8:1,所以可以推算出总的新生代空间为27328KB/80%=34160KB

2)空间未回收的原因是, 对象都存活。List<OOMObject>存活的原因是因为 fillheap()方法还未退出,如果在fillheap()方法后执行gc,就可以回收掉全部内存。


 

2、线程监控

“线程”页签相当于可视化的jstack,导致线程长时间停顿的主要原因有,等待外部资源(数据库连接,网络分析,设备资源等)、死循环、锁等待等。

package erwan.jvm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Hello world!
 *
 */
public class ThreadWaitingTest {
	public static void createBusyThread() {
		Thread thread = new Thread(new Runnable() {

			public void run() {
				while (true)
					;

			}

		}, "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){
						System.out.println(e.getMessage());
					}
					
				}
				
			}
			
		},"testLockThread");
		thread.start();
	}

	public static void main(String[] args) throws InterruptedException, IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		br.readLine();
		createBusyThread();
		br.readLine();
		Object obj=new Object();
		createLockThread(obj);
	}
}

下图显示了main函数BufferReader.readBytes()方法正在等待System.in的输入,这个时候线程为Runnable状态,Runnable状态的线程仍然会被分配运行时间,但是readBytes()方法检查到流没有更新就会立即归还执行令牌给操作系统,这种等待只消耗很小的处理器资源。JConsole监视一会儿后 main函数的线程就会消失。

下图显示testBusyThread线程,testBusyThread一直在执行空循环,这个时候线程为Runnable状态,而且没有归还线程执行令牌的动作,所以会在空循环中耗尽操作系统为它分配的执行时间,直至线程切换为止,这种等待会消耗大量的处理器资源。

testLocakThread线程在等待lock对象的notify()或notifyAll()方法的出现,线程这个时候处于WAITING状态,在重新唤醒前不会被分配执行时间。

下面代码展示一段死锁等待,200线程去计算1+2,和2+1的值,理论上for循环可以省略,这两个线程也能导致死锁,不过那样的概率太小。需要尝试运行多次才能看到死锁的效果。造成死锁的根本原因是Integer.valueOf()方法出于减少对象创建次数和节省内存的考虑,会对数值为-128~127直接的Integer对象进行缓存,所以代码中尽管调用了200次Integer.ValueOf()方法,但是一共只返回了两个Integer对象。如果在某个线程的两个Synchronized快之间发生了一次线程切换,那就会出现线程A在等待线程B持有的Integer.valueOf(1),而线程B在等待线程A持有的Integer.valueOf(2),结果大家都执行不下去的情况。

public 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) throws InterruptedException, IOException {
		for(int i=0;i<100;i++){
			new Thread(new SynAddRunnable(1, 2)).start();
			new Thread(new SynAddRunnable(2, 1)).start();
		}
	}
}

下图显示的很清楚45被阻塞(BLOCKED),在等待48拥有的锁。

4.3.3、VisualVM:多合-故障处理工具

VisualVm(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。它除了能运行监视,故障处理外,还提供了性能分析等功能。它的很大一个优点是不需要被监视的程序基于特殊的Agent去运行,因为它的通用性很强,对于应用程序实际性能的影响也较小。

1、VisualVM兼容范围与插件安装

VisualVm基于NetBeans平台,所以一开始它就具备了通过插件扩展功能的能力,通过插件可以做到:

  • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)
  • 监视应用程序的处理器、垃圾收集、堆、方法区以及线程信息(jstat、jstack)
  • dump以及分析堆转储快照(jmap、jhat)
  • 方法级的程序运行性能分析,找出调用最多、运行时间最长的方法
  • 离线程序快照,收集程序运行时配置、线程dump、内存dump等信息建立一个快照,可以将快照发送给开发者进行Bug反馈

visualVM没有加载任何插件,所以我们需要手工安装,visualVM的Profiler页签中,VisualVM提供了程序运行期间方法级的处理器执行执行分析以及内存分析,做Profiling肯定会对程序运行性能有比较大的影响,一般不在生产环境中使用这项功能,或者改用JMC来完成,JMC对应用影响非常小。

2、BTrace动态日志跟踪

BTrace的作用是在不中断目标程序运行的前提下,通过HotSpot虚拟机的Instrument功能(HotSpot虚拟机允许在不停止运行的情况下,更新已加载的类的代码)动态加入原本不存在的调试代码,BTrace的用途很广泛,打印调用堆栈,参数,返回值只是它最基础的使用形式,在它的网站上有使用BTrace进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等使用案例。   阿里巴巴开源的诊断工具Arthas也通过Instrument实现了与BTrace类似的功能。

4.3.4 Java Mission Control:可持续在线的监控工具

JFR是一套内建在HotSpot虚拟机里面监控和基于事件的信息收集框架,与其他监控工具相比,Oracle特别强调它的“可持续在线”的特性。JFR在生产环境中对吞吐量的影响一般不会高于1%,而且JFR监控过程的开始、停止完全可动态,即不需要重启应用。JMC与虚拟机之间同样采取JMX协议进行通信,JMC一方面作为JMX控制台,显示来自虚拟机MBean提供的数据;另外一方面作为JFR的分析工具,展示来自JFR的数据。

连接好JMC后,每个进程都有Mbean和JFR两个数据源,MBEan与JConsole和VisualVM上取到的内容是一样的

启动JFR(飞行记录时),可以进行记录时间、垃圾收集器、编译器、方法采样、线程记录、异常记录、网络和文件I/O、事件记录等选项和频率设定。飞行记录包含以下几类信息:

  • 一般信息:关于虚拟机、操作系统和记录的一般信息
  • 内存:关于内存管理和垃圾收集的信息
  • 代码:关于方法、异常错误、编译和类加载的信息
  • I/O:关于文件和套接字输入、输出的信息
  • 系统:关于正在运行Java虚拟机的系统、进程和环境变量的信息。
  • 事件:关于记录中的事件类型的信息,可以根据线索和堆栈跟踪,按照日志或图形的格式查看。

HotSpot的MBean中一般有各个分代大小,收集次数,时间,占用率等数据,这些都属于结果类的信息,而JFR中还可以看到内存中这段时间分配了哪些对象、哪些在TLAB中(或外部)分配、分配速率和压力大小如何、分配归属线程、收集时对象分代晋升的情况等,这些就是属于“过程”类的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值