JVM调优--常用JVM监控工具使用
转自:https://blog.csdn.net/qq_20597727/article/details/86619408
在实际工作中,在进行jvm调优或者分析内存泄露、溢出等问题时,熟练掌握JVM常用的监控工具能够帮助更快地定位问题所在,目前记录一下使用过的常用的jvm监控工具以及其使用、和对应分析过程。
1|0查看jvm使用的垃圾回收器
在开始了jvm调优或者内存问题分析时,我们首先要了解的就是JVM使用垃圾回收器是什么,才能结合监控工具,做出正确的结果分析。那么如何查看JVM运行时使用的垃圾回收器是什么?
-
如果在程序的jvm启动参数中没有指定使用什么垃圾回收器,我们可以直接在命令行执行以下命令
得到以下输出,此命令默认打印出jvm运行使用的参数以及jvm版本信息,默认情况下不指定垃圾回收器类型的话,从虚拟机参数是没办法知道垃圾回收器类型的。
但是我们可以从虚拟机版本得知默认垃圾回收器,64位server的虚拟机默认垃圾回收器是ParallerGC吞吐量优先
-
如果程序启动的jvm参数有指定使用的垃圾回收器类型的话,更加简单,直接查看jvm启动参数是什么垃圾回收器即可,常用的启动参数与垃圾回收器对应关系如下。
回收器类型
jvm参数
新生代串行 Serial收集器
-XX:+UseSerialGC
新生代并行 ParNew收集器
-XX:+UseParNewGC
新生代Parallel Scanvenge收集器
-XX:+UseParallelGC
老年代 Parallel Old 收集器
-XX:+UseParallelGC
老年代CMS收集器
-XX:+UseConcMarkSweepGC
2|0jps
执行jps命令可以获取到当前虚拟机中正在运行的java程序,以及对应的进程号,输出格式如下
前面的数字就是进程号,后续分析肯定用到。
3|0jmap
jmap使用主要是打印某一时刻,某个进行的jvm堆信息,使用步骤如下
-
获取进行pid
ps -ef|grep 对应进程
或者使用jps获取
-
执行以下命令,例如我要获取进行
jvm-demo1-0.0.1-SNAPSHOT.jar
的堆使用信息,可以执行获得输出如下
4|0jstat
jstat用的最多的是用来统计gc相关信息,先看使用步骤如下。
-
获取进程号
-
例如我想统计
jvm-demo1-0.0.1-SNAPSHOT.jar
(进程号2865)的gc信息,并且每隔3秒统计一次,可以执行以下命令经过一阵时间监控,得到以下输出
主要关注以下几个。
- s0、s1:表示两个survior区域的使用百分比
- e:eden区域使用百分比
- o:老年代使用百分比
- m:metaspace(元数据空间)使用百分比
- ygc:新生代gc次数
- ygct:新生代gc累计总时间
- fgc:full gc次数
- fgct:full gc累计总时间
- gct:gc累计总时间
-
假如我想知道
jvm-demo1-0.0.1-SNAPSHOT.jar
上一次gc的原因,并且持续监控,3秒输出一次,可以执行以下命令监控一阵子后,得到输出
大部分根gcutil差不多
- LGCC:上一次gc的原因,Allocation Failure就是新生代满了,进行gc
- gcc:当前gc的原因,如果当前没有gc就no gc
5|0jconsole
可视化监控jvm,基本上看到啥就是啥,但是生产比较少会用到
6|0gc log
gc log顾名思义就是在gc时打印出来的日志,关于这一块的分析,对于问题定位也是非常有用的。这里gclog的垃圾回收器是ParallerGC
-
在jvm启动参数加上以下,可以开启gc log,配置gclog的输出位置
-
gc log分析,借用网上的一张图片
所有的gc日志输出,基本上大同小异,下面看一段gc日志结合分析。2019-01-23T22:18:10.534+0800: 442.398: [GC (Allocation Failure) 2019-01-23T22:18:10.534+0800: 442.398: [DefNew: 77
654K->77654K(78656K), 0.0000730 secs]2019-01-23T22:18:10.534+0800: 442.398: [Tenured: 174240K->174240K(174784K), 0
.1493292 secs] 251895K->250131K(253440K), [Metaspace: 30461K->30461K(1077248K)], 0.1495998 secs] [Times: user=0.07
sys=0.00, real=0.15 secs]
上面是一段gc日志。
GC (Allocation Failure)
这种明显就是新生代满,导致无法分配的gc类型,minor gc。
[DefNew: 36848K->2878K(39296K), 0.0039304 secs]
:虽然跟图中有所出入,但是可以分析得知是表示新生代,占用在垃圾回收前后的大小变化,括号表示新生代总大小,gc日志中,新生代总大小表示(eden+1个survior)。可以通过-Xmn大小m -XX:SurvivorRatio=比率
来控制新生代的eden、survior大小。
[Tenured: 174240K->174240K(174784K), 0.1493292 secs]
:表示老年代gc前后占用大小变化,括号表示老年代总大小。
下面再来看一段full gc日志。
跟minor gc基本大同小异。
7|0jstack
jstack主要的用途是打印出Thread dump,使用步骤如下。
-
执行以下命令,输出dump 到stack.log中。
查看log,截取下面一部分,可以发现,如果出现死锁,log中在末尾部分会提示出来。
根据两个线程id,在日志内查找,得到以下信息,下面的信息记录了Thread-5 锁住了Thread-4等待的资源,反过来也是,从而导致死锁。这就是一个简单的Thread dump分析。
补充一些概念:
-
“Thread-5” 这个位置表示线程id
-
daemon :表示线程类型
-
prio:表示优先级
-
nid:表示线程pid的十六进制
"Thread-5" #32 daemon prio=5 os_prio=0 tid=0x00007f3678f01800 nid=0x156f waiting for monitor entry [0x00007f36a8c99000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.garine.controllers.JvmTestControllerThread1.run(JvmTestController.java:102)"Thread-4" #31 daemon prio=5 os_prio=0 tid=0x00007f367851b800 nid=0x156e waiting for monitor entry [0x00007f36a8598000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.garine.controllers.JvmTestControllerThread0.run(JvmTestController.java:118)
如果我们分析Thread dump目的是寻找死锁,等待的线程,可以直接搜索BLOCKED
,deadlock
等字眼
8|0MAT
mat,全程内存分析工具,专门分析内存溢出时,转存的快照,从而定位问题。
-
想要分析内存快照,需要配置内存dump,在jvm启动参数中加入以下参数,配置好dump存储路径方便查找。
-
必要的mat工具下载地址
在jvm-demo1-0.0.1-SNAPSHOT.jar
中,提供一个接口,调用之后疯狂新建对象,最终out of memory,demo如下.在我们配置好的路径下面,会保存一个error.hprof文件,我们使用mat打开该文件。
8|1MAT使用
-
打开首页,进入以下界面,选择第一项,'怀疑内存泄露’的分析报告,点击finish
-
打开error.hprof文件,进入以下界面,进来就可以看到,mat帮助我们分析出疑似是内存泄漏的对象,我们可以直接根据mat的分析作为线索,开展排查。
点击进入detail ,然后翻到以下位置。可以定位疑似内存泄漏的对象与对象代码所在位置。在这例子中,是
com.garine.controllers.JvmTestController
疯狂创建对象,加入到ArrayList中,导致内存泄漏,对照下面的图片分析,就可以定位问题代码。
-
或者我们可以自己去定位问题所在,不使用mat提供的线索,我们进入OverView-》Historgram视图,这个视图是列出每个类都多少个实例,如下图所示。
可以看出是int[]对象实例最多,可以点击表头进行排序。其中shalldown heap表示对象的实际大小,retained heap表示回收这个对象可以释放的大小,也就是仅仅以这个对象作为gc root,所能关联到的对象大小也算进去。
同时,还可以看到左侧面板,能看到我们选定的对象是否是gc root。
根据当前所见,我们暂且怀疑int[]是内存泄漏的对象,但是int[] 并不是gc root,为了更好定位具体内存泄漏的位置,我们需要定位int[]的gc root。操作方法如下所示。右键目标对象,寻早gc root。
最终得到结果如下。如此操作,也能得到分析出疑似内存泄漏 的代码位置所在,这种方式可以让我们一个个怀疑点都去分析一遍,更加全面。
9|0参考链接
[案例]https://blog.csdn.net/u014730165/article/details/81984523
详细JVM参数
__EOF__

本文链接:https://www.cnblogs.com/treasury/p/13096465.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?