jmap命令的实现原理解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jmap命令的实现原理解析相关的知识,希望对你有一定的参考价值。

参考技术A 当服务发生GC问题时,一般会使用jmap工具进行分析,jmap工具很强大,所以有必要了解它的方方面面。

通过histo选项,打印当前java堆中各个对象的数量、大小。
如果添加了live,只会打印活跃的对象。

通过-dump选项,把java堆中的对象dump到本地文件,然后使用MAT进行分析。
如果添加了live,只会dump活跃的对象。

通过-heap选项,打印java堆的配置情况和使用情况,还有使用的GC算法。

通过-finalizerinfo选项,打印那些正在等待执行finalize方法的对象。

通过-permstat选项,打印java堆永久代的信息,包括class loader相关的信息,和interned Strings的信息。

通过jmap和jvm之间进行通信,有两种实现方式:attach 和 SA。

attach方式,简单来说就是客户端和服务端之间的通信,客户端发送请求,主要逻辑在服务端执行,jmap相当于客户端,JVM相当于服务端。

在JVM中,有一个叫"Attach Listener"的线程,专门负责监听attach的请求,并执行对应的操作。

比如现在执行"jmap -histo:live 5409",一步一步的实现如下:
1、在Jmap.java类的main函数中,对参数进行解析。
2、解析出来参数中有“-histo:live”,则执行histo方法:

attach方法建立了jmap进程和JVM之间的socket连接,建立过程可以查看笨神的文章 JVM Attach机制实现 ,后续基于该连接进行通信。

因为命令行中添加了[:live]选项,这里的live参数是true。

再看看heapHisto方法

executeCommand方法基于之前的socket连接向JVM发送了一条"inspectheap"命令,当然了,还有参数。

虚拟机的"Attach Listener"线程当发现有新的命令时,就拿出来处理它。

命令和具体的函数对应关系如下:

和"inspectheap"对应的是heap_inspection方法,实现如下:

live_objects_only的值取决于请求中是否有"-live",再jmap中,取决于是否有":live",所以不管是不是添加了":live",都会有STW过程,时间长短而已。

在VM_GC_HeapInspection的doit方法中

_full_gc的值就是live_objects_only,如果为true,可能会执行一次full gc,清空非活跃的对象,但是可能会因为GC locker,导致跳过本次的GC。

"jmap -dump"实现的原理和"jmap -histo"类似,都是通过attach的方式实现,
attach API的实现方式是:
1、客户端连接到目标JVM,向其发出一个类似“inspectheap”命令;
2、目标JVM接收到命令,执行JVM内相关函数,将收集到的结果以文本形式返回;
3、客户端接收到返回的文本并将其显示出来;

假如执行"jmap -heap 5409",就不会使用attach方式实现了。

在参数解析中,如果参数是"-heap|-heap:format=b|-permstat|-finalizerinfo"中的一种,或者添加了"-F",比如"jmap -histo -F 5409",则使用SA的方式。

SA方式,和attach方式不同的是,相关的主要逻辑都在SA中实现,从JVM中获取数据即可。

可以大概看下"jmap -heap"的实现,对应的实现类是"HeapSummary",内部通过BugSpotAgent工具类attach到目标VM,更具体的底层细节,可以参考 HotSpot Serviceability Agent 实现浅析

执行jmap -heap有些时候可能会导致进程变T,一般是有一个线程在等信号量,这时会block住其它所有线程,可以执行kill -CONT <pid>进行恢复,不过还是强烈建议别执行这个命令。

Java - JVM - jmap 简单使用

  1. 概述

    1. 继续聊 jvm 命令行工具
  2. 背景

    1. 之前聊过一些简单的命令行工具

      1. jps
        1. 查看当前 java 进程
      2. jinfo
        1. 查看 java 运行参数
        2. 查看当前 相关系统变量
      3. jstat
        1. 查看 jvm 的 堆内存,gc 统计信息
    2. 如果我想查看堆内存里到底有什么, 有办法吗?

      1. 当然有啦
        1. jmap 为当前的堆内存打快照
        2. jhat 解析快照, 并以 httpserver 的形式, 对外发布
    3. 作为一个学渣, 之前这些东西, 我都得过且过, 或者压根不管他们存在不存在

      1. 就算存在, 也不耽误我写 bug
      2. 其实应该鼓起勇气, 跟随好奇心, 去看书, 去找各路学霸交流
  3. 环境

    1. OS
      1. win10
    2. Java
      1. 1.8.0_201
    3. demo
      1. Spring Boot
        1. 2.1.3
    4. shell
      1. win10 cmd

1. 准备

  1. 示例进程

    1. 随便起了个 spring-boot 的 webmvc 工程
      1. 写个 hello world 之类的就行
  2. jps

    1. 获取进程的 pid
  3. 约定

    1. 后面只描述 本地执行 的结果
      1. jmap 其实可以远程执行, 这个有兴趣的同学自己去研究吧

2. jmap

  1. 概述

    1. jmap 简介
  2. jmap

    1. 展示工具
      1. 展示内容
        1. 堆内存
        2. 共享对象的内存映射
          1. 这块我目前不是很了解
      2. 展示方法

3. 命令使用

1. 共享对象映射

  1. 命令

    >jmap 8944
    Attaching to process ID 8944, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.181-b13
    0x000000005f8a0000      52K     C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\management.dll
    0x000000005f8b0000      68K     C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\nio.dll
    0x000000005f8d0000      104K    C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\net.dll
    0x000000005f8f0000      140K    C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\instrument.dll
    0x000000005f920000      88K     C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\zip.dll
    0x000000005f940000      164K    C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\java.dll
    0x000000005f970000      60K     C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\verify.dll
    0x000000005f980000      8840K   C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\server\\jvm.dll
    0x0000000060230000      840K    C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\msvcr100.dll
    ...
    
  2. 内容

    1. JVM version is 25.181-b13
      1. jvm 版本
    2. 0x000000005f8a0000
      1. 共享对象所映射的 内存地址
    3. 52K
      1. 共享对象的大小
    4. C:\\Program Files\\Java\\jdk1.8.0_181\\jre\\bin\\management.dll
      1. 共享对象在本地的路径
  3. 共享对象

    1. 疑问1

      1. 这玩意是干啥的, 之前听人讲 jvm 内存, 印象中没人提起过
        1. 可能是我之前没认真听讲吧...
    2. 又是一些疑问

      1. 启动 java 程序, 每次都要启动 jvm, 加载系统类
        1. 既然是这样的话, 有没有什么办法, 可以让这些老熟人能够区别于通用的类, 加载得更快
      2. 如果在一个环境下, 启动多个 jvm
        1. 如果每个 jvm 都把这些类加载一遍, 是不是又费时又费力
        2. 有没有办法, 把这个进程加快
    3. 共享对象

      1. 引入时间

        1. java 1.5
      2. 目的

        1. 加快特定类型的加载
          1. 特别是对 小型 java 应用, 效果拔群
        2. 并对这些特定类型, 提供 跨 jvm 共享
      3. 机制

        1. 文件机制
          1. linux
            1. classes.jsa 与 classeslist
              1. 将多个类放入 classes.jsa 中
              2. 将具体的类名, 记录在 classeslist 中
          2. win
            1. 老实说, 我还没找到具体的机制
            2. 但是从结果来看, 类型都放到了 dll 文件里
        2. 加载机制
          1. 这块暂时也没弄太明白
        3. 内存机制
          1. 这个我也暂时没弄明白
            1. 比如在 jvm 内存中的哪个位置
            2. 不过按理说, 应该会在 meta space 那块
            3. 而且通常, 这些 公用的类型, 应该也是 只读 的
      4. 其他

        1. 当然这个并不是重点, 看不懂也没关系

2. 堆内存概况

  1. 命令

    >jmap -heap 8944
    Attaching to process ID 8944, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.181-b13
    
    using thread-local object allocation.
    Parallel GC with 10 thread(s)
    
    Heap Configuration:
       MinHeapFreeRatio         = 0
       MaxHeapFreeRatio         = 100
       MaxHeapSize              = 17163091968 (16368.0MB)
       NewSize                  = 357564416 (341.0MB)
       MaxNewSize               = 5721030656 (5456.0MB)
       OldSize                  = 716177408 (683.0MB)
       NewRatio                 = 2
       SurvivorRatio            = 8
       MetaspaceSize            = 21807104 (20.796875MB)
       CompressedClassSpaceSize = 1073741824 (1024.0MB)
       MaxMetaspaceSize         = 17592186044415 MB
       G1HeapRegionSize         = 0 (0.0MB)
    
    Heap Usage:
    PS Young Generation
    Eden Space:
       capacity = 268435456 (256.0MB)
       used     = 26775152 (25.534774780273438MB)
       free     = 241660304 (230.46522521972656MB)
       9.974521398544312% used
    From Space:
       capacity = 44564480 (42.5MB)
       used     = 20312032 (19.371063232421875MB)
       free     = 24252448 (23.128936767578125MB)
       45.578972311580884% used
    To Space:
       capacity = 44564480 (42.5MB)
       used     = 0 (0.0MB)
       free     = 44564480 (42.5MB)
       0.0% used
    PS Old Generation
       capacity = 468189184 (446.5MB)
       used     = 15216480 (14.511566162109375MB)
       free     = 452972704 (431.9884338378906MB)
       3.2500708089830628% used
    
    16826 interned Strings occupying 2198488 bytes.
    
  2. 解释

    1. Parallel GC with 10 thread(s)

      1. 使用了 Parallel GC
      2. 使用了 10 个线程
    2. Heap Configuration

      1. 堆配置
        1. 具体配置我就不细说了
    3. Heap Usage

    4. 16826 interned Strings occupying 2198488 bytes.

      1. 有 16826 个内置字符串
      2. 占用了 2M 的空间
  3. 疑问

    1. NewRatio
      1. 完全不懂
    2. SurvivorRatio
      1. 完全不懂
    3. CompressedClassSpaceSize
      1. 它不是 meta space 的一部分吗, 怎么比 meta 还大
    4. internal Strings
      1. 这块老实说, 我也不是很懂...

3. 析构信息概述

  1. 命令

    >jmap -finalizerinfo 8944
    Attaching to process ID 8944, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.181-b13
    Number of objects pending for finalization: 0
    
  2. 解释

    1. Number of objects pending for finalization: 0
      1. 有 0 个对象, 即将被析构

4. 类型统计

  1. 命令

    >jmap -histo 8944
    
     num     #instances         #bytes  class name
    ----------------------------------------------
       1:        370446       47173928  [C
       2:         13190       16507488  [B
       3:        237789        5706936  java.lang.String
       4:          3867        3913304  [I
       5:         56074        2945096  [Ljava.lang.String;
       6:         42757        1368224  java.io.File
       7:         32465        1298600  java.util.LinkedHashMap$Entry
    
    ...
    ...
    
  2. 解释

    1. 排列顺序

      1. 默认是按照 bytes 的 降序 来做排列的
    2. 1: 370446 47173928 [C

      1. 1
        1. 编号为 1
      2. 370446
        1. 实例个数, 为 370446 个
      3. 47173928
        1. 实例占据内存空间, 为 47M
      4. [C
        1. 类型名为 [C
  3. 继续解释

    1. live
      1. -histo 还可以跟 live 参数
        1. -histo:live
        2. 结果会有比较大的差别
          1. live 只展现当前 存活 的对象
  4. 疑问

    1. [C
      1. 类型
        1. 有的类型, 不是直接显示的类型, 而是显示的这个...
        2. 我现在也是 蒙圈的, 后续再讲吧

5. 输出 dump 文件

  1. 命令

    # 1. 这次就带上 live 了
    # 2. 写作中途电脑又重启了, 所以 pid 换了
    >jmap -dump:live,format=b,file=dump.txt 14504
    Dumping heap to E:\\dump.txt ...
    Heap dump file created
    
  2. 解释

    1. 选项
      1. -dump:live
        1. dump 当前 java 进程的堆内存
      2. format=b
        1. 使用 hprof 二进制格式
      3. file=dump.txt
        1. 指定输出文件名, 为 dump.txt
      4. 分割
        1. 使用 , 分割选项
  3. 结果

    1. 产生了一个 dump 文件
      1. 这个后续再讲吧...

ps

  1. ref

    1. \'Shared Object Memory\' vs \'Heap Memory\' - Java
      1. 引出了第二个 ref
    2. what is shared objects file?
      1. 引出了 第三个 ref
    3. Class Data Sharing
      1. 类型数据共享
        1. 官方文档
        2. 简单描述了下机制
        3. 感兴趣的童鞋, 可以自己了解, 这个和我关系不大, 我就不多说了
    4. jmap
      1. 官方文档
    5. Java-String.intern的深入研究
      1. internal Strings 的相关研究
        1. 有空的同学可以看一看
    6. jmap命令详解----查看JVM内存使用详情
      1. 博主讲得还不错
  2. 后续

    1. jhat
    2. 可视化工具
      1. 命令行工具讲完了, 就该讲用户喜闻乐见的可视化工具了
    3. jvm 内存的相关知识
    4. 之前那个 模糊的类型, 最好也了解一下

以上是关于jmap命令的实现原理解析的主要内容,如果未能解决你的问题,请参考以下文章

git知识总结-3常用命令原理解析

vue中楼层滚动实现原理解析

vue数据双向绑定原理-解析器Complie

Spring MVC工作原理及源码解析 ViewResolver实现原理及源码解析

感知机原理解析与代码实现

Spring MVC工作原理及源码解析DispatcherServlet实现原理及源码解析