JVM基础之字节码文件分析

332 阅读6分钟

简介

研究jvm肯定要研究字节码文件,本篇文章对内容会对class文件有一个介绍

  • 了解一下class文件里这些“天书”到底是啥
  • 介绍jclasslib工具的使用
  • i++,++i的本质区别

贴士:本篇文章是介绍class文件让大家class文件,和java方法是怎么执行的,有一个初步的认识,并不会逐个字节来介绍。想要了解每个字节是什么意思的还是要通读《java虚拟机规范》

字节码文件是什么

字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。

总结: java文件给人看,class文件给jvm看。

我们都知道class文件是由java文件编译后得到。jvm加载class文件而不是java文件,那就是说,jvm只认class文件,而class文件是怎么来的并不在乎。

image.png

除java外能够编译成class文件的语言有:kotlin ,Scala,Groovy....

image.png

class文件解析

前期准备

  1. 一个简单的java
public class Test {

    public static void main(String[] args)  {

    }
}

2.能看十六进制的文本编辑器(比如sublime看个人习惯)

3.把刚刚写好的java文件编译成class并用文本编辑器打开查看,类似于这样

image.png

4.一本java虚拟机规范

image.png

class文件结构

image.png

u1:表示1个字节无符号整数,u2表示2个字节无符号整数,u4,u8以此类推

魔数

image.png

看第一个“magic”,u4是4个字节无符号整数(两个十六进制数的大小是一个字节),也就是说class文件的前8个数组成了classfile的第一个元素“magic”,我们来看一眼

image.png

magic: CAFEBABE(咖啡贝贝~程序员的浪漫)

版本号

image.png

同样的方法往下数4个十六进制数就是副版本号(minor_version),再往后数4个就是主版本号(major_version),我们看一眼。

image.png

副版本:00 00

主版本:00 34 (十进制52-代表java 1.8)

常量池

常量池:顾名思义,放常量的池子。

image.png

注意这里的常量,不是java里的常量

public static String XXX = "XXX";(跟这个常量是两回事注意区分)

(下面开始高能)

常量池数量

image.png

image.png

常量池数量:00 14(十进制20),实际常量池里的常量是constant_pool_count - 1 = 19个

再往后数19个元素,都是常量池里的元素

常量池中项的结构

常量池中每一项都有固定的结构

image.png

这个结构长下面的样子,tag表示这一项是什么类型

image.png

一共有这些类型,如下表

image.png

信息量有点大,这里有点复杂,继续坚持往下看,这段完了会画图解释,看完就清晰了

刚刚我们看了,tag占一个字节

往后再数一个字节是 0A对应十进制11

image.png

说明该项是CONSTANT_InterfaceMethodref(接口方法)

image.png

CONSTANT_InterfaceMethodref的结构为

image.png

该结构一共5个字节,我们截取一下

image.png

0A 00 03 00 11

class_index:00 03,表示常量池第三个项是类信息

name_and_type_index: 00 11(十进制 17),表示常量池中第17个元素表示该项的名字和描述符

我们先看常量池中第二个项

07:表示的是Constant_class_info,

image.png

image.png

该项由三个字节组成 07 00 12 表示常量池中18个元素表示类名,

tag:07 name_index:12(十进制的18)

image.png

再看第三个项 07 00 13 同样是class_info类型,第19个表示类名

tag:07 name_index:13(十进制的19)

image.png

直接看一眼第18项

0E,表示后面16个字节是一个字符串,截出来是这样

image.png

对照ascii表反序列出来结构就是 com/haozi/Test

再看一眼19项

image.png

结果是java/lang/Object

(是不是有点绕,画图来解释下)

未命名文件 (1).jpg

看到这里大家是不是对class文件,如何存储信息的有一些感觉了(其实蛮简单的,不断往下找,先看tag是什么,去书里找tag对应的类型,读出这个tag的信息)。剩余的内容我们就不助益阅读了,还是借助工具吧

jclasslib

idea的插件,安装就不用多说了

image.png

安装完成后点击classs文件,view->show ByteCoode With Jclasslib

image.png

借助这个工具再来研究class文件就方便多了.

image.png

class文件如何存储方法的信息

image.png

借助jclassLib工具

我们看一下方法信息在class文件中是如何存储

image.png

init:构造方法

main:主方法

如果有其他方法也会展示在这

方法名,描述符

方法名,描述符存储很简单指向常量池中某一项(如这里是11)

image.png

image.png

描述符

image.png

0009:表示的是方法描述符,什么意思呢?

image.png

public 是0x0001 (0b0001)

static是0x0008 (0b1000)

public static 就是他俩取“或” 结果为1001(0x9)

方法中的代码

Code是方法的其中一个属性

image.png

image.png

image.png

image.png

Code属性

Code属性格式如下

image.png

不在这介绍这里的所有细节了,只挑几个重点的说一下.注意一点,code里还包含attribute_info。

借助jclasslib看一下Code里的内容。

贴士: 本地变量表与操作数栈在下一篇文章还会讲到(与本篇不同的是下一篇侧重从内存的角度介绍)

本地变量表

准备java文件

public static void main(String[] args) {
    int o = 0;
}

main函数里定义一个变量 o = 0

借助jclasslib看一下本地变量表

image.png

发现这里有两个变量,一个是方法参数中的args,一个是我们自己定义的o。

image.png

操作码

image.png

return 不多解释了

iconst_0,istore_1是什么意思呢?

其实这里还有一个本地方法栈的概念。

iconst_0:向本地方法栈里,压 0;

image.png

istore_1:表示弹栈,并给本地变量里索引为1的变量赋值

image.png

i++ ++i

看了前面有些许枯燥的理论后我们实战研究下 ”++“ 在前跟在后到底有什么区别。

准备java代码

public static void main(String[] args) {
    int o = 0;
    int i = ++o;
}

看本地变量表

image.png

此时有三个变量,o,i索引分别是 1,2

image.png

看操作码

iinc by:给本地变量加上一个常量(在这里是给 o加上了1)

Increment local variable by constant

iload_1:拿本地变量的值压栈,这里是把o压入了本地方法栈

Load int from local variable

istore_2:刚才已经说过了,这里说本地方法栈里的1,弹栈赋值给i

所以整个执行流程是

换成o++

再看一眼操作码

image.png

很清楚发现,先执行了iload_1再执行了iinc,然后执行istore_2,此时的栈顶元素是0,所以赋值给i的也是0

看一下程序运行结果

image.png

本篇文章到此内容,就结束了,拜拜~