前言
- 文中所用到的class文件结构思维导图下载:class文件思维导图(这个思维导图的来源是下面 的
jvm class 文件格式 官网
) - jvm 13版本 规范 HTML 版本:https://docs.oracle.com/javase/specs/jvms/se19/html/index.html
- java 各版本和 JVM各版本下载:https://docs.oracle.com/javase/specs/index.html
- 本博文是以jdk8版本学习的,文档是13版本。
- 本博客是讲:
- JVM的一些基础、跨平台的语言和跨语言的平台。
- 字节码
Class 文件(Class File Format)
的十六进制的布局、含义,看class文件的几个工具。
- 下个博文讲,class文件如何加载(load)到内存以及加载的。
- 知识体系分布:
- JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
- JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
- JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
- JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
一、JVM基础
1、cross platform 跨平台
即任何语言只要编译成 class 文件,在装有JVM任何的系统上 都可以运行。
2、cross language 跨语言
即:有好多语言是在JVM上运行的,这就是夸语言的。
3、什么是JVM呢?一张图告诉你
4、java从编码到执行*****
- 通过 javac 命令:将 java文件 编译成 class 文件
- 通过 java 命令:classloader 加载 class 文件以及 java 类库 到
内存
中进行装载,装载完成后, 然后调用字节码解释器
或者JIT即时编译器
来进行解释 或者 编译。 - 之后 由 执行引擎 进行 执行,最终到OS操作系统。
- 那java是解释执行的还是编译执行的呢?其实
解释和编译是可以混合的
,如果代码用到的次数比较多,会把代码做成 即时编译 即做成本地的编译,就类似c语言在win中编译成exe文件,这样效率会比较高。
5. 从跨平台的语言到跨语言的平台
JVM:是跨语言的平台。
java:是跨平台的语言。
在JVM上能够跑的语言 到目前有100多种,比如下图中的Scala、groovy语言等。
6. jvm与class文件格式
== jvm跟java无关==
7. JVM
- jvm是一种规范 – java virtual machine specifications
https://docs.oracle.com/en/java/javase/13/
https://docs.oracle.com/javase/specs/index.html
- JVM 是 虚构出来的一台计算机
- 字节码指令集(汇编语言)
- 内存管理:栈 堆 方法区等
白话解释:
JVM是一台虚拟出来的一台机器,也就有自己的CPU、内存管理,比如栈、堆、方法区,所以也就有后面的JVM调优等等。
8. javac的过程
9. 常见的JVM实现
Hotspot
- oracle官方,我们做实验用的 JVM
- java –version
Jrockit
- BEA,曾经号称世界上最快的 JVM
- 被Oracle收购,合并于hotspot
J9 – IBM
Microsoft VM
TaobaoVM
- hotspot深度定制版 ▪
LiquidVM
- 直接针对硬件 ▪
azul zing
- 最新垃圾回收的业界标杆
- www.azul.com
Hotspot:就是上面所说的第一个JVM类型
mixed mode:就是 上面 1.4说的混合模式,解释和编译混合执行。
10. JDK JRE JVM
jdk全称:
java development kit
,其意思是java开发工具包
。jdk是sun公司开发的,jdk包括jre(java runtime environment)java运行环境,一堆java工具[java的编译器(java c.exe),java解释执行器(java.exe)]和java基础的类库(有3000多类,常用的类150多个)。JRE(Java Runtimely Environment)
,java运行环境,只能运行.class文件,不能编译,针对用户。JRE,包含一个JVM(java虚拟机),与java核心类库与其所支持的文件。与JDK不同,它不包含开发工具—编译器,调试器和其他工具。JVM(java Virtual Machine )
,Java虚拟机,Java运行环境。Java虚拟机,是一种虚拟出来的计算机,是通过在实际的计算机上模拟仿真各种计算机功能来实现的。
二、Class File Format (class 文件格式)
分析和学习class文件。目前公司面试很少用到,但是需要学习和了解哈,抱着兴趣去学。
不能抱着功利性去学。
class文件:就是编译完成之后的 .class
文件
1、测试小程序
a、T0100_ByteCode01.java
最简单的测试小程序,就是一个类,是为了方便观察其编译后的内容,然后由简到繁,逐步学习。
package com.mashibing.jvm.c1_bytecode;
public class T0100_ByteCode01 {
}
然后编译,生成 T0100_ByteCode01.class
文件。
b、idea打开T0100_ByteCode01.class
如果在idea中打开编译后的 T0100_ByteCode01.class
的文件,就是idea会帮我们进行反编译
,反编译的多了一个默认的无参构造函数,这是默认添加的。
注释是反编译出来的注释
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.mashibing.jvm.c1_bytecode;
public class T0100_ByteCode01 {
public T0100_ByteCode01() {
}
}
2、class文件
任何的文件都应该是010101二进制,
class文件
如果用一个16进制编辑器(sublime text 3
)打开,就是如下图所示:class文件
是 二进制字节流。数据类型:u1 u2 u4 u8 和 _info (表类型)(
只是逻辑上分的,其实没有数据类型,只有0和1
)(u:Unsigned,意为为无符号的,u1指1个字节;u2指2个字节;u3指3个字节;u4指4个字节;u8指8个字节
)_info
的来源 是Hotspot 源码中的写法
查看
16进制格式classFile
的工具sublime
(打开如上图) /notepad
idea插件:
Bined
(上面的红框可以查看二进制、八进制、十六进制的文件,当然二进制是最根本的文件格式)
有很多可以
观察byteCode的方法
:javap
(java自带):下面有使用JBE
可以直接修改JclassLib
idea插件之一 (下面以这个工具主要讲解)
未下载前 是idea自带的
:
将鼠标放在class文件或者Java文件中:
不好观察,所以下载插件:JclassLib
后
将鼠标放在class文件或者Java文件中:
classfile构成
看博文开头的xmind文件。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]; }
3、Class文件解读
class就是二进制字节流,那怎么解释呢,看由谁来解释,这里是
由java虚拟机 JVM 来解释的
,为了方便下面以16进制进行查看二进制字节流,十六进制也是 jvm 的规范进行解读。每个十六进制 对应 jvm 制定规范的 含义和指令。比如下面的十六进制 CAFE BABE 对应就是magic number ,含义就是class文件的开头,
提前剧透:常量池是class文件里最复杂的部分
下图就是上面2.2 第一个图片,编译后的class文件,对其进行解读。
== 讲解上图:==
一个小格是一个字节。对十六进制来讲,一个十六进制就是4位。两个十六进制就是一个字节 即8位。(如下图可看出,第一个小格是CA,这是16进制,所以这是一个字节,即8位。)(1字节可以包含两个16进制数
)
CAFE BABE
这是java编译后class文件
的抬头,比如其他png、GIF的都有自己独有的抬头。这就是magic number
魔法数。0000 0034
中,0000
为minor version
,小版本号,比如版本是52.0,则minor version就是.0
的概念0034
是major version
,大版本号,十进制是52,1.8编译完后就是52,1.9就是53。
1110
:constant_pool_count
,常量池里存在常量的个数
;两个字节,216=65536,,最多可放65535个常量
最多是 - 1。接着是常量的表
constant_pool
:这里就是存常量池的地方,这里面放的是类的一些信息,比如类名、方法、参数等等。如xmid文档,截图部分如下图。其长度为
constant_pool_count - 1
的表;这里是10,十六进制 就是15,则 就是15-1=14个常量,为什么要减一,因为常量池数组是从1开始的(平时数组是从0开始)因为最前面保留了一个0,将来可能有一些引用指向会表示不指向任意常量的任何一项,就可以用0来代表。access flags
:访问标识,比如public、private、protect、final等。后面的依次进行 翻译,这里就略过,
每个十六进制数
都对应jvm的设置好的含义或者是指令
。可以主要看博文最开始的 xmind文件以及jvm class 格式 官网。
a、javap 翻译class 文件 (java自带)
这样看不是很清楚,可以通过工具来很清晰的查看,就是java自带的javap
:会把class文件中的内容帮我们翻译好。(从2.2中可以看到)javap T0100_ByteCode01.class
:显示内容较少,如下,(可通过javap查看参数)javap -v T0100_ByteCode01.class
:下图中可以看到帮我们翻译出来的 minor version、major version、flags 等名称。flags: (0x0021) ACC_PUBLIC, ACC_SUPER
:后面的 ACC_SUPER 就是:该标志必须为真,JDK1.0.2之后编译出来的内容必须为真,指明invokespectial指令使用新语义
b、jclasslib 翻译class文件(idea插件)
使用 jclasslib 打开 class 文件,下面并进行分析和说明
i、一般信息
这里包含了class 文件结构的大多数基础信息,当然最重要的还是常量池。
- 本类索引,就是 this class :
cp_info #7 <com/mashibing/jvm/c1_bytecode/T0100_ByteCode01>:
后面的<com/mashibing/jvm/c1_bytecode/T0100_ByteCode01>
就是本类的名称,cp_info #7
就是在常量池的7号存的。绿色可以点击,点击过去就是 常量池的7号位置。 - 父类索引,就是 super class:
cp_info #2 <java/lang/Object>
:后面的还是父类名称,前面就是父类存储在常量池的2号位置。 - 接口计数 即 interfaces count。
- 字段计数 即 fields count。
- 方法计数 即 methods count。
- 属性计数 即 attributes count。
ii、常量池:
- 常量类型有很多种,如下图所示1,3,4,5,6到18,但是没有2,标记:常量池的每一种类型前边都有一个一个字节的标记,用的最多的是第一个:CONSTANT_Utf8_info ,代表 utf8的字符串。 (
下图的思维导图可从文章开头的前言中找到并下载
) jclasslib 打开的常量池 1号位置
是Methodref_info
文件:存的是方法引用信息,从思维导图中可以看出存的如下图所示,包括三个 1是标记10,2是index2个字节指向其他常量池 ,3是index2个字节指向其他常量池。1号位置 methodref_ref
中的 类名cp_info #2
:意思是指向类的名字在2号位置,这里又存在了4号位置。- 回过头来再看
1号位置 methodref_ref
中的 描述cp_info #3
:指向的是3号位置的name和type。可以看出<init>
: 是构造函数<()v>
:()
是指 没有参数,V
是指返回类型为void类型。
iii、接口、字段
因为2.1测试小程序很简单,没有接口和字段,所以这里为空
在一般信息中,也可以看出个数,如下
iv、方法*****
测试小程序虽然很简单,啥都没有
但是生成一个默认的 构造函数,调用的父类也就是java.lang.Obeject的构造方法
。
这里也有最重要的code 环节,因为函数里会有code的哈,这里会有大量的指令集(JVM大约定义256个左右)
<init>
就是默认的构造函数,从指令的右边可以看出 调用的是父类的默认的构造方法。
code
就是重要的代码部分,右边的上面也有指向常量池的类名称等信息,从这里也可以看出,类方法信息也都存在了常量池中。JVM 13 版本 的第七章,是十六进制码 与 指令的 对照。即 十六进制码 代表了什么意思,方法的具体实现翻译成class文件都变成了第七章(下图)的 十六进制(本质二进制)和指令(十六进制的映射),也就是
java的汇编语言
。当前在字节码中,有三个 指令。
指令的翻译可以从官网中查找对应的意思。第一个是
aload_0
:通过上面文档可以找到
(也可以通过鼠标在 jclasslib 中点进去如下图),aload_0
对应的是0X2a
这个十六进制,可以在class文件的十六进制文本中找到2a
这个位置,这个位置对应的就是aload_0
指令,那么 这个指令的作用是干嘛的呢。在文档中检索找到此位置,这个汇编指令代表的是:把本地变量表中的第0项(只要不是静态方法就是 this)放到栈里,然后执行第二条指令
invokespecial
。invokespecial
:可以看到右面的#1
可以点进去进行跟踪。在文档找到描述,如下图,可以看到对应的16进制是0Xb7
。加载到栈中,然后是第三条指令,也就是最后一条指令。format中下面两个indexbyte1和indexbyte2 是指令的两个参数 。return
返回指令,对应b1
。构造函数
<init>
的 三个指令 体现在十六进制的 class文件中如下所示。
v、附加属性 attributes
这里默认的就是java的类名称
c、小总结
上面主要讲解了class文件的十六进制由JVM解释后的内容,通过JVM文档进行对应解释。
除了常量池之外的,存的内容都是常量池中的地址。
由常量池存放除常量池之外的其他内容的引用地址。
三、循序渐进增加内容,查看class文件
1、实现 interface 接口
a、测试小程序
package com.mashibing.jvm.c1_bytecode;
import java.io.Serializable;
public class T0101_ByteCode_With_Interfaces implements Cloneable, Serializable {
}
b、编译后class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.mashibing.jvm.c1_bytecode;
import java.io.Serializable;
public class T0101_ByteCode_With_Interfaces implements Cloneable, Serializable {
public T0101_ByteCode_With_Interfaces() {
}
}
c、jclasslib 查看
多了两个实现的接口,可以看到存在了常量池。
2、添加 函数
a、测试小程序
package com.mashibing.jvm.c1_bytecode;
public class T0102_ByteCode02 {
void m() {
int i=0;
int j = i++;
}
}
b、编译后class文件
package com.mashibing.jvm.c1_bytecode;
public class T0102_ByteCode02 {
public T0102_ByteCode02() {
}
void m() {
int i = 0;
++i;
}
}
c、jclasslib 查看
可以看到 自定义方法和构造函数也都放到了常量池中。
- 增加了一个自定义方法。和默认的构造函数。
- 自定义函数对应的指令。
3、增加类属性
a、测试小程序
package com.mashibing.jvm.c1_bytecode;
public class T0103_ByteCode03 {
int i = 0;
String s = "Hello ByteCode!";
}
b、编译后的class文件
package com.mashibing.jvm.c1_bytecode;
public class T0103_ByteCode03 {
int i = 0;
String s = "Hello ByteCode!";
public T0103_ByteCode03() {
}
}
c、jclasslib 查看
类属性,即类成员变量,也存放在了常量池中。
4、增加带参构造函数和类属性
a、测试小程序
package com.mashibing.jvm.c1_bytecode;
public class T0104_ByteCode04 {
int i = 0;
String s = "Hello ByteCode!";
public T0104_ByteCode04(int i, String s) {
this.i = i;
this.s = s;
}
}
b、编译后的class文件
package com.mashibing.jvm.c1_bytecode;
public class T0104_ByteCode04 {
int i = 0;
String s = "Hello ByteCode!";
public T0104_ByteCode04(int i, String s) {
this.i = i;
this.s = s;
}
}
c、jclasslib 查看
红框中是函数参数类型和返回值类型。
5、增加构造函数、类方法、类属性
a、测试小程序
package com.mashibing.jvm.c1_bytecode;
public class T0104_ByteCode05 {
int i = 0;
String s = "Hello ByteCode!";
public T0104_ByteCode05(int i, String s) {
this.i = i;
this.s = s;
}
public void m() {}
}
b、编译后的class文件
package com.mashibing.jvm.c1_bytecode;
public class T0104_ByteCode05 {
int i = 0;
String s = "Hello ByteCode!";
public T0104_ByteCode05(int i, String s) {
this.i = i;
this.s = s;
}
public void m() {
}
}
c、jclasslib 查看
四、如何找到 JVM class 格式 官网文档
1、直接进入网址即可
这是java 19版本的 JVM
https://docs.oracle.com/javase/specs/jvms/se19/html/index.html
2、从oracle 官网开始找
- 进入oracle 官网:https://www.oracle.com/,通过product 找到java 所在地进来。
- 进入到了java页面,然后点击右上角的
download
- 然后进入到了下载页面,也就是默认是最新的java版本,目前是19版本,如下所示
- 拉到下面,找到
document download
中 下面的read me
,点进去 - 然后找到如图的
jdk document
中的红框中的第一个,(这两个都有用)。 - 这就是jdk 文档了,也可以从上面搜索任意一个版本的java版本。
- 找到 下面的
specifications
中的language and VM
这就是我们此行的目的地了 - 这里就是各个版本的 jdk 文档了,同时还有每个版本新增的亮点,如下图所示,点击下面的 HTML。
- 进入到文档(如下图所示),然后找到第四章(如下图所示),这就是 class 文件 格式的 内容了。
3、总结
从上面的最后一张截图所在的网址里,就可以学习 class 文件 格式的内容,也就是博文最开始总结的 xmind 的内容。
下图中就是class 文件 中 最重要的 常量池中的 tag 的标识。