最近在研究Java的反射和动态代理,发现使用这两个Java神器须要了解.class文件的字节码。因而翻阅了相关资料,在这篇博客中进行一番整理,也做为本身学习的记录。java
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.classjvm
要想能读懂class文件这种生肉干货,首先要理解.class文件中的一些基本概念工具
无符号数是一种基本的数据类型,一般用u1,u2,u4,u8表明一、二、四、8个字节的无符号数。
无符号数是用来描述数字、索引引用、数量值或者UTF-8编码的字符串值,能够称做是.class文件的基本组成单位学习
表是由多个无符号数或其余表构成的复合数据类型,整个.class文件的本质就是一个表。(表大都习惯性的以_info结尾)this
不管是无符号数,仍是表,当须要描述同一类型但数量不定的多数据的时候,常常会使用一个位置的容量计数器加若干个连续的数据项的形式。编码
每一个.class文件的头四个字节被称为“魔数”,其做用是肯定该.class文件是否为一个能被HOTSPOT虚拟机接收的.class文件操作系统
魔数后面的四个字节是版本号,第五和第六个字节是“次版本号”,第七和第八个字节是“主版本号”。
Java的版本号是从45开始的,自jdk1.1以后的每一个jdk大版本发布的主版本号都向上+1,而且高版本的jdk能向下兼容之前版本的.class文件。(注意:hex viewer下,.class文件中的数字都是16进制数)
在主版本号后面的是常量池入口,常量池是.class文件结构中与其余项目关联最多的数据类型,也是占用.class文件空间最大的数据项目之一。
因为常量池中的常量的数量是不固定的,因此常量池的入口须要放置一项u2类型的数据,表明常量池容量计数。这个容量计数是从1开始的(有别于传统的程序员计数法则)。
上图中的.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类型的常量表示。
Java程序方法体内的代码通过javac编译处理以后,最终编程字节码指令存储在Code属性内。这以后就涉及到了字节码执行引擎的问题,以后会在其余的博客中进行讲解,敬请期待。
在属性表集合以后就是Code属性,具体对应的类型详见【查阅表格】
对于.class文件的解析工做,jdk为咱们提供了类解析工具javap。javap生成的.class文件解析比较直观,容易理解,算是半生肉。结合上文讲述的各个概念,应该不难理解。
具体使用方法是在cmd中输入:
javap -verbose 类名
输出结果大体是这样:(以ClassTest.class为例)
常量池类型表
全部结构类型
访问标志
字段表结构
字段表访问标志
==各标志的含义和其后半段的内容一致,表示字段的修饰符==
描述符标志字符含义
==对于数组类型,每一位都使用一个前置的“[”来描述。好比一个java.lang.String[][] 被记录为 [[Ljava/lang/String
一个int[]被记录为[I==
方法表结构
方法访问标志
Code 属性
2020年01月14日 17时03分40秒
打赏一下: