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