拆class文件(一)

准备

1.新建一个TestClass.java文件,编译(使用开发工具ide或java命令工具javac)拿到TestClass.class文件。示例java代码如下

public class TestClass {
	private int m;
	
	public int inc() {
		return m+1;
	}
	
}

2.下载一个16进制编译器 WinHex 或 010 Editor,安装并打开TestClass.class文件。

class文件组成

class文件格式

class字节码解释

用编译器打开class文件后,可以看到字节码 和 帮我们转译后的内容(同时我们也可以通过 javap -verbose TestClass 分析字节码 )。后面结合图文简单介绍class文件的组成。

图一:Class字节码

图二:TestClass结构

  • magic:图二第一行magic,它位于所有class文件的开头占4个字节长度(由无符号类型u4决定),取四个字节长度,对应 图一 0xCAFEBABE,这个值在所有class文件中均一样,它的作用:标识文件为合法class文件,能被java虚拟机识别。
  • minor_Version: 副版本号。类型u2, 0x0000 十进制为0。
  • major_version:主版本号,class文件的支持的最大版本,如果大于这个版本,class将无法执行。无符号类型u2, 0x0034,十进制为52 。
  • constant_pool_cout:常量池内常量的数量。u2两个字节长度取值 0x0016, 十进制为22,所有总共有22个常量,但从图二中看常量只有21个,其中隐藏的一个目的在于满足后面某些指向常量池的索引在特定情况下需要表达“不引用任何一个常量池项目”的含义。

  • struct cp_info constant_pool[*] :常量池中的常量,表类型,详情后面解释
  • access_flags: 访问标志,这里用于识别一些类或者接口层次的访问信息(是接口还是类,是否是public,有没有abstract等等),具体后面解释
  • this_class:当前类的全名称,它指向常量池中常量的索引,本例子为“ysh/TestClass”。u2两个字节长度取值0x0001,引用关系 Value=1 ——>struct cp_info constant_pool[0] ——> struct cp_info constant_pool[1] ——> “ysh/TestClass” (常量池中会解释引用关系)。
  • super_class:父类的全名称 “java/lang/Object”,同样指向常量池中常量的索引。u2两个字节长度,0x0003,引用关系Value=3 ——>struct cp_info constant_pool[2] ——> struct cp_info constant_pool[3] ——>"java/lang/Object"。java默认Object为所有类父类(除Object类)
  • interfaces_count: 接口个数 ,u2两个字节长度0x0000等于0个接口。
  • fields_count:字段表中字段的数量,本例中长度u2个 ,0x0001 等于1个变量。

  • field_info:字段表,描述成员变量,详情后面解释。
  • methods_count:方法表中方法数,u2取两个字节长度0x0002十进制为2。
  • method_info :方法表,描述方法的一系列信息,详情后面解释。
  • attributes_count: 属性表内的属性个数。u2,0x0001等于1个。
  • attribute_info :属性表,详情后面解释。

访问标志 access_flags

类的访问标志,用于识别一些类或者接口层次的访问信息,包括:这个class是类还是接口;是否是public类型;是否定义为abstract类型;如果是类的话,是否被申明为final等。本文例子字节对应如下:

acess_flags值为0x0021。通过查询访问标志表(JDK大于1.2这个已知)  0x0021 = 0x0001|0x0020。可以得到类由 public修饰。

访问标志不仅用来描述java 类或接口的信息,也在字段表和方法表中描述字段,方法,下面给出两个对应的访问标志表,后文分析会用到。

常量表  cp_info

常量池中不会记录字段,方法的引用地址,需要经过运行期转换后才能能到真正的内存入口地址。下面解释第1 ,2项常量。

常量池中每个常量都是一个表,表的第一个数据 tag 标识当前常量是什么类型,pool [ 0 ] tag =7, pool [ 1 ] tag = 1.对照常量类型表(省略),分别为CONSTANT_Class_info (类或接口的引用), CONSTANT_Utf8(UTF-8编码的字符串)。

pool[ 0 ] name_index= 2 ,他是一个索引值,这里他指向常量池的第二项常量。第二项常量 length说明这个utf-8缩略编码的字符串长度。

utf-8缩略编码:‘\u0001’—‘\u007f’ 之间的字符一个字节表示;‘\u0080’—‘\u07ff’之间的字符两个字节表示;‘\00800’—“\0ffff”之间的字符3个字节表示。

本例子中13个字节转换内容为 “ysh/TestClass”。后面的常量换算一样省略。

字段数据区 fileds_info

字段数据区,只有类或接口的全局变量没有局部变量。包括变量的详细信息:修饰符(public private ...);static ; final 等。

  • access_flags :描述字段的作用域,对照上面表6-9访问标志 0x0002 ,该字段由private修饰。
  • name_index : 描述变量的名称,值是常量池中常量的索引。 0x0005十进制为5 ——>struct cp_info constant_pool[5]=“m”。
  • descriptor :描述变量的类型,值是常量池中常量的索引。0x0006十进制为6——>struct cp_info constant_pool[6] = I 。其“ I ” 代表基础类型int,这个与JNI中标注类似,

还有其他标识 { B = byte, C = char, D = double, F = float, I = int, J = long , S = shot , Z = boolean, V = void, L = 对象类型如:(Ljava/lang/Object ) }。还有数组有 “ [ ”标识。 

  • methods_count :u2 , 方法数量0x0002 2个。
  • attribute_info :后面讲。

方法表 method_info 

  • access_flags: 对照方法访问标志,该方法由public修饰。
  • name_index: 方法名在常量池中的索引,value = 7查看常量池第七个常量,通过ASCII码转换过来是“<init>”。<init>:实例构造器,java规定未重写无参构造器时编译器自动添加的无参构造函数。
  • descriptor_index: 描述方法的返回值,同样指向常量池的引用。第8个常量转换为“()V”,()V表示返回值是void 传参为空()。
  • attribute_info: 属性表,这里方法内的代码全部放在属性表的”Code“属性里 (后面解释)。

第一个方法翻译为 public void <init> () ;第二个方法initc()I , public int initc()。

attribute_info 简单介绍

对于每个属性,他的名称(attribute_name_index)需从常量池中引用一个CONSTANT_Utf8_info的类型的常量来表示。属性值的自定义,自需要用长度u4属性区说明所占位数即可(attribute_length)。

Code属性

方法专属,Java方法中的代码经过Javac处理后,最终变为字节码存储在Code属性内。上面介绍方法表时第二个方法内的代码就存放在Code属性中。如下图:

  • u2 attribute_name_index:值为常量池常量的索引,转换为“Code”, 代表该属性的属性名称。
  • u4 attribute_length:属性值得长度,属性名称占6字节。需要减去6。
  • u2 max_stack:操作栈数的最大值。
  • u2 max_locals:局部变量表所需要的存储空间大小(单位Slot)。
  • u4 code_length 和code:存储java原程序编译后生称的字节码指令长度和一系列字节流(方法怎么执行都在这,详情看不懂了,指令太多。加载存储指令,运算指令,类型转换指令,对象创建与访问指令。。。。)。

其他属性:Exceptions,ConstantValue,SourceFile等共有21项(下次再写)。

猜你喜欢

转载自blog.csdn.net/m0_37312601/article/details/82221996