likes
comments
collection
share

干货!直击底层 —— Java Class字节码文件解析

作者站长头像
站长
· 阅读数 8

目录

前言

最近在研究Java的反射和动态代理,发现使用这两个Java神器需要了解.class文件的字节码。于是翻阅了相关资料,在这篇博客中进行一番整理,也作为自己学习的记录。

如何阅读class文件

Java的可移植性是基于.java文件编译后形成的唯一的字节码文件.class文件可以在不同操作系统上的jvm运行的机制。.class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在.class文件中,中间没有任何分隔符。

当程序员编译了.java文件后,在指定的路径下会生成一个.class文件,使用editplus可以直接以Hex viewer的格式打开.class文件

ClassTest.java

package com.classloader;
public class ClassTest {
    public static void main(String[] args) {
        System.out.println("Hello,World!");
    }
}


ClassTest.class

干货!直击底层 —— Java Class字节码文件解析

要想能读懂class文件这种生肉干货,首先要理解.class文件中的一些基本概念

基本概念

无符号数&表

无符号数是一种基本的数据类型,通常用u1,u2,u4,u8代表1、2、4、8个字节的无符号数。 无符号数是用来描述数字、索引引用、数量值或者UTF-8编码的字符串值,可以称作是.class文件的基本组成单位

干货!直击底层 —— Java Class字节码文件解析

表是由多个无符号数或其他表构成的复合数据类型,整个.class文件的本质就是一个表。(表大都习惯性的以_info结尾)

干货!直击底层 —— Java Class字节码文件解析

无论是无符号数,还是表,当需要描述同一类型但数量不定的多数据的时候,经常会使用一个位置的容量计数器加若干个连续的数据项的形式。

常量池

魔数(magic number) & 版本号

每个.class文件的头四个字节被称为“魔数”,其作用是确定该.class文件是否为一个能被HOTSPOT虚拟机接收的.class文件

魔数后面的四个字节是版本号,第五和第六个字节是“次版本号”,第七和第八个字节是“主版本号”。 Java的版本号是从45开始的,自jdk1.1之后的每个jdk大版本发布的主版本号都向上+1,并且高版本的jdk能向下兼容以前版本的.class文件。(注意:hex viewer下,.class文件中的数字都是16进制数)

干货!直击底层 —— Java Class字节码文件解析

常量池

在主版本号后面的是常量池入口,常量池是.class文件结构中与其他项目关联最多的数据类型,也是占用.class文件空间最大的数据项目之一。 由于常量池中的常量的数量是不固定的,所以常量池的入口需要放置一项u2类型的数据,代表常量池容量计数。这个容量计数是从1开始的(有别于传统的程序员计数法则)。

干货!直击底层 —— Java Class字节码文件解析

上图中的.class文件的常量池计数是34,由于从1开始,所以常量的个数是33(十六进制的22是十进制的34)。也就是说,从计数位之后的33个表,都是表示常量的。

常量池中主要存放两大类常量:字面量(literal)和字符引用(Symbolic References)。 字面量比较接近于Java语言层的常量概念,例如文本字符串、被声明为final的常量值等等。 字符引用包括三类变量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

刚刚提到过,常量池中的每一个常量都是一个表,一共11种结构各不相同的表,这些表都有一个共同的特点,开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种类型。(具体查看【查阅表格】)

总而言之,查看常量的方法就是:

1.第一个字节为tag 查看常量池类型表找到对应的类型 2.找到对应结构的表,找到tag之后属于常量的其他无符号数

访问标志

常量池结束后,紧接着的两个字节表示访问标志(access_flags)。这个标志用来识别一些类或接口层次的访问信息,包括:这个classs是实体类还是接口;是否定义为public;是否是抽象类;是否为final类等等。 具体访问标志的映射详见【查阅表格】

类引索&父类引索&接口引索集合

类引索(this_class)和父类引索(super_class)都是一个u2类型的数据,接口引索集合是一组u2类型的数据集合。 在访问标志之后,紧接着是类引索、父类引索,共占据4个字节。他们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型常量中的全限定名字符串。

字段表集合

在接口索引集合后的两个字节是fields_count类型,描述的是字段表集合内有多少个字段表。 字段表对应的是程序员在.java文件中的字段,字段表的各类型分别对应修饰词、引用名称等等 字段表的阅读方法和常量的阅读方法一致。 字段表结构以及字段表中各结构类型详见【查阅表格】

方法表集合

在字段表集合结束后,接下来的两个字节是method_count类型,描述的是方法表集合中有多少个方法表。 方法表对应的是程序员在.java文件中编写的方法,方法表的各类型分别对应修饰词、引用名称等等。 方法表结构以及方法表中各结构类型详见【查阅表格】

属性表集合

方法表集合之后四个字节,描述的是属性表集合。.class文件有很多属性,每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量表示。

Code属性

Java程序方法体内的代码经过javac编译处理之后,最终编程字节码指令存储在Code属性内。这之后就涉及到了字节码执行引擎的问题,之后会在其他的博客中进行讲解,敬请期待。 在属性表集合之后就是Code属性,具体对应的类型详见【查阅表格】

使用javap解析class文件

对于.class文件的解析工作,jdk为我们提供了类解析工具javap。javap生成的.class文件解析比较直观,容易理解,算是半生肉。结合上文讲述的各个概念,应该不难理解。 具体使用方法是在cmd中输入:

javap -verbose 类名

输出结果大致是这样:(以ClassTest.class为例)

干货!直击底层 —— Java Class字节码文件解析

干货!直击底层 —— Java Class字节码文件解析

查阅表格

常量池类型表

干货!直击底层 —— Java Class字节码文件解析

所有结构类型

干货!直击底层 —— Java Class字节码文件解析

访问标志

干货!直击底层 —— Java Class字节码文件解析

字段表结构

干货!直击底层 —— Java Class字节码文件解析

字段表访问标志

干货!直击底层 —— Java Class字节码文件解析 ==各标志的含义和其后半段的内容一致,表示字段的修饰符==

描述符标志字符含义

干货!直击底层 —— Java Class字节码文件解析

==对于数组类型,每一位都使用一个前置的“[”来描述。比如一个java.lang.String[][] 被记录为 [[Ljava/lang/String 一个int[]被记录为[I==

方法表结构

干货!直击底层 —— Java Class字节码文件解析

方法访问标志

干货!直击底层 —— Java Class字节码文件解析

Code 属性

干货!直击底层 —— Java Class字节码文件解析

2020年01月14日 17时03分40秒

打赏一下:

干货!直击底层 —— Java Class字节码文件解析