Loading

JVM工具使用和生产Full GC排查

本文对Java虚拟机工具的使用进行总结,并且记录了通过堆栈快照进行排查问题的思路,以便后续再发生问题时能够提供参考。

一、Java虚拟机工具

1、jps命令

jps是java提供的一个显示当前所有java进程pid的命令,适合在linux/unix平台上简单察看当前java进程的一些简单情况。很多人都是用过unix系统里的ps命令,这个命令主要是用来显示当前系统的进程情况,有哪些进程以及进程id。 jps 也是一样,它的作用是显示当前系统的java进程情况及进程id。

usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]
说明:
-q   只显示pid,不显示class名称,jar文件名和传递给main方法的参数
-m   输出传递给main方法的参数,在嵌入式jvm上可能是null
-l   输出应用程序main class的完整package名或者应用程序的jar文件完整路径名
-v   输出传递给JVM的参数
-V   隐藏输出传递给JVM的参数
示例1:只使用jps命令

image-20211208105720520

示例2:jps -q
image-20211208105839106
示例3: jps -m

image-20211208110023854

示例4:jps -l

image-20211208110201965

示例5: jps -v

jps -v 可以用于查看JVM的启动参数

image-20211208110323837

jps主要是为了获取java进程的PID,以及使用jps -v查看传递给JVM的参数。

2、jmap 命令

命令jmap是一个多功能的命令。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               查看进程的内存映像信息,类似 Solaris pmap 命令。
    -heap                显示Java堆详细信息
    -histo[:live]        显示堆中对象的统计信息
                         
    -clstats             打印类加载器信息
    -finalizerinfo       显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象
    -dump:<dump-options> 生成堆转储快照
                         dump-options:
                           live         只dump出存活的对象,如果不指定,堆中所有对象都会被dump
                                        
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             指定传递给运行jmap的JVM的参数

示例1:no option

命令:jmap pid

使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的起始地址、映射大小以及共享对象文件的路径全称。这与Solaris的pmap工具比较相似。

image-20211208121252891

示例2:heap

命令:jmap -heap pid

显示Java堆详细信息,打印一个堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息。

image-20211208121627302

示例3:histo[:live]

命令:jmap -histo:live pid

显示堆中对象的统计信息。其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有一个’*’前缀。如果指定了live子选项,则只计算活动的对象。

image-20211208124038655

示例4:clstats

命令:jmap -clstats pid

-clstats是-permstat的替代方案,在JDK8之前,-permstat用来打印类加载器的数据
打印Java堆内存的永久保存区域的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。

image-20211208125914984

示例5:finalizerinfo

命令:jmap -finalizerinfo pid

描述:打印等待终结的对象信息

image-20211208130211162

Number of objects pending for finalization: 0 说明当前F-QUEUE队列中并没有等待Fializer线程执行final

示例6:dump:

命令:jmap -dump:format=b,file=heapdump.hprof pid

生成堆转储快照dump文件。以hprof二进制格式转储Java堆到指定filename的文件中。live子选项是可选的。如果指定了live子选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使用jhat(Java堆分析工具)读取生成的文件。

这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用, 线上系统慎用。

jmap -dump:live,format=b,file=heap.bin

3、jstat命令

Jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
       
option: 参数选项
-t: 可以在打印的列加上Timestamp列,用于显示系统运行的时间
-h: 可以在周期性数据数据的时候,可以在指定输出多少行以后输出一次表头
vmid: Virtual Machine ID( 进程的 pid)
interval: 执行每次的间隔时间,单位为毫秒
count: 用于指定输出多少次记录,缺省则会一直打印

