你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

《深入理解Java虚拟机》第4章 虚拟机性能监控与故障处理工具-看看虚拟机工具用起来有多简单

2021/12/27 16:17:11

1 为什么要用工具

善用工具能帮助开发者实时对虚拟机监控,优化虚拟机内存分配结构;快速完成排障功能,减少故障停顿时间。

2. JDK命令行工具

在紧急排障,没有趁手的工具时(电脑),可使用命令行排障。命令行层面的工具,是Java自带工具,基本上有电子设备就能完成排障过程。

本人遇到过这种情况,后面在手机里装了链接服务器的工具软件,随时随地可以进服务器检查。基本上,有移动设备,就可以完成服务器连接操作。

2.1 命令行工具的实现原理

首先,这些工具都在jdk/bin包下,共同特点是几十到几百K,非常小巧

为啥怎么小呢?linux里面可以看出来

统一调用了tools.jar包

2.2  jps:显示系统内所有HotSpot虚拟机进程

参数:

-q 只看进程号 

-m 启动传入参数

-l 主类全名,jar包名

-v jvm参数

 实测发现,jps -v 是比较好用的方式了,可以看出具体的项目,其次是jps,如果是部署的Spring Boot项目,直接通过**.jar,这个结尾就可以看出具体的项目了

以下是几种项目的实测情况

2.2.1 SSM 项目

这是个比较古老的项目,用的技术是SSM+tomcat

jps

2692 Bootstrap

jps -v

 可以看到jps-v有较清晰的指明,是使用IDEA启动的tomcat项目,jps指示不明确

2.2.1 MAVEN+tomcat 项目

jps

17680 Launcher

jps -v

 可以看出是maven项目,jps指示不明确

2.2.2 SpringBoot 项目

jps

8536 O2oOrderApplication
17332 vwzt-eureka-1.0.0-SNAPSHOT.jar

可以看到,对于SpringBoot项目,如果只是查PID,其实jps已经够用了。

8536这个进程号是直接用IDEA启动的,可以看到应用启动类名

17332是打包后启动的,可以看到*.jar格式

jps -v

17332 vwzt-eureka-1.0.0-SNAPSHOT.jar
8536 O2oOrderApplication -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59297,suspend=y,server=n -Denv=DEV -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.liveBeansView
.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8

多了一些配置信息

2.2.3 总结

实际生产环境大多是linux,其实用ps -ef 命令就够了,还能查到启动时间信息。

用jps特别方便的情况:

微服务项目

微服务项目查PID的时候,用jps命令更简洁

服务器运行多种不同类型的服务

比如有java项目,又有数据库,还有ngnix,这种情况下,用jps命令就可以排除掉非java的应用了,查看起来更容易。

2.3 jstat:监控类回收、编译情况、gc情况的工具

前置说明:最后一参数是PID,可通过jps、ps命令查到PID,再执行此命令

2.3.1 监控类回收

-class 配参

jstat -class 23152

Loaded  Bytes  Unloaded  Bytes     Time
 63033 127087.6     1911  2578.1     229.60

这个命令最赞的地方是列名特别容易读记

Loaded:加载了多少个类

Bytes(第二字段):加载这些类占用了多少字节

Unloaded:卸载了多少类

Bytes(第四字段):卸载这些类释放多少字节

Time:装载、卸载过程总耗时

个人评价:长期监控可以作为初步排查内存泄露的手段

2.3.2 编译情况

 -compiler 配参

jstat -compiler 23152

Compiled Failed Invalid   Time   FailedType FailedMethod
   65217      9       0    71.87          1 com/intellij/configurationStore/StoreUtilKt saveAllProjects

其实就是显示编译中编译多少个类,失败了多少个之类的信息,事实上使用IDE开发阶段,已经基本上完成了这步操作。

个人评价:用不上(如果有用到的情况,欢迎在评论区指出)

2.3.3 GC监控

事实上有很多调参,只要记住最重要的两个

  • -gc 查看回收情况(具体大小,单位KB)

jstat -gc 116844

 S0C=Survivor0 GC大小

S1C=Survivor1 GC大小

S0Used=S0区当前占用的大小

S1Used=S1区当前占用的大小

EC=Eden区 GC的大小

EU=Eden区 当前占用大小

OC=老年代GC大小

OU=老年代占用大小

PC=方法区回收大小

PU=方法区占用大小

YG=新生代回收次数

YGCT=新生代总回收时间

FGC=Full GC次数

FGCT=Full GC占用时间

GCT=回收总时间

  • -gcutil查看gc比例

 

这里的符号上面都出现过了,只不过值换成百分比

评价:主要用到总次数、总时间这些信息,可以用来确定总停顿时间,吞吐量和内存占用的情况,非常有用的Java工具

2.3.4 jinfo:Java配置信息工具

已配置信息可以用 jps -v 命令查询,未配置信息则可以通过jinfo查到默认值,jinfo工具可以当成是jps的一种补充。

用法一:jinfo [-option] [args] [PID]

 用法二:生成全部配置信息

 因为太长,没有截取全部信息,注意,用法一是可以用在非自身开发的应用,方法二则可能被其他应用屏蔽报错:

 2.4 jmap生成内存快照工具

获取内存快照的几种方式:

