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


    本文对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
    

    三、总结

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

  • 相关阅读:
    akka设计模式系列-actor锚定
    Akka源码分析-Remote-位置透明
    Akka源码分析-Remote-网络链接生命周期
    Akka源码分析-Remote-收消息
    Akka源码分析-Remote-网络链接
    Akka源码分析-Remote-发消息
    Akka源码分析-Remote-Actor创建
    Akka源码分析-Remote-ActorSystem
    Akka源码分析-ask模式
    Akka源码分析-深入ActorRef&ActorPath
  • 原文地址:https://www.cnblogs.com/ChenBingJie123/p/15665803.html
Copyright © 2020-2023  润新知