Bytecode Manipulation Frameworks

ASM vs Javassist

很多开发者在选择字节码操作框架之初,都会有所疑惑,到底是选择 Javassist 呢?还是 ASM 呢?我们可能从以下几个方面来对比一下两者之间的差异,以及适用范围:

特性JavassistASM
包大小771 KB (3.27)265 KB (6.0 BETA)
性能劣于 ASM优于 Javassist
API 封装程度
功能完备程度完备完备
对开发者的要求基本了解 class 文件格式和 JVM 指令集需要精通 class 文件格式和 JVM 指令集
学习曲线平缓陡峭
文档及手册简单明了有些繁琐(Vistor 模式让初学者有点懵)

从上面的对比来看,我想各位读者已经有所选择,对我个人而言,如果是初学者,建议选择 Javassist,毕竟上手快,学习起来比较容易,如果是对性能、包体积方面要求比较高,建议选择 ASM

所以,为了照顾到尽可能多的开发者,Booster 对两者都做了支持,看过 Booster 的源码的同学可能会问,为什么 Booster 的大部分实现都是基于 ASM 呢?究竟有哪些考量?

ASM & Javassist Benchmark Test

Booster 最初在选择字节码操作框架的时候,最主要的考量因素是性能,Booster 作为质量优化框架,不仅自身模块在性能上要求做到极致,也要让其他开发者基于 Booster 开发的功能在性能上也要表现卓越,所以,针对 JavassistASM 在字节码处理方面的性能作了 benchmark 测试,以下是通过处理 guava-28.2-jre.jar 来对比二者之间的性能:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 2, jvmArgs = ["-Xms2G", "-Xmx2G"])
@State(Scope.Benchmark)
open class JavassistVsAsmBenchmark {

    private lateinit var file: File

    @Setup
    fun setup() {
        this.file = File.createTempFile("guava-", "-28.2-jre.jar")
        javaClass.classLoader.getResourceAsStream("guava-28.2-jre.jar").use { input ->
            file.outputStream().use { output ->
                input!!.copyTo(output)
            }
        }
    }

    @Benchmark
    fun transformJarUsingAsm() {
        TransformHelper(this.file, AndroidSdk.getAndroidJar().parentFile)
                .transform(transformers = *arrayOf(AsmTransformer(AsmThreadTransformer())))
    }

    @Benchmark
    fun transformJarUsingJavassist() {
        TransformHelper(this.file, AndroidSdk.getAndroidJar().parentFile)
                .transform(transformers = *arrayOf(JavassistTransformer(JavassistThreadTransformer())))
    }

    @TearDown
    fun teardown() {
        this.file.delete()
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

Benchmark 测试代码:https://github.com/johnsonlee/booster-benchmark

Benchmark 测试结果如下:

BenchmarkModeCntScoreErrorUnits
JavassistVsAsmBenchmark.transformJarUsingAsmavgt10203.489± 52.174ms/op
JavassistVsAsmBenchmark.transformJarUsingJavassistavgt10277.695± 10.801ms/op

从上面的结果来看,ASM 平均耗时更低

Apache BCEL

除了 ASMJavassist 以外,Booster 同样支持使用其它的字节码框架,比如:Apache Commons BCELopen in new window,只不过,ASMJavassistBooster 默认提供了支持,如果要在项目中使用 Apache Commons BCELopen in new window —— 请参阅字节码操作 - 自定义 Transformer