java 小结

· 2571 words · 6 minute read

java 字节码格式 🔗

Class 文件格式图

image

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 是把如何查找目标方法的决定权给了具体的用户代码中
  • 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

一个对象在内存中的结构

image

对象头由 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
  • 扩容过程

    • 经过 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 里的参数。