简介
研究jvm肯定要研究字节码文件,本篇文章对内容会对class文件有一个介绍
- 了解一下class文件里这些“天书”到底是啥
- 介绍jclasslib工具的使用
- i++,++i的本质区别
贴士:本篇文章是介绍class文件让大家class文件,和java方法是怎么执行的,有一个初步的认识,并不会逐个字节来介绍。想要了解每个字节是什么意思的还是要通读《java虚拟机规范》
字节码文件是什么
字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。
总结: java文件给人看,class文件给jvm看。
我们都知道class文件是由java文件编译后得到。jvm加载class文件而不是java文件,那就是说,jvm只认class文件,而class文件是怎么来的并不在乎。
除java外能够编译成class文件的语言有:kotlin ,Scala,Groovy....
class文件解析
前期准备
- 一个简单的java
public class Test {
public static void main(String[] args) {
}
}
2.能看十六进制的文本编辑器(比如sublime看个人习惯)
3.把刚刚写好的java文件编译成class并用文本编辑器打开查看,类似于这样
4.一本java虚拟机规范
class文件结构
u1:表示1个字节无符号整数,u2表示2个字节无符号整数,u4,u8以此类推
魔数
看第一个“magic”,u4是4个字节无符号整数(两个十六进制数的大小是一个字节),也就是说class文件的前8个数组成了classfile的第一个元素“magic”,我们来看一眼
magic: CAFEBABE(咖啡贝贝~程序员的浪漫)
版本号
同样的方法往下数4个十六进制数就是副版本号(minor_version),再往后数4个就是主版本号(major_version),我们看一眼。
副版本:00 00
主版本:00 34 (十进制52-代表java 1.8)
常量池
常量池:顾名思义,放常量的池子。
注意这里的常量,不是java里的常量
public static String XXX = "XXX";(跟这个常量是两回事注意区分)
(下面开始高能)
常量池数量
常量池数量:00 14(十进制20),实际常量池里的常量是constant_pool_count - 1 = 19个
再往后数19个元素,都是常量池里的元素
常量池中项的结构
常量池中每一项都有固定的结构
这个结构长下面的样子,tag表示这一项是什么类型
一共有这些类型,如下表
信息量有点大,这里有点复杂,继续坚持往下看,这段完了会画图解释,看完就清晰了
刚刚我们看了,tag占一个字节
往后再数一个字节是 0A对应十进制11
说明该项是CONSTANT_InterfaceMethodref(接口方法)
CONSTANT_InterfaceMethodref的结构为
该结构一共5个字节,我们截取一下
0A 00 03 00 11
class_index:00 03,表示常量池第三个项是类信息
name_and_type_index: 00 11(十进制 17),表示常量池中第17个元素表示该项的名字和描述符
我们先看常量池中第二个项
07:表示的是Constant_class_info,
该项由三个字节组成 07 00 12 表示常量池中18个元素表示类名,
tag:07 name_index:12(十进制的18)
再看第三个项 07 00 13 同样是class_info类型,第19个表示类名
tag:07 name_index:13(十进制的19)
直接看一眼第18项
0E,表示后面16个字节是一个字符串,截出来是这样
对照ascii表反序列出来结构就是 com/haozi/Test
再看一眼19项
结果是java/lang/Object
(是不是有点绕,画图来解释下)
看到这里大家是不是对class文件,如何存储信息的有一些感觉了(其实蛮简单的,不断往下找,先看tag是什么,去书里找tag对应的类型,读出这个tag的信息)。剩余的内容我们就不助益阅读了,还是借助工具吧
jclasslib
idea的插件,安装就不用多说了
安装完成后点击classs文件,view->show ByteCoode With Jclasslib
借助这个工具再来研究class文件就方便多了.
class文件如何存储方法的信息
借助jclassLib工具
我们看一下方法信息在class文件中是如何存储
init:构造方法
main:主方法
如果有其他方法也会展示在这
方法名,描述符
方法名,描述符存储很简单指向常量池中某一项(如这里是11)
描述符
0009:表示的是方法描述符,什么意思呢?
public 是0x0001 (0b0001)
static是0x0008 (0b1000)
public static 就是他俩取“或” 结果为1001(0x9)
方法中的代码
Code是方法的其中一个属性
Code属性
Code属性格式如下
不在这介绍这里的所有细节了,只挑几个重点的说一下.注意一点,code里还包含attribute_info。
借助jclasslib看一下Code里的内容。
贴士: 本地变量表与操作数栈在下一篇文章还会讲到(与本篇不同的是下一篇侧重从内存的角度介绍)
本地变量表
准备java文件
public static void main(String[] args) {
int o = 0;
}
main函数里定义一个变量 o = 0
借助jclasslib看一下本地变量表
发现这里有两个变量,一个是方法参数中的args,一个是我们自己定义的o。
操作码
return 不多解释了
iconst_0,istore_1是什么意思呢?
其实这里还有一个本地方法栈的概念。
iconst_0:向本地方法栈里,压 0;
istore_1:表示弹栈,并给本地变量里索引为1的变量赋值
i++ ++i
看了前面有些许枯燥的理论后我们实战研究下 ”++“ 在前跟在后到底有什么区别。
准备java代码
public static void main(String[] args) {
int o = 0;
int i = ++o;
}
看本地变量表
此时有三个变量,o,i索引分别是 1,2
看操作码
iinc by:给本地变量加上一个常量(在这里是给 o加上了1)
Increment local variable by constant
iload_1:拿本地变量的值压栈,这里是把o压入了本地方法栈
Load
intfrom local variable
istore_2:刚才已经说过了,这里说本地方法栈里的1,弹栈赋值给i
所以整个执行流程是
换成o++
再看一眼操作码
很清楚发现,先执行了iload_1再执行了iinc,然后执行istore_2,此时的栈顶元素是0,所以赋值给i的也是0
看一下程序运行结果
本篇文章到此内容,就结束了,拜拜~