概述
一说到 JVM 三个字母,你脑子里首先蹦出来的是什么?我分析一般有以下三种人:
- 第一种:JVM 三个字母,分开我是认识的,一组合,我不知道是啥
- 第二种:他不就是 Java 虚拟机么,跑 Java 程序的
- 第三种:分为堆内存,方法区,老年代,新生代.....巴拉巴拉...,可以跟你说几个小时的
经过我的日常观察,一般第一种人是程序猿的家属,第二种人是程序猿的同事(非 Java 工作的同事),第三种人就是程序猿本猿了,而本文的适合读者是第三种人,我试图从不同的视角出发,带领程序猿们站在另外一个高度点重新全面的重新认识一次 JVM,从浅到深的逐步深入,再一次对我们认知的 JVM 做一个全面的梳理,不仅知道它有堆内存、方法区、老年代、新生代...更要知道其原理,也要知道其使用的方法。在深入理解其设计原理的基础上,再对它进行使用和调优,就会更加的得心应手。
接下来,让我逐步带领大家进入 JVM 的世界!后续我将以 JVM 系列文章的形式,逐步带大家从浅到深的认识 JVM。
声明:以下内容篇幅较长,请耐心看完,你一定会有不一样的收获!
一切从官网开始
那么认识 JVM 的第一步该是什么呢?有很多人第一次认识 JVM 是在百度或者 google 上的,其实第一步你就错了,我认为一切应该还是从官网开始,从它出生的地方先做一个全面的了解。
Java Platform Standard Edition 8 Documentation
官网地址:docs.oracle.com/javase/8/do…
Reference -> Developer Guides -> 定位到:docs.oracle.com/javase/8/do…
Tips:花点时间,耐心的读完这段话,你会有新的心得的!
Oracle has two products that implement Java Platform Standard Edition (Java SE) 8: Java SE Development Kit (JDK) 8 and Java SE Runtime Environment (JRE) 8.
JDK 8 is a superset of JRE 8, and contains everything that is in JRE 8, plus tools such as the compilers and debuggers necessary for developing applets and applications. JRE 8 provides the libraries, the Java Virtual Machine (JVM), and other components to run applets and applications written in the Java programming language. Note that the JRE includes components not required by the Java SE specification, including both standard and non-standard Java components.
The following conceptual diagram illustrates the components of Oracle's Java SE products:
Getting Started with the G1 Garbage Collector
官网地址:www.oracle.com/technetwork…
Exploring the JVM Architecture
Hotspot Architecture
The HotSpot JVM possesses an architecture that supports a strong foundation of features and capabilities and supports the ability to realize high performance and massive scalability. For example, the HotSpot JVM JIT compilers generate dynamic optimizations. In other words, they make optimization decisions while the Java application is running and generate high-performing native machine instructions targeted for the underlying system architecture. In addition, through the maturing evolution and continuous engineering of its runtime environment and multithreaded garbage collector, the HotSpot JVM yields high scalability on even the largest available computer systems.
The main components of the JVM include the class loader, the runtime data areas, and the execution engine.
阅读理解一下(摘自上文):
原文:
In other words, they make optimization decisions while the Java application is running and generate high-performing native machine instructions targeted for the underlying system architecture
翻译:
换句话说,它能在 Java 应用程序运行时做出优化决策,并生成针对底层系统架构的高性能本地机器指令。
意思就是说,JVM 的最大能力是,针对底层不同的操作系统,将 Java 程序翻译成不同操作系统能够认识和执行的本地机器指令,提供给操作系统进行运行。
以下这个图对于认识 JVM 的人来说一点都不陌生(一次编译到处运行):
源码到类文件,再到字节码
我们先写一个最简单的 HelloWorld.java,编译一下,得到 HelloWorld.class 文件。以上两个步骤是我们每一个 Java 程序猿最清楚不过的了,一个是.java 结尾的源码文件,一个是.class 结尾的字节码文件,我们都知道一个 java 程序要想运行起来,必须经过编辑器编译成.class 字节码文件,JVM 才可以运行起来。但是你有没有想过,这个.class 结尾的字节码文件是如何运行起来,并且在控制台上打印出“Hello World”的呢?
别急,通过以下几个步骤的解读,让我带领大家进入 JVM 的世界,从不同的角度来重新认识一次 JVM。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
复制代码
查看一个 class 文件字节码
我们先通过 JDK 自带的命令:hexdump -C HelloWorld.class 来查看下字节码。
输入以上命令后,输出以下内容,现在这一堆东西,你一定看不懂,不急,先看下官网是怎么说的,看下面内容。
00000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09 |.......4."......|
00000010 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07 |................|
00000020 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.....<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 |umberTable...Loc|
00000050 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 |alVariableTable.|
00000060 00 04 74 68 69 73 01 00 0c 4c 48 65 6c 6c 6f 57 |..this...LHelloW|
00000070 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 |orld;...main...(|
00000080 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 |[Ljava/lang/Stri|
00000090 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b |ng;)V...args...[|
000000a0 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e |Ljava/lang/Strin|
000000b0 67 3b 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 |g;...SourceFile.|
000000c0 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 |..HelloWorld.jav|
000000d0 61 0c 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 |a...............|
000000e0 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 07 00 1f 0c |.hello world....|
000000f0 00 20 00 21 01 00 0a 48 65 6c 6c 6f 57 6f 72 6c |. .!...HelloWorl|
00000100 64 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 |d...java/lang/Ob|
00000110 6a 65 63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 |ject...java/lang|
00000120 2f 53 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 |/System...out...|
00000130 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 |Ljava/io/PrintSt|
00000140 72 65 61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f |ream;...java/io/|
00000150 50 72 69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 |PrintStream...pr|
00000160 69 6e 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c |intln...(Ljava/l|
00000170 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 |ang/String;)V.!.|
00000180 05 00 06 00 00 00 00 00 02 00 01 00 07 00 08 00 |................|
00000190 01 00 09 00 00 00 2f 00 01 00 01 00 00 00 05 2a |....../........*|
000001a0 b7 00 01 b1 00 00 00 02 00 0a 00 00 00 06 00 01 |................|
000001b0 00 00 00 06 00 0b 00 00 00 0c 00 01 00 00 00 05 |................|
000001c0 00 0c 00 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 |................|
000001d0 00 00 00 37 00 02 00 01 00 00 00 09 b2 00 02 12 |...7............|
000001e0 03 b6 00 04 b1 00 00 00 02 00 0a 00 00 00 0a 00 |................|
000001f0 02 00 00 00 08 00 08 00 09 00 0b 00 00 00 0c 00 |................|
00000200 01 00 00 00 09 00 10 00 11 00 00 00 01 00 12 00 |................|
00000210 00 00 02 00 13 |.....|
00000215
复制代码
The ClassFile Structure(字节码解析)
看看官网是如何解释字节码文件的:
官网地址:docs.oracle.com/javase/spec…
A class file consists of a single ClassFile structure:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
复制代码
解读字节码文件
第一个,u4 magic;
- u4:代表 4个字节 Byte(0x cafebabe),他是个魔数(不具有业务含义,只是表明了该文件的类型)
基础知识:
一个字节存储 8位无符号数,储存的数值范围为 0-255。1111 0000(为 1个字节)
1Byte = 8bit
1Byte = 2 位16 进制
4Byte = 0x cafebabe
所以,u4 magic,就是 0x cafebabe,转换为二进制为:1100 1010 1111 1110 1011 1010 1011 1110
第二个和第三个 ,u2 minor_version; u2 major_version; (合并组合为版本号)
- u2 minor_version:代表 2个字节(0x 00 00),代表小版本号
- u2 major_version:代表 2个字节(0x 00 34),代表主版本号
- 主版本号+小版本号合并起来,组成版本号,0x 00000034 -->转换成十进制就是:52(查找对应表,表示版本号为:Java SE8)
第四个,u2 constant_pool_count; 表示常量数量
constant_pool_count
The value of the constant_pool_count item is equal to the number of entries in the constant_pool table plus one. A constant_pool index is considered valid if it is greater than zero and less than constant_pool_count, with the exception for constants of type long and double noted in §4.4.5.
- u2 constant_pool_count:代表 2个字节(0x 00 22),转换十进制值为:34,代表常量池中,常量的数量有 34-1=33 个
- 那么这个类里面到底有哪些常量呢? 看下面的常量数组:
第五个,cp_info constant_pool[constant_pool_count-1]; 表示常量数组
constant_pool[]
The constant_pool is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile structure and its substructures. The format of each constant_pool table entry is indicated by its first "tag" byte.
The constant_pool table is indexed from 1 to constant_pool_count - 1.
- 每个常量的形式:
The Constant Pool
Java Virtual Machine instructions do not rely on the run-time layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table.
All constant_pool table entries have the following general format:
cp_info {
u1 tag; //表示:常量的类型
u1 info[]; //表示:
}
复制代码
第一个常量:
- u1 tag:代表 1个字节(0x 0a),转换为十进制值为:10,找到以下对应表,表示是一个方法类型的常量
// 方法类型常量
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
复制代码
常量编号 | u1 tag | 常量类型 | 常量内容 |
---|---|---|---|
#1 | 0x 0a-->10 | CONSTANT_Methodref | u2 class_index:0x 00 06--> #6 u2 name_and_type_index:0x 00 14 --> #20 #6.#20 |
其他常量解析:(按照上面的方法,一步一步,就可以解析出来这样一张表格)
到现在为止,你一定还是看不懂解析出这一堆有啥用,不着急,先看下面的一个命令。
常量编号 | u1 tag | 常量类型 | 常量内容 |
---|---|---|---|
#1 | 0x 0a-->10 | CONSTANT_Methodref | u2 class_index:0x 00 06--> #6 u2 name_and_type_index:0x 00 14 --> #20 #6.#20 |
#2 | 0x 09-->9 | CONSTANT_Fieldref | u2 class_index:0x 00 15--> #21 u2 name_and_type_index:0x 00 16-> #22 #21.#22 |
#3 | 0x 08-->8 | CONSTANT_String | u2 string_index:0x 00 17--> #23 |
#4 | 0x 0a-->10 | CONSTANT_Methodref | u2 class_index:0x 00 18--> #24 u2 name_and_type_index:0x 00 19 --> #25 #24.#25 |
... | |||
#33 |
javap 反汇编
- 命令:javap -v -p -c HelloWorld.class > HelloWorld_javap.txt (将 HelloWorld 字节码文件进行反汇编,生成的内容写入 HelloWorld_javap.txt)
// 截取部分内容如下:
Classfile /.../HelloWorld.class
Last modified 2021-9-28; size 533 bytes
MD5 checksum c3ef731e76bb9c516e7840a04a0c71af //<-----------magic
Compiled from "HelloWorld.java"
public class HelloWorld
minor version: 0
major version: 52 //<-----------------版本号
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // hello world
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
...
#33
...
复制代码
小结
从上面我们的分析,我们大概知道了一个 Java 源文件需要首先编译成 class 文件后,JVM 虚拟机才能真正的运行这段代码的一个全过程,那么接下来,我们要研究下,class 文件是如何被加载到 JVM 的,Java 类加载机制。