目录
1.概述
2.字节码文件构成
2.1.魔数
2.2.版本号
2.3.常量池
2.4.访问标志
2.5.索引
2.6.字段表
2.7.方法表
3.字节码指令
3.1.概述
3.2.指令分类
3.2.1.加载存储指令
3.2.2.运算指令
3.2.3.其他指令
3.3.完整指令工作流程
4.字节码保护
1.概述
以往的编程语言是直接编译为计算机可识别并直接执行的机器码,但不同平台(机器)的指令集不同,编译出来的机器码不同,一个平台编译出来的机器码在另一个平台上可能无法正常运行。JAVA为了实现“write once run anywhere”的效果,先将JAVA代码编译为平台无关的.class文件(字节码文件),不论什么平台编译出来的字节码都是一样的,然后用适配不同平台的JVM来加载字节码文件,将字节码文件翻译给对应平台来执行。
字节码文件是个二进制文件,由JVM定义字节码文件的规范,任何满足这种规范的class文件都会被JVM加载运行。字节码规范规定字节码应该具有以下基本结构:
2.字节码文件构成
2.1.魔数
字节码的前4个字节是魔数,所有字节码文件的魔数是固定的,4个byte合起来是cafebabe,用来标识该文件是JAVA的class文件。
2.2.版本号
字节码中记录编译使用的JDK版本,字节码的第5、6位表示次版本号,第7、8位表示主版本号:
查表可以得,示例中的版本号为JDK12。
2.3.常量池
常量池存放两大类常量:
- 字面量,如文本字符串、fnal的常量值等
- 符号引用,类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
字节码中存放有常量池,使用java -verbose命令可以反编译得到常量池信息。
2.4.访问标志
常量池结束后的两个字节,描述了该Class是类还是接口,以及是否被public、abstart、final等修饰符修饰。
2.5.索引
字节码文件中的索引分为三类:
- 类索引
- 父类索引
- 接口索引集合
类索引:
访问标志后的两个字节,描述的是当前类的全限定名,这两个字节保存的值为常量池中的索引值,根据索引值就能在常量池中找到这个类的全限定名
父类索引:
当前类名后的两个字节描述父类的全限定名,同上,保存的也是常量池中的索引值。
接口索引集合:
父类名称后为两字节的接口计数器,描述了该类或父类实现的接口数量。紧接着的n个字节是所有接口名称的字符串常量的索引值。
2.6.字段表
用来记录成员变量,分为两部分:
- 计数器,记录成员变量的个数。
- 数据区,记录变量的具体内容。
2.7.方法表
记录方法,分为两部分:
- 计数器,记录方法的个数
- 数据区,记录方法的具体内容
属性列表中记录了方法的具体内容:
- Code,源代码对应的]VM指令操作码
- LineNumberTable,行号表,将Code区的操作码和源代码中的行号对应
3.字节码指令
3.1.概述
字节码指令在栈里面的每个栈帧中操作局部变量表进行对操作数栈的压栈出栈,从而完成方法中编写的一系列内容,变量表是“菜篮”;操作数栈是“砧板”。
3.2.指令分类
3.2.1.加载存储指令
用于变量对于操作数栈的压栈、出栈。
- 用于将数据在栈帧中的局部变量表和操作数栈之间来回传输
- 将一个局部变量加载到操作栈: iload、lload、fload、dload、aload等
- 将一个数值从操作数栈存储到局部变量表: istore、Istore、fstore、dstore、astore等
- 将一个常量加载到操作数栈: bipush、sipush、ldc、ldc_w、ldc2 w、aconst null、iconst m1等
3.2.2.运算指令
用于进行运算的指令。
- iadd、isub、imul、idiv等类型转换指令
- i2b、i2l、i2s等对象/数组创建与访问指令
- new、newarray、getfield等操作数栈管理指令
- pop、dup等
3.2.3.其他指令
- lfeq、goto等方法调用和返回指令
- invokevirtual、ireturn等异常处理指令
- athrow同步控制指令
- monitorenter、monitorexit
3.3.完整指令工作流程
以下面代码为例展示一个完整的指令工作流程:
public class Sum {
private int base = 10;
public long add(int toAdd){
this .base += toAdd;
return this.base;
}
}
反编译查看源码:
整体步骤如下:
- aload 0 将索引为0的局部变量压栈
- dup 复制栈顶值并压栈
- getfeld 取出栈顶对象引用,获取其成员变量并压栈
- iload_1 将索引为1 int型局部变量压栈
- iacd 取出栈顶两个int型值相加,结来压栈
- puteld 将栈顶两个元素取出 (值和对象引用) ,将值赋给该对象宇段
- i2l 将栈顶int型值取出转为long型压栈lreturn 将栈顶long型元素取出返回
4.字节码保护
防止字节码文件被反编译为.java文件,一般有两种手段:
- 字节码保护
- 字节码混淆
字节码保护:
对字节码进行加密使其不再遵循JVM的规范,JVM加载之前先解密。
字节码混淆:
被混淆代码依然遵循JVM制定的规范,变量命名和程序流程上进行等效替换,使得程序的可读性变差,使得代码难以被理解和重用,达到保护代码的效果。
常用的字节码混淆器:ProGuard
下载地址:https://www.guardsquare.com/en/products/proguard