option 可以从下面参数中选择
-class 显示ClassLoad的相关信息;
-compiler 显示JIT编译的相关信息;
-gc 显示和gc相关的堆信息;
-gccapacity    显示各个代的容量以及使用情况;
-gcmetacapacity 显示metaspace的大小
-gcnew 显示新生代信息;
-gcnewcapacity 显示新生代大小和使用情况;
-gcold 显示老年代和永久代的信息;
-gcoldcapacity 显示老年代的大小;
-gcutil   显示垃圾收集信息;
-gccause 显示垃圾回收的相关信息(通-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
-printcompilation 输出JIT编译的方法信息;

示例1:-class

显示加载class的数量,及所占空间等信息。

image-20211208131312262

  • Loaded : 已经装载的类的数量
  • Bytes : 装载类所占用的字节数
  • Unloaded:已经卸载类的数量
  • Bytes:卸载类的字节数
  • Time:装载和卸载类所花费的时间
示例2:-compiler

显示VM实时编译(JIT)的数量等信息。

image-20211208131553670

示例3:-gc

显示和gc相关的堆信息

image-20211208131739417

示例4:-gcmetacapacity

显示metaspace的大小

image-20211208131947675

4、jstack 命令

Jstack是JVM自带dump线程堆栈的工具,很轻量易用,并且执行时不会对性能造成很大的影响。灵活的使用jstack可以发现很多隐秘的性能问题,是定位问题不可多得的好帮手。线程堆栈也称作线程调用堆栈。Java线程堆栈是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况等信息,从线程堆栈中可以得到以下信息:

  1. 线程的名字,ID,线程的数量等;
  2. 线程的运行状态,锁的状态(锁被那个线程持有,哪个线程在等待锁等);
  3. 函数间的调用关系,包括完整类名,所执行的方法,源代码的行数等;

可以通过Jstack获取应用运行时的线程堆栈,可以通过如下方式获取线程堆栈:jstack pid > jstack.log。对于Java应用而言,一下常见的几个性能问题都可以从线程堆栈入手定位:

  1. 系统挂起无响应
  2. 系统CPU较高
  3. 系统运行的响应时间长
  4. 线程死锁等
实战1、 找出cpu占用最高的线程

1、首先查看CPU的使用情况,使用top命令查看cpu占用情况,排在第一位的是进程号为30328的进程,占用了6.6%的cpu;

a1

2、找线程,知道进程号了,接着就是找线程了,输入以下命令

top -Hp 30328

打印结果如下,这里有一点需要注意,在我们加上-Hp指令后,PID展示就是线程的id了,这时候我们看到占用CPU最高的线程id是30365;

a2

还有另一种方式,就是使用ps命令来查找线程

ps -mp 30328 -o THREAD,tid,time| sort -n -k1 -r

以上的方式我们成功找到了占用cpu高的线程id是11631,但这个id是十进制的,在这里需要先转为16进制,输入命令

printf "%x\n" 30365

3、使用jstack分析堆栈快照

jstack 30328 | grep '线程id' -A 50

经过查看就知道是哪个线程出了问题

a3

二、一次生产环境的内存泄露问题

晚上吃过饭,正准备写会儿代码,清理一下本周的任务,突然报警里面报出许多慢请求日志,顿时感觉到不妙,马上查看监控发现内存快慢了,快撑不住了,情况紧急马上进入到容器的控制台,堆内存使用情况,果然老年代已经99%了,想到或许有内存泄露了,导致GC不掉日志。

heap-simpily

想到这里,一个电话打到运维哪里,赶紧先重启其他服务,留下一台dump出内存转储快照。运维也是很给力一会就把内存文件整出来了,使用MAT费了半天劲打开发现,一大部分已经被占据

微信图片_20211207211913

然后点开详情信息,发现QueryPlanCache这个类对象竟然占据了90%多的内存,看这类的名字,难道是缓存的数据吗?

微信图片_20211207211921

查了一下问题原因:

hibernate中的QueryPlanCache会缓存sql,以便于后边的相同的sql重复编译。如果in后的参数不同,hibernate会把其当成不同的sql进行缓存,从而缓存大量的sql导致heap内存溢出。

最新上线的服务刚刚进行完springboot改造,服务中还是有不少地方用到了in的查询的,这些数据被大量缓存导致了内存溢出,通过设置缓存最大值来进行限制,不设置默认是2G。

spring:
  jpa:
    properties:
      hibernate:
        query:
          plan_cache_max_size: 64
          plan_parameter_metadata_max_size: 32
          plan_cache_max_soft_references: 1024
          plan_cache_max_strong_references: 64

三、总结

生产问题不要急,冷静分析才可以,堆栈日志要查起,及时总结才可以。

posted @ 2021-12-09 10:21  Charming-Boy  阅读(186)  评论(0编辑  收藏  举报