1. jmap

2. JVM调参,并满足参数条件:-XX:HeapDumpOnOutOfMemoryError 产生OOM时记录内存快照,-XX:HeapDumpOnCtrlBreak 按住Ctrl+Break记录内存快照

3.kill -3 发出退出信号拿到内存快照

可以看到是JVM内存详细信息

注意jmap是版本不兼容的,如果有多个版本的jdk,可以进入到相应的bin目录进行执行


2.5 jhat:虚拟机堆转储快照分析工具

在没装任何软件的情况下可以进行快照分析

命令:jhat k.prof

 

大概就Heap Histogram比较有用,通过数量和大小的筛选可以定位内存泄露和内存溢出问题。

2.6 jstack  命令行输出虚拟机线程快照

开始的时候有一点摸不着头脑,在Java程序中加断点后,jstack命令直接卡住,并不会输出。debug之后下一步,输出退出,个人猜测是程序计数器指针移动的情况下就会直接输出,否则进行等待。注意这里输出的线程快照和前面的内存快照是有差别的。

命令:jstack [-l] [PID]

 这个命令可以用来查询死锁,线上死锁可考虑用此方式排查

作者生成用以下代码可以排查(和原文略有差异,增加UTF8编码):

<%@ page language="java" pageEncoding="UTF-8" import="java.util.Map"%>
<html>
    <head>
        <title>服务器线程信息</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <pre>
            <%
            for (Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStackTraces().entrySet()) {
                Thread thread = (Thread) stackTrace.getKey();
                StackTraceElement[] stack = (StackTraceElement[]) stackTrace.getValue();
                if (thread.equals(Thread.currentThread())) { continue; }
                out.print("\n线程:" + thread.getName() + "\n");
                for (StackTraceElement element : stack) {
                    out.print("\t"+element+"\n");
                }
            }
            %>
        </pre>
    </body>
</html>

个人感觉,没有jstack命令好用,没有特别直接的标明线程状态和锁情况,可能更适合有经验的程序员自定义线程堆栈输出,大致如下:

 

2.7 总结

上述每种工具的用途:

jps: 查java进程号、配置JVM参数的

jstat:查内存占用和GC回收停顿的

jinfo:修改JVM参数前确认旧参数值的

jmap:直接查内存快照的

jhat:简单版的dump分析工具,查看对象创建数量和占用内存的

jstack:生成线程快照,可以用来查死锁的

联用:部署了项目A,用jps确认运行态,jstat监控GC情况,不理想,程序卡顿有些严重,jstack先排除死锁,然后jmap生成内存快照,jhat开启dump分析,查到问题后用jinfo确认当前JVM参数,然后调参

上章答案:

Java 虚拟机针对不同的区域采取的不同回收策略?
程序计数器、虚拟机栈、本地方法栈:采用线程消亡时或者栈帧弹出时回收的方式
java堆:死亡对象未覆盖finalize()方法或已调用过的直接回收;覆盖且未调用过finalize()方法的放入F-Queue进行第二次小规模标记,未产生引用链就回收;
方法区:常量没有引用可回收,类需要优先考虑Java堆对象、类加载器、Class类引用的回收情况,这三个部分都已经回收,类可以回收。

堆区如何判断对象死亡?
引用计数法和可达性分析法

不同引用类型
强引用、软引用、弱引用、虚引用

GC回收的方式有哪些?
大的分类分为部分收集和全部收集
部分收集又分为新生代收集、老年代手机和混合收集,其中混合收集只有G1收集器支持。

垃圾回收的算法
标记-清除法、标记-整理法、复制算法、分代收集算法

HotSpot算法实现方式?
枚举根节点、安全点、安全区域、记忆集、写屏障

多线程实现安全点的两种方式?
抢先式中断和主动式中断

GC Roots根节点和其他节点遍历时长会随堆的大小改变吗?
根节点不会,因为使用了OopMap做了预处理;其他节点会随着堆大小增加而增加

并发情况下收集器和用户线程并发可能产生什么问题?哪些问题是必须解决的?
对象冗余,产生新的未回收对象;
对象消失,对标记消亡对象建立新引用,导致程序错误(必须解决)

对象消失产生的条件
已扫描对象产生了新的引用,部分扫描对象删除了引用

并发情况对象消失问题的解决方案
增量更新(已扫描改为正在扫描,破坏已扫描产生新引用条件,修正对象会重新扫描)、原始快照(按快照决定,破坏删除引用条件)

常见的垃圾收集器:
新生代:Serial、ParNew、Parallel Scavenge
老年代:Serial Old,Parllel Old、CMS
跨代:G1、Shenandoah、ZGC
不进行垃圾回收的垃圾收集器:Epsilon

如何选择垃圾收集器?
应用程序提供什么功能,运行环境的基础是什么,JDK发行商、版本号对应的可用收集器

内存分配策略
优先Eden区,大对象直接回收、长期存活对象进入老年代(根据虚拟机参数设定,但是同年龄超过survivor一半超过此年龄也会进入老年代)

什么是空间担保?为什么要使用空间担保?
老年代剩余空间小于新生代占用空间时,为了减少Full GC的次数,首先计算平均晋升情况,不超过平均晋升情况占用空间的,则优先Minor GC