文章目录
-
- 简介
- 文件结构
-
-
- magic
- minor_version 和 major_version
- constant_pool_count 和 constant_pool[constant_pool_count-1]
- access_flags
- this_class
- super_class
- intertaces_count 和 interfaces[intertaces_count]
- fields_count 和 fields[fields_count]
- methods_count 和 methods[methods_count]
- attributes_count 和 attributes[attributes_count]
-
- 实战
- 使用ASM修改Class文件
简介
C或者C++传统语言写的程序通常首先被编译,然后被连接成单独的、专门支持特定硬件平台和操作系统的二进制文件,因此一个平台上的二进制可执行文件往往不能在其他平台上工作,而Java 程序是先被编译成Class文件,然后通过Classloader装载进入JVM运行时环境的方法区中,直到被字节码执行引擎执行。
写完程序点击运行,编译器会自动将代码编译成class文件然后交给JVM去执行,但是JVM只认Class文件,毫不关心这个Class是如何生成的,只要符合JVM规范中定义的Class文件的结构即可,所以其他语言也可以通过JVM来执行,这就是JVM的语言无关性。
通常我们会认为只有与Java类似的Kotlin、Groovy、Jython等语言能直接编译成Class文件
但是时至今日,GraalVM的出现已经可以让更多的语言来享受JVM的托管环境,并且能支持的语言也越来越多。
文件结构
Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,如果遇到需要占用8个字节以上空间的数据项时,会按照高位在前的方式分割成若干个8个字节进行存储。高位在前就是Big-Endian,即按高位字节在地址最低位,最低字节在地址最高位来存储数据。
JVM规范文档中严格规定了Class文件结构,只有满足规范的Class二进制流文件才会被装载到方法区,否则在ClassLoader连接的验证阶段就会被剔除。
先看左边一列
- u1、u2、u4、u8
这些是无符号数,u1、 u2、 u4、 u8分别代表1个字节、 2个字节、 4个字节和8个字节的无符号数 - _info结尾
这些是表,表用于描述有层次关系的复合结构的数据, 整个Class文件本质上也可以视作是一张表
再看右边一列
magic
魔数:很多文件格式标准中都有使用魔数来进行身份识别的习惯, 譬如图片格式, 如GIF或者JPEG等在文件头中都存有魔数,Class文件也不例外,Class文件的魔数是0xCAFEBABE,不能修改,它用来确定这个文件是否为一个能被虚拟机接受的Class文件。
minor_version 和 major_version
minor_version 是副版本号:Java 2出现前被短暂使用过,后来一直被弃用(值固定为0),直到JDK12出现,由于JDK提供的功能集已经非常庞大, 有一些复杂的新特性需要以“公测”的形式放出, 所以设计者重新启用了副版本号, 将它用于标识“技术预览版”功能特性的支持。 如果Class文件中使用了该版本JDK尚未列入正式特性清单中的预览功能, 则必须把次版本号标识为65535, 以便Java虚拟机在加载类文件时能够区分出来。
major_version是主版本号:JDK版本是向下兼容的,它不能执行高于自身版本的Class文件
constant_pool_count 和 constant_pool[constant_pool_count-1]
constant_pool_count表示常量池里面常量个数,它的值等于常量池中成员数加1
constant_pool[constant_pool_count-1]是常量池,它包含Class文件结构及其子结构中所有字符串常量、类名、接口名、字段名和其他常量
access_flags
访问标志:是一种由标志所构成的掩码,用于表示某个类或者接口的访问权限及属性
this_class
类索引:它的值是常量池中某项的索引
super_class
父类索引,要么是0,要么也是常量池中某项的索引
intertaces_count 和 interfaces[intertaces_count]
intertaces_count是当前类或接口的直接超接口数量
interfaces[intertaces_count]接口表
fields_count 和 fields[fields_count]
fields_count 表示当前class文件fields表的成员个数
methods_count 和 methods[methods_count]
methods_count表示当前class文件methods表的成员个数
attributes_count 和 attributes[attributes_count]
attributes_count 表示当前class文件attributes表的成员个数
了解Class文件具体结构可以看官方文档,或者查看8.0中文文档 提取码:v1fz
实战
首先写一个类
public class Hello{
private static String message = "Hello World!";
static{
System.out.println("class has been loaded");
}
private int add(int a, int b){
return a + b;
}
public static void main(String[] args){
System.out.println("Dean say " + message);
}
}
通过javac -g:vars Hello.java 编译成class文件,然后使用javap -verbose Hello,就可以简单查看class文件翻译后的样子
Classfile /C:/Users/LWH/Desktop/Hello.class
Last modified 2021-3-26; size 839 bytes
MD5 checksum c9a28db4d04a7b597d25b698820b4bbf
public class Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#32 // java/lang/Object."<init>":()V
#2 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #35 // java/lang/StringBuilder
#4 = Methodref #3.#32 // java/lang/StringBuilder."<init>":()V
#5 = String #36 // Dean say
#6 = Methodref #3.#37 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = Fieldref #12.#38 // Hello.message:Ljava/lang/String;
#8 = Methodref #3.#39 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = String #42 // Hello World!
#11 = String #43 // class has been loaded
#12 = Class #44 // Hello
#13 = Class #45 // java/lang/Object
#14 = Utf8 message
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LocalVariableTable
#20 = Utf8 this
#21 = Utf8 LHello;
#22 = Utf8 add
#23 = Utf8 (II)I
#24 = Utf8 a
#25 = Utf8 I
#26 = Utf8 b
#27 = Utf8 main
#28 = Utf8 ([Ljava/lang/String;)V
#29 = Utf8 args
#30 = Utf8 [Ljava/lang/String;
#31 = Utf8 <clinit>
#32 = NameAndType #16:#17 // "<init>":()V
#33 = Class #46 // java/lang/System
#34 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#35 = Utf8 java/lang/StringBuilder
#36 = Utf8 Dean say
#37 = NameAndType #49:#50 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = NameAndType #14:#15 // message:Ljava/lang/String;
#39 = NameAndType #51:#52 // toString:()Ljava/lang/String;
#40 = Class #53 // java/io/PrintStream
#41 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#42 = Utf8 Hello World!
#43 = Utf8 class has been loaded
#44 = Utf8 Hello
#45 = Utf8 java/lang/Object
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 append
#50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = Utf8 toString
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V
{
public Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LHello;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #2