月泉的博客

JVM调试工具和类结构还有类加载机制

月泉 JVM

JVM调试工具和类结构还有类加载机制

大纲如下:

  • JVM调试工具
  • Class文件结构
  • 类加载机制

JVM调试工具

打开$JAVA_HOME/bin目录下,当然我这里是指的JDK(Java Development Kit)可以看到里面很多可执行文件,这里面都是官方提供的一些工具,都是基于$JAVA_HOME/lib/tools.jar进行的封装,这里介绍几款常用的以及我常用的使用方式。

命令行

  • jps
  • jinfo
  • jstat
  • jmap
  • jhat
  • jstack

图形化(以后再说)

  • jconsole
  • jvisualvm

jps

和Linux下的ps命令很类似,只不过Linux下的ps命令针对的是系统进程,而jps是列出Jvm进程罢了

console: jps
11168 Jps
17536
11428 RemoteMavenServer
11320 DemoApplication

前面那个数据就是所谓的vmid通常是与pid一致的,这个命令还有几个可选参数

-m参数既是会打印执行main函数时时传入的参数

console: jps -m
17536
11428 RemoteMavenServer
12660 Jps -m
11320 DemoApplication

-q只显示vmid

console: jps -q
16960
17536
11428
11320

-q显示主类的全限定名

console: jps -l
17536
11428 org.jetbrains.idea.maven.server.RemoteMavenServer
11320 org.yuequan.test.demo.DemoApplication
1384 sun.tools.jps.Jps

-v输出传递给Jvm的参数

console: jps -v
17536  -Xms128m -Xmx750m -XX:ReservedCodeCacheSize=240m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Djb.vmOptionsFile=C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin\idea64.exe.vmoptions -Didea.jre.check=true -Dide.native.launcher=true -Didea.paths.selector=IntelliJIdea2018.1 -XX:ErrorFile=C:\Users\yuequan\java_error_in_idea_%p.log -XX:HeapDumpPath=C:\Users\yuequan\java_error_in_idea.hprof
11428 RemoteMavenServer -Djava.awt.headless=true -Didea.version==2018.1.4 -Xmx768m -Didea.maven.embedder.version=3.3.9 -Dfile.encoding=GBK
13108 Jps -Dapplication.home=C:\Program Files\Java\jdk1.8.0_171 -Xms8m
11320 DemoApplication -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=53521 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\lib\idea_rt.jar=53522:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin -Dfile.encoding=UTF-8

同时也可以调用远程的jps信息,当然默认是关闭的需要手动去开启,使用示例如下

