java 字节码格式 🔗
Class 文件格式图
javap 查看字节码文件 🔗
- magic, 0xCAFEBABE ,pdf png 文件内都有魔数
- version, 生成 class 文件时的 java 版本
- constant pool, javap -v Helloworld
- access flag, 2 字节 标识一个类为 final, abstract, public 之类的
- this class, 确定类的继承关系 指向常量池的索引
- super class, 确定类的继承关系
- interface, 确定类的继承关系
- field, 字段表
- 变长
- field_info 结构
- access_flags 字段访问标识
- name_index 字段名索引 指向常量池的字符串常量
- descriptor_index 字段类型描述符的索引 引用类型“L;” -> “Ljava/lang/String;” 指向常量池的字符串常量
- method 类中定义的方法存储在这里
- 变长
- method_info
- access_flags
- name_index
- decriptor_index 方法描述符 指向常量池类型为 constant_uft8_innfo 的字符串常量项,表述(参 1 的类型,参 2 的类型...) 返回值类型
- attribute 属性表
- attribute_info
- attribute_name_index 属性名索引 指向常量池的字符串常量
- attribute_length info 数组长度
- info
- ConstantValue 属性
- Code 属性 重要的部分 包含了方法的字节码
字节码指令 🔗
- switch-case 的两种字节码指令的实现
- tableswitch , case 间隔紧密时使用 o(1)
- lookupswitch , case 间隔稀疏时使用 o(lgn)
- try-catch 字节码分析 JVM 的异常处理是通过编译期间确定下来的异常表,在运行时利用异常表来实现的
- code 属性里有异常表 表项 4 元组:from, to, target, exception type
- 在 from,to 范围内的字节码发生指定的 exception type 时跳转到 target 字节码的位置
- finally 字节码分析
- 为什么 finally 一定执行: javac 生成字节码时在 try 和 catch 中所有退出之前加入 invokevirtual 调用 finally 里代码块
- finally 语句中有 return 语句时,javac 生成字节码时会将 try 和 catch 里的 return 语句的值临时放入局部变量表里,只返回 finally 里的 return 语句
- 对象相关的字节码
方法 ,类的构造方法,非静态变量的初始化,对象初始化代码块 - 创建对象需要三条指令: new, dup, invokespecial
类的静态初始化方法 , 类静态初始化块 静态变量初始化 - static {};
- new, getstatic, putstatic, invokestatic 触发
方法调用
-
5 条方法调用指令的联系和区别 (以 invoke 开头的指令)
- invokestatic 调用 static 静态方法
- 调用的方法在编译期间确定,静态绑定
- 不需要将对象加载到操作数栈上,仅参数入栈,执行 invokestatic 就行
- invokespecial 调用特殊的实例方法,构造器方法
- 调用的方法在编译期间确定,效率比 invokevirtual 高
- 实例构造器方法
, private 修饰的私有方法, super 关键词调用的父类方法
- invokevirtual 运行时根据对象的实际类型,执行具体子类的实现方法。 vtable
- 调用的方法在运行时才能根据对象的实际类型确定
- 需要将对象应用,参数入栈
- invokeinterface 调用接口方法 itable
- 调用的方法在运行时根据对象的类型确定目标方法
- invokeinterface 从 itbale 表找对应的方法
- invokedynamic 调用动态方法
- 前 4 条指令的分派规则固化在 jvm 中
- invokedynamic 是把如何查找目标方法的决定权给了具体的用户代码中
- invokestatic 调用 static 静态方法
-
JVM 方法分配机制与 vtable,itable 原理, 方法分派
- vtable, itable 是 java 实现多态的基础
- 对象的继承与多态
- 子类继承父类的 vtable。 一个没有方法的类的 vtable 也有 5 条来自 Object 类
- final, static, private 方法不能被继承和重写,所以不会出现在 vtable 中
- itable = offset table + method table 去支持 invokeinterface 指令
-
invokedynamic 指令,lambda 表达式中的作用
- java8 中出现的 lambda 表达式才第一次使用上这条指令
- 匿名内部类是在编译期间生成新的 class 文件来实现的
- asm 在运行时生成 class 对象
-
从字节码角度理解范型擦除
- 范型类型会被擦除为 Object 类型
- ArrayList
不允许,因为原始类型不能存储到 Object 类型上
-
synchronized 关键字的字节码原理
- 插入 monitorenter (获取栈顶对象的锁), 插入 monitorexit(释放锁,一定插入至少 2 个,原因是正常退出和异常退出均会插入)
- 一定会在 code 属性里生成异常处理的描述表,即在所有退出的地方插入 monitorexit 保证锁在任何退出的节点均被释放(try-catch-finaly monitorexit)
类加载 🔗
主要做三件事
-
通过类名从 class 文件中获取字节流
-
将字节流存储在方法区上的运行时数据结构
-
在堆上生成 Class 对象,作为这个类在方法区上的访问入口
-
java 基础类库提供 2 种动态加载类的方法
- Class 的静态方法 forName, 会执行该类的 clinit 初始化。
- ClassLoader 的 loadClass 方法,不会执行泪的初始化
生成字节码的框架 🔗
- javassit,cglib,asm
- 读和写字节码
- 一个 CtClass 对象 - 对应 Class 对象
- ClassPool 是由 CtClass 对象组成的 hashtable
一个对象在内存中的结构
对象头由 2 部分组成 - mark word(32 位或者 64 位): 25bit 哈希码,2bit 锁标志位 ; 二 类型指针(指向 Class 对象)
在对象上加锁的过程
-
复制对象头到执行栈中的锁记录中
-
修改对象头中的 mark word, 2 点修改:修改 mark word 的前 30bit 为存放锁记录的地址,修改锁标志位
-
如何自实现一个 Immutable 类
- class 定义为 final,避免继承
- 所有成员熟悉定义为 final
- 构造函数不要引用外部对象,构造时深度拷贝
- 其实在类加载是可以做任何字节码修改
-
判断一个类是否是 Immutable 的
-
Concurrent Modification(并发修改集合对象)
- 遍历集合类的同时,修改集合类的结构,产生 ConcurrentModificationException
-
java 里有 2 种 iterator 的实现方式: fail-safe , fail-fast(快速失败)
- fail fast
- fail fast iterator throw ConcurrentModifacationException
- iterator use original collection to travel over
- fail safe
- iterator required extra memory to clone the original collections
- iterator use the copy collection to traverse over
- allow modification a collection while iterating over it
- fail fast
-
扩容过程
- 经过 rehash 之后,元素的位置要么是在原位置,要么是在原位置再移动 2 次幂的位置
- HashMap 扩容时,size 增大一倍, newsize=oldsize*2
- ArrayList(动态数组)扩容时,扩大 0.5 倍,newsize=oldsize*1.5
- ArrayQueue, 当 size 小于 64 时,+2;size 大于 64 时,newsize=oldsize*1.5
java 程序的静态分析框架 soot, wala 🔗
4 中 IR
- Baf
- 在 bytecode 上进行的抽象,忽略字节码对于常量池和 type 的依赖
- Jimple
- 重要的 IR,三地址表达式 res = num1 op num2,一个表达式最多有三个引用地址
- 15 条指令
- SSA,Static Single-Assignment。 每个变量仅被赋值一次,保证每个被使用的变量都有唯一的定义
- Grimple
- Shimple
Soot 中的数据结构
- Scene 类, 环境上下文
- SootClass, 代表类
- SootMethod,类的方法
- SootField,类的属性
- Body,方法体
Soot 支持的分析:
- Call-graph 构建
- Points-to 分析
- Def/use 链
- 模板驱动的过程间数据流分析
- 模板驱动的跨过程数据流分析(与 heros 组合使用)
- 污点分析(与 FlowDroid 组合使用)
Body 类 (用来存放方法的代码)
- BafBody, JimpleBody, ShimpleBody, GrimpBody 四种不同 IR 所对应的 Body 类
- 3 中主要的 chain, Unints chain, Locals chain, Traps chain
Jimple 代码
locals 方法内的局部变量在方法的顶部 $开头的变量为原 java 代码中没有的变量
traps: 异常处理被表示为一个 tuple(元组,异常,开始,结束,处理器)
units: body 中包含的实际代码, 语句
- interface Unit, Jimple 使用 Stmt, Grimp 使用 Inst
Value 接口(代表数据): Locals, Constants, Expressions, ParameterRefs
Boxes(代表指针): ValueBox, UnitBox
AnalysisScope
- 用于指定用户静态代码分析的程序或库
- format: Classloader, Language, Type, Location
CallGraphBuilder
- makeCallGraph() -> CallGraph
- getPointerAnalysis() -> PointerAnalysis
软件分析技术(Software Analysis) 🔗
哥德尔不完备定理
莱斯定理
数据流分析
过程间分析,指针分析,控制流分析,抽象解释
约束求解,符号执行
数据流分析 🔗
活跃变量分析, 可达性分析, 可用表达式分析
https://xiongyingfei.github.io/SA/2020/main.htm
《编译原理》Aho
《Lecture notes on static analysis》Moller and Schwartzbach
https://cs.au.dk/~amoeller/spa/
南京大学《软件分析》课(B 站视频)
《Decision Procedures -- An Algorithmic Point of View》 Daniel Kroening and Ofer Strichman
github: CMinor-Verifier.
spring 🔗
@value 🔗
启动 jar 包时的命令行参数优先级最高,会覆盖 application.properties 里的参数。