前言
我们工作中经常面临下面一系列的问题:
- 不清楚应用的运行情况,包括但不限于GC情况、热点代码、热点线程、异常(特别是被吞没的异常);
- 不清楚应用代码执行情况,甚至不知道某个模块是否还可能会运行,尤其是对于有着一定历史的应用,可能已经有很多开发在上面贡献过代码,但随着业务发展这些功能可能已经不再被使用;
- 不清楚应用的IO情况,注意,这里说的绝对不仅仅是zabbix上的"磁盘IO",而是具体到某个文件、某个端口、某个线程的IO情况。
这些问题导致的直接后果就是:对于性能优化,无从下手,只能凭经验和直觉去猜测、埋点、优化,当然,最终结果一般就是“加机器扩容”。
那么有没有一些工具能够精确定位我我们的问题呢?首先我们看看java 有哪些监控工具。
Java 监控工具
Java 不仅是一种编程语言,而且是一个非常丰富的生态系统,其中包含许多工具。JDK 包含的程序允许我们编译自己的程序,并在程序执行的整个生命周期中监视它们的状态和 Java 虚拟机的状态。
JDK 发行版的bin文件夹包含以下可用于分析和监控的程序:
- Java VisualVM (jvisualvm.exe)
- JConsole (jconsole.exe)
- Java 任务控制(jmc.exe)
- 诊断命令工具(jcmd.exe)
Java VisualVM 过去是 Oracle 和 Open JDK 发行版的一部分。但是,从 Java 9 开始,JDK 发行版不再附带 Java VisualVM。
但是值得高兴的是JDK7以上已经内置了一款新型的性能剖析工具,也就是今天要重点介绍的-Java Flight Recorder,简称JFR。
Java Flight Recorder 及其基本概念
Java Flight Recorder (JFR) 是一种监视工具,用于在 Java 应用程序执行期间收集有关 Java 虚拟机 (JVM) 中事件的信息。JFR 是 JDK 发行版的一部分,它已集成到 JVM 中。
JFR旨在尽可能少地影响正在运行的应用程序的性能。
为了使用JFR,我们应该激活它。我们可以通过两种方式实现这一点:
启动 Java 应用程序时
当 Java 应用程序已经在运行时传递jcmd工具的诊断命令
JFR 没有独立的工具。我们使用 Java Mission Control (JMC),它包含一个插件,允许我们将 JFR 收集的数据可视化。
这三个组件 — JFR、jcmd和JMC — 形成一个完整的套件,用于收集正在运行的 Java 程序的低级运行时信息。我们可能会发现这些信息在优化程序时或在出现问题时诊断程序时非常有用。
JFR 记录了关于 Java 运行时及运行在其内的 Java 应用程序的详细信息,记录用少量的开销完成。数据是作为时间上的数据点(称为事件)记录的。典型的事件可以是线程等待锁、GC、CPU 周期使用数据等。
在创建飞行记录时,可以选择哪些事件应当保存,这叫做记录模板。有些模板只保存基本事件,对性能几乎没有影响。其他模板可能有轻微的性能开销,还可能触发 GC 来收集更多信息。通常,超过百分之几的开销是很罕见。飞行记录可用于调试很大范围的问题,从性能问题到内存泄漏或严重的锁竞争。
需要注意的是,JFR是一个性能数据采集工具,它把最终采集到的数据存在一个格式为jfr的文件中,由于本身并不具备数据分析或者可视化功能,所以一般我们需要配合JDK另外一个内置工具,即JMC一起使用。
什么是JMC
JMC, 即Java任务控制(Java Mission Control)是从Java7(7u40)和 Java8 的商业版本包括一项新的监控和控制特性的图形化性能监控工具,它可以直接打开由jfr生成的原始数据采集文件。
可以在自己机器的命令行输入 jmc 来体验。
相比其它Profile工具比有什么优点
在开发环境中,我们用VisualVM、JProfiler等,功能强大,支持图形化界面操作,可以很快定位代码问题。
但是他们对应用性能的影响也非常大,所以不适合在生产环境下使用。还有这些软件要attach到jvm进程上,生产环境一般网络隔离,很难做到。
在生产环境我们最常用的profiling工具就是java/bin下的jstack,多做几次jstack,也相当于profiling了。
jstack方便易用,但并不是特别适合来做profiling,操作频率低,会导致safepoint指标急剧增长等等。
重点来了,使用jfr不需要在现有应用上额外添加任何参数、重启进程等,直接在命令行执行即可实时生效,100%无入侵,稳定可靠不影响线上应用运行。
使用方式
- 选定一台机器,登陆上去,并找到要监控的进程PID
- 执行下列命令
- pid=`jcmd | grep java进程关键词 | awk '{print $1}'`
- jcmd $pid VM.unlock_commercial_features #先解锁技能
- jcmd $pid JFR.start name=myrec settings=profile delay=20s duration=2m filename=/tmp/$pid.jfr
- #其中,delay参数表示profile延迟启动时间,duration表示持续采集时间,这里设置为2分钟
- #settings表示使用哪种采集配置
- #官方默认自带一个名为profile的配置,这个配置不会收集异常信息
- #注意,采集数据生成后请执行下列命令移除这个采集
- jcmd $pid JFR.stop name=myrec
- 这样就好了,等待2分钟+20秒,/tmp/pid.jfr文件就生成好了,这个文件直接导入JMC工具即可
如何定制采集信息
默认采集配置收集的信息较少,性能较好,若需要收集更多信息,请使用JMC工具的模版管理器来进行自定义,
生成好的自定义文件放置在
实战 OkHttpClient 内存溢出问题
以最近排查的一个问题为例,应用服务在使用 OkHttpClient 时,在创建大量对外连接时线程堆积导致内存溢出。
我们首先登录线上服务器,通过上面介绍的设置,进行信息采集:
等待2-3分钟,在tmp目录下查看目标文件,文件写入完成后,将生成的jfr文件上传到ftp,通过安全的方式下载到安装了JMC 的机器。
打开JMC,加载jfr 文件,开始分析。
通过上图分析,看见了大量OkHttpClient创建的线程,栈上分配的内存也比较多,经查询源代码分析出主要原因是应用中在创建 OkHttpClient 对象时,没有创建同一个 OkHttpClient 实例并重复使用,而是对于所有的 http 请求都重复创建一个新的实例,而每个实例都有自己的连接池和线程池,从而导致线程大量堆积。
本文转载自微信公众号「小汪哥写代码」,可以通过以下二维码关注。转载本文请联系小汪哥写代码公众号。