[protocol:][[//]hostname][:port][/servername]

jps -l xxxx:9666

jinfo

是根据传入的vmid(当然也可以是远程)来查看虚拟机信息的

console: jinfo 11320
Attaching to process ID 11320, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
Java System Properties:

com.sun.management.jmxremote.authenticate = false
spring.output.ansi.enabled = always
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.171-b11
sun.boot.library.path = C:\Program Files\Java\jdk1.8.0_171\jre\bin
java.vendor.url = http://java.oracle.com/

.....................信息太多了省略

-flag传入名称可以查询指定的名称参数,还有一个-flags差不多

λ jinfo -flag MaxNewSize 11320
-XX:MaxNewSize=711458816

jstat

用于显示检测JVM统计的性能信息

-class显示类的装载卸载信息,往泛了说就是统计类加载器的行为

console: jstat -class 11320 1000 20
Loaded  Bytes  Unloaded  Bytes     Time
  6224 11228.1        0     0.0       4.04
  6224 11228.1        0     0.0       4.04
  6224 11228.1        0     0.0       4.04
  6224 11228.1        0     0.0       4.04
  6224 11228.1        0     0.0       4.04
  6224 11228.1        0     0.0       4.04

vmid后面跟着的1000是代表每1秒,20就是重复20次,也就是20次20秒

-compiler显示虚拟机的JIT信息

console: jstat -compiler 11320 500 2
Compiled Failed Invalid   Time   FailedType FailedMethod
    3198      0       0     0.77          0
    3198      0       0     0.77          0

-gc显示堆GC信息

console: jstat -gc 11320 600 2
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
8192.0 11776.0 8171.7  0.0   111616.0 76573.5   51200.0    12523.0   29440.0 28144.0 4096.0 3772.1      6    0.048   1      0.038    0.085
8192.0 11776.0 8171.7  0.0   111616.0 76573.5   51200.0    12523.0   29440.0 28144.0 4096.0 3772.1      6    0.048   1      0.038    0.085

S0C:显示当前虚拟机Survivor0的内存容量

S1C:显示当前虚拟机Survivor1的内存容量

S0U:显示当前虚拟机Survivor0的已用空间大小

S1U:显示当前虚拟机Survivor1的已用空间大小

EC:当前虚拟机Eden区的内存容量

EU:当前虚拟机Eden区已用空间大小

OC:老年代的内存容量

OU:老年代已用内存空间大小

MC:Metaspace内存容量

MU:显示Metasapce已用空间大小

CCSC:压缩类空间容量

CCSU:压缩类空间容量已用空间大小

-gccapacity-gc差不多只不过它较注重与容量相关的信息输出

console: jstat -gccapacity 11320 600 2
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
 43520.0 694784.0 137728.0 8192.0 11776.0 111616.0    87552.0  1390080.0    51200.0    51200.0      0.0 1075200.0  29440.0      0.0 1048576.0   4096.0      6     1
 43520.0 694784.0 137728.0 8192.0 11776.0 111616.0    87552.0  1390080.0    51200.0    51200.0      0.0 1075200.0  29440.0      0.0 1048576.0   4096.0      6     1

NGCMN:最小新生代大小

NGCMX:最大新生代大小

NGC:当前新生代容量

YGC:新生代GC次数

FGC:Full GC 次数

-gccause-gc也是差不多的只不过它更关注于上一次和当前GC的原因

console: jstat -gccause 11320 600 2
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
 99.75   0.00  69.60  24.46  95.60  92.09      6    0.048     1    0.038    0.085 Allocation Failure   No GC
 99.75   0.00  69.60  24.46  95.60  92.09      6    0.048     1    0.038    0.085 Allocation Failure   No GC

GCC:当前GC的原因

LGCC:上一次GC的原因

还有-gcnew-gcnewcapacity-gcold-gcoldcapacity-gcmetaspace-gcutil都是差不多的,-gcutil是统计GC的信息摘要,其它几个命令就顾名思义了

jmap

打印相应的vmid或者远程虚拟机的内存映射信息,还在实验性阶段,这里介绍几个我常用的

-dump file=filename可以输出文件

-heap输出GC堆内存映射信息

console: jmap -heap 11320
Attaching to process ID 11320, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2134900736 (2036.0MB)
   NewSize                  = 44564480 (42.5MB)
   MaxNewSize               = 711458816 (678.5MB)
   OldSize                  = 89653248 (85.5MB)
   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 = 114294784 (109.0MB)
   used     = 80316248 (76.59554290771484MB)
   free     = 33978536 (32.404457092285156MB)
   70.2711402823072% used
From Space:
   capacity = 8388608 (8.0MB)
   used     = 8367792 (7.9801483154296875MB)
   free     = 20816 (0.0198516845703125MB)
   99.7518539428711% used
To Space:
   capacity = 12058624 (11.5MB)
   used     = 0 (0.0MB)
   free     = 12058624 (11.5MB)
   0.0% used
PS Old Generation
   capacity = 52428800 (50.0MB)
   used     = 12823520 (12.229461669921875MB)
   free     = 39605280 (37.770538330078125MB)
   24.45892333984375% used

16116 interned Strings occupying 2144264 bytes.

jhat

也是一个实验性的工具,启动一个小型的http服务器来分析dump文件

jhat heap-dump-file

jstack

打印VM栈信息

λ jstack 11320                                                                                                                        
2018-09-02 11:22:28                                                                                                                   
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):                                                           
                                                                                                                                      
"DestroyJavaVM" #45 prio=5 os_prio=0 tid=0x000000001c4f7000 nid=0x1a94 waiting on condition [0x0000000000000000]                      
   java.lang.Thread.State: RUNNABLE                                                                                                   
                                                                                                                                      
"http-nio-8080-AsyncTimeout" #43 daemon prio=5 os_prio=0 tid=0x000000001c4fd000 nid=0x1028 waiting on condition [0x000000001ed1f000]  
   java.lang.Thread.State: TIMED_WAITING (sleeping)                                                                                   
        at java.lang.Thread.sleep(Native Method)                                                                                      
        at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1143)                                            
        at java.lang.Thread.run(Thread.java:748)                                                                                      
                                                                                                                                      
"http-nio-8080-Acceptor-0" #42 daemon prio=5 os_prio=0 tid=0x000000001c4fc800 nid=0x1d58 runnable [0x000000001ec1f000]                
   java.lang.Thread.State: RUNNABLE                                                                                                   
        at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)                                                                  
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)                                                ............................

