应用发布后,运行的情况怎么样,服务器,数据库资源是否存在浪费的情况?
流量高峰期,我们是否需要增加系统配置,通过什么来判断?
系统卡顿,进程假死,我们通过什么方式来定位问题呢?
这里我介绍几款款不错的 JVM 监控工具,可以很好的处理以上问题:
- JProfiler 详解以及 Demo 举例。
- JConsole 详解以及 Demo 举例。
- JVisualVM 详解以及 Demo 举例。
- jps、jstat、jinfo、jmap、jhat、jstack 指令详细解释以及举例。
常用监控工具和监控指令
刚入行,我接触的是 JConsole 和 JVisualVM 两款工具,这两种最方便,它属于 JDK 自带,在 bin 目录下,直接双击就可以使用。
后来,机缘巧合,看到同事在用 JProfiler,很不错,我爱不释手。
下面,我挨个为大家展示:
- JProfiler 详解以及 demo 举例
- JConsole 详解以及 demo 举例
- JVisualVM 详解以及 demo 举例
- jps、jstat、jinfo、jmap、jhat、jstack 指令详细解释以及举例
在本文中,我着重讲解 JProfiler,在我看来 JProfiler 的体验还是要比另外两种工具好一些。
JProfiler 详解以及 demo 举例
JProfiler 是一款性能分析工具,可以直接连接运行中的虚拟机,监控 JVM 的性能。
它可以分析 CPU、Thread、Memory 的性能,监控 JDBC、NoSQL、JSP、Servlet、Socket,可以在线分析,也可以离线分析。对平台支持也很友善,支持 Linux、Windows、Mac 等。
它的原理是获取 JVM 分析接口 JVMTI 的数据。
什么是 JVMTI?它是为了虚拟机调试和监控专门提供的一套虚拟机工具接口,本质上是在 JVM 内部进行了许多埋点。通过埋点给外部传递虚拟机的内的运行情况。
如何使用 JProfiler
JProfiler 的安装步骤很简单就不赘述,下载软件后,按照提示,下一步,下一步,然后安装完毕。
点击左上角图标,如下图,链接 JVM:
选择需要连接的 JVM,入下图:
连接成功后 IDEA 工具会有如下提示:
实时监测 Telemetries
整体视图情况,包含内存、垃圾回收、类文件、线程、CPU 使用情况等 5 大块,也是通常人们最关注的地方。连接 JVM 后,可以很直观地查看内存、CPU、GC 等情况。
内存监控详情,选择左侧菜单 Memory,查看内存使用详情。
如图:可以按照堆内存和非堆内存查看。
针对堆,我专门编写代码进行了测试,更好的感受堆内存的变化,如下:
我在工程中写了测试程序,循环增加对象。堆内存则出现了明显的增加。
@RestController @RequestMapping(\"my/test\") public class MyTestController { public static List<User> listTest = new ArrayList<>(); @GetMapping(path =\"/add\") public Response<Integer> save() throws InterruptedException { for (int i=0;i<5000000;i++){ System.out.println(\"======\"+i+\"========\"); User user = new User(); user.setId(UUID.randomUUID().toString()); user.setName(\"code\"+i); listTest.add(user); Thread.sleep(100); } return null; } }
程序运行以后,如下图,可以很明显的看到,堆内存增加了。
左侧菜单:记录的对象 Recorded objects
从时间的维度,记录进程中的对象数量。数量越多也就说明 JVM 压力越大,需要检查程序是否有逻辑问题,有对象没有及时释放。时间久了会导致内存溢出。
记录吞吐量 Record Throughput
对象的创建和释放速度展示了 JVM 内,对象创建的速度,例如 每秒创建了多少对象。
对象创建速度越快,就系统内存消耗也就越快。需要及时预警。
GC Activity 垃圾回收情况
模拟出现大量垃圾回收。出现大量的垃圾回收的危害,容易给系统造成卡顿,如果频繁出现 Full GC 一定要认真检查原因。否则给用户的体验十分差。
Demo 代码:
List<UserEntity> listT1 = new ArrayList<>(); int i = 0; while (true){ System.out.println(\"======\"+i+\"========\"); UserEntityentity = new UserEntity(); entity.setId(UUID.randomUUID().toString()); entity.setUserName(\"code\"+i); listT1.add(entity); //Thread.sleep(50); i++; }
类 Classes
虚拟机装载的类的数量,可以帮我们查看类文件的装载情况。
线程 Threads
监控虚拟机线程使用情况,特别是开启某种秒杀活动,或者大量用户连接,容易导致线程被耗光。
线程使用完毕的危害十分严重,其他链接无法获取到线程,请求将会丢失。
示范代码,模拟开启过量的线程:
while (flag){ int finalI = i; Runnable r2 = () -> { System.out.println(\"开启一个线程\"+ finalI +\";\"); try { Thread.sleep(500000000); } catch (InterruptedException e) { e.printStackTrace(); } }; new Thread(r2).start(); Thread.sleep(50); i++; }
CPU 监控
CPU 如果长期达到 90%,则需要考虑,增加系统 CPU 的配置,当 CPU 达到 100%,系统将出现严重的卡顿,无法处理请求。
内存剖析 Live Memory
单纯看内存使用率,并不能精确定位问题,我们需要查看内存中的对象分布。这个时候,我们可以使用内存视图。
内存视图 Live Memory 可以直观地查看实时的内存使用情况。
例如:当前内存中有哪些对象,当系统出现卡顿等情况,我们可以查看到底是哪些对象占用了大量的内存空间。查看对象的名字,对象的数量,对象的大小。
模拟测试生成大量 User 对象
Demo 代码:
List<User> list = new ArrayList<>(); int i = 0; while (true){ System.out.println(\"======\"+i+\"========\"); User user = new User(\"李四\",15,\"男\"); list.add(user); Thread.sleep(2); i++; }
如下图,我们可以清楚地看到内存中的 User 对象,大小占比。一般查看排行靠前的对象,就很好的定位问题了。
也可以从其他维度进行查看,例如从方法、类、包等多个方面查看内存。利于排查问题。
堆分析 heap walker
堆快照分析,JProfiler 创建一个内部数据库,经过数据库优化,可以生成在堆 walker 中提供视图所需的数据。
如下图,可以看到 User 对象,数量已经非常庞大了。
关于 JProfiler 的介绍暂时介绍到这里,因为它的功能远远不止这么点,但我又不能一一全部截图展示。只能靠大家在使用的过程中,细心地摸索。
下面介绍另外两款工具。
JConsole 详解以及 Demo 举例
JConsole 是一种基于 JMX 的可视化监视、管理工具。它主要包含概述、内存、线程、类、VM 概要、MBean,一共 6 个部分。
JMX 是什么?JMX 是一份规范,SUN 公司依据这个规范在 JDK 提供了 JMX 接口。
如何使用 JConsole
点击 JDK/bin 目录下面的 jconsole.exe 即可启动。
使用 JConsole 链接进程:
链接成功:首先是概况图,堆内存,线程,类,CPU 占用率。
单看内存,可以从原始区,年老区,各个维度查看内存使用情况。
线程部分,可以查看单个线程详情,以及检测线程死锁的情况。
查看虚拟机的概况
连接名称、运行时间、虚拟机版本、CPU 运行时间、活动线程等等,你所需要看到的概况信息,下图中都是拥有的。
JVisualVM 详解以及 Demo 举例
JVisualVM 类似于 JConsole,也是 JDK 自带的一款监控工具。JVisualVM 的查看角度稍微丰富一些。
如何使用 JVisualVM
Java 安装目录的 bin 目录内,双击 JVisualVM 启动工具。
然后,连接虚拟机。
选择您启动的虚拟机,然后链接,就可以看到虚拟机运行的概况了。
堆 dump
可以查看堆里面具体的实例对象,甚至可以看到堆里面的具体对象的值。
如图:可以看到 user 对象的属性,李四、男、15 岁。
还可以检测堆对象的数量,以及整体占比大小。如图:
如下图是查看运行时线程情况。
jps、jstat、jinfo、jmap、jhat、jstack 指令详细解释以及举例
jps 详解
jps 是 Java 提供的一个显示当前所有 Java 进程 pid 的命令,这个命令主要是用来显示当前系统的进程情况,例如:有哪些进程以及进程 id。
一般使用 jps,目的就是为了查询出进行的 pid,为了方便执行其他指令。
举例说明:查看 Java 进程的 pid 和名称,不需带参数。
jps
只显示 pid 的方式,加参数 q:
jps -q
jstat 详解
jstat 提供与 JVM 性能相关的统计信息,例如垃圾收集,编译活动。
jstat 最强大的地方,它可以在运行 JVM 时,无需任何先决条件的情况下动态捕获系统指标。
例如:捕捉 GC 信息。
-gc
:将显示与垃圾收集相关的统计信息
查看进程 25680 的垃圾回收情况,每 2 秒刷新一次,一共刷新 10 次。
jstat -gc -t 25680 2000 10
效果如图:
如下是对截图中参数的解释:
- S0C:幸存者 0 区域的容量
- S1C:幸存者 1 区域的容量
- S0U:幸存者 0 区域使用的空间
- S1U:幸存者 1 区域
- EC:伊甸园地区容量
- OC:旧区域容量
- OU:旧区域的已利用空间
- MC:元空间区域容量
- MU:元空间区域使用的空间
- CCSC:压缩类空间区域的容量
- CCSU:压缩类空间区域
- YGC:年轻 GC 事件的数量
- YGCT:年轻 GC 花费的时间
- FGC:完全 GC 事件的数量
- FGCT:完整 GC 时间
jinfo 详解
jinfo 是为了查看虚拟机配置信息,Java 进程运行的 JVM 参数。例如分配的内存多大、堆占多大空间、最小内存、是否开启垃圾回收打印等等。
举例示范:
jinfo -flags 25680
指令执行完毕返回的内容:
Attaching to process ID 25680, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.152-b16 Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4253024256 -XX:MaxNewSize=1417674752 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC Command line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63181,suspend=y,server=n -javaagent:C:\\Users\\2592\\AppData\\Local\\JetBrains\\IntelliJIdea2020.1\\captureAgent\\debugger-agent.jar -Dfile.encoding=UTF-8
jmap 详解
讲解前,先解释下 dump 的概念。
dump 虚拟机快照文件。dump 文件本质上是一个二进制文件,它记录了虚拟机在某一时刻的运行情况,例如 CPU 使用率、内存分布情况、线程执行情况等等。通过分析 dump 文件,有利于我们快速定位系统出现的问题。
获取 dump 文件,就等于给运行中的虚拟机拍了一个快照。通过分析快照文件,可以获取虚拟机的运行概况。
jmap 如何获取 heap dump 文件
运行如下指令,我们就可以在当前目录获取一个 heap.hprof 文件,也就是堆快照文件。
jmap -dump:format=b,file=heap.hprof 25700
如下图获取的快照文件:
如何解析快照文件
我们可以使用 JProfiler 打开这个快照文件,然后按照上面的使用教程,一步步分析堆内部的运行情况,定位问题。
jstack 详解
jstack,可以理解为 CPU 快照指令,可以生成 CPU 的快照文件。这个命令用于生成 JVM 进程当前时刻的线程的调用堆栈,用来定位线程间死锁、锁等待、是不错的选择。
如何使用 jstack
获取 thread dump 文件,采用如下指令,25680 是进程的 pid:
jstack 25680> thread-25680.txt
如下图,我们就可以看到 jstack 生成的快照文件,进而去分析里面的内容了。
总结
我们在开发日常项目的过程中,往往只顾着编写业务代码,却忽略了项目本身的运行性能。其实,项目在开发完毕后的运维阶段,同样值得关注,软件开发和运维,都是一个持续的过程。
一款软件只有在不断地使用和打磨的过程中,才会越来越好,我们采用合理的监控手段,可以为我们的软件生命周期助力。无论是定位问题,还是事前预警,我介绍的集中监控方式,都可以提供很大的帮助。希望大家一起多多学习,做最好的软件。