月泉的博客

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

字数统计: 3k阅读时长: 13 min
2018/09/02 Share

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进程罢了

1
2
3
4
5
console: jps
11168 Jps
17536
11428 RemoteMavenServer
11320 DemoApplication

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

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

1
2
3
4
5
console: jps -m
17536
11428 RemoteMavenServer
12660 Jps -m
11320 DemoApplication

-q只显示vmid

1
2
3
4
5
console: jps -q
16960
17536
11428
11320

-q显示主类的全限定名

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

-v输出传递给Jvm的参数

1
2
3
4
5
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信息,当然默认是关闭的需要手动去开启,使用示例如下

1
2
3
[protocol:][[//]hostname][:port][/servername]

jps -l xxxx:9666

jinfo

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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差不多

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

jstat

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

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

1
2
3
4
5
6
7
8
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信息

1
2
3
4
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信息

1
2
3
4
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差不多只不过它较注重与容量相关的信息输出

1
2
3
4
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的原因

1
2
3
4
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堆内存映射信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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文件

1
jhat heap-dump-file

jstack

打印VM栈信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
λ 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识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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这个类时,是先用子节点一直委派到父节点,父节点找不到这个类才从自己这个类加载器中找

原文作者:yuequan

原文链接:http://www.lunaspring.com/2018/09/02/jvm2/

发表日期:September 2nd 2018, 3:05:00 pm

更新日期:July 11th 2019, 3:40:06 pm

版权声明:© 月泉 - 邓亮泉 版权所有

CATALOG
  1. 1. JVM调试工具和类结构还有类加载机制
    1. 1.1. JVM调试工具
    2. 1.2. Class文件结构
    3. 1.3. 类加载机制