Class文件结构

众说周知Class文件实际上指的就是字节码文件,它为什么叫字节码文件呢? 因为它的内容是严格紧密无空格并且有序的字节流排列组合(big endian),严格遵循JVM规范。

了解之前先知道定义的字节码的类型,分别为u1、u2、u4 分别为1、2、4无符号整数,每个class文件都会根据JVM规范按照ClassFile结构排放才能够被JVM识别

ClassFile {
    u4              magic;
    u2              minor_version;
    u2              major_version;
    u2              constraint_pool_count;
    cp_info         constraint_pool[constraint_pool_count - 1];
    u2              access_flag;
    u2              this_class;
    u2              super_class;
    u2              interfaces_count;
    u2              interfaces[interfaces_count];
    u2              field_count;
    field_info      field_info[field_count];
    u2              methods_count;
    method_info     methods[methods_count];
    u2              attributes_count;
    attribute_info  attributes[attributes_count];
}

现在简略的阐述下各个分类的含义,内容可能比较干和简略(因为并不想写太长2333),建议配合我之前的一篇文章食用手把手分析字节码文件

开头是u4 也就是4个字节这4个字节是魔数 0XCAFEBABE

接着是2个u2,分别代表此次版本号和主版本号

接着是u2,表示常量池数量

接着是一个cp_inf结构,长度根据常量池数量与结构来判断,常量池主要用来存放字面量和符号引用,符号引用指的就是:字段名称和描述符、方法名称和描述符、类和接口的全限定名

接着是2个字节的访问表示符例如:ACC_PUBLIC具体参照虚拟机文档

接着是u2的字段数量

字段数量后也是跟了一个接口体field_info,其中的信息主要包括类中定义的类字段和实例字段

接着是方法数量

方法数量后是methods_info结构体,这个结构体只描述当前类或者接口声明的方法包括实例方法和类方法,但不包括父类和父接口的方法

接着是属性数量

属性数量后面是一个attributes结构体,其中每个项的至都是attribute_info结构

这个小节的内容和之前写的那篇文章有很多重复的地方,我不太喜欢写重复的东西,所以简要说一下就算带过了。

类加载机制

虚拟机的类加载机制可以分为7个阶段

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化
  6. 使用
  7. 卸载

逐个简要的说明一下

加载

该阶段主要是将字节码读取到JVM中,Java虚拟机并没有规定必须是从class文件中读取,所以对于加载class文件的管控是非常放松的,你可以从网络上读取,可以从数据库中读取,只要读出来是个规范的字节码文件就可以

验证

验证主要是验证字节码是否符合规范、是否会危害到虚拟机、验证其常量池的引用是否存在,验证是否存在不符合规范的行为,例如继承了final类等。

准备

该阶段主要是对所有字段赋予零值

解析

将常量池符号替换为直接引用,直接引用可以是一个目标指针也可以是一个相对偏移量也可以是一个引用句柄

初始化

执行进行赋值操作和静态语句块,对于静态赋值可以不按定义的顺序来,但绝不能向前引用

使用/卸载

故名思意。

类加载器

要判断一个类是否相等(通指 instanceof == 等)要在它们是同一类加载器的前提下,否则尽管类的内容全部是一致的,但是不属于同一个类加载器加载 那么它们就不是相等的,在虚拟机眼中只有2种类加载器,一种是启动类加载器、一种是其它加载器,启动类加载器是由JVM所提供,而其它加载器由Java编写,在Java程序眼中类加载器分为三种,这三种在虚拟机眼里都是属于其它加载器,分别为:引导类加载器、扩展类加载器、应用程序类加载器。

引导类加载器

引导类加载器主要用于加载$JAVA_HOME/lib下的类库和在参数-Xbootclasspath中指定的类库

扩展类加载器

扩展类加载器主要用于加载$JAVA_HOME/lib/ext下的类库和指定环境中的java.ext.dirs中的类库

应用程序类加载器

用于加载应用程序的类和库的加载器,程序执行中的默认类加载器就是它了

说到类加载器就不得不说一个概念叫做”双亲委派“,双亲委派其实很简单,举个栗子,例如应用程序类加载器的父级是扩展类加载器,扩展类加载器的父类是类加载器,这时候如果要寻找java.lang.Object这个类时,是先用子节点一直委派到父节点,父节点找不到这个类才从自己这个类加载器中找

月泉
伪文艺中二青年,热爱技术,热爱生活。