char... whose default value is the null code point ('u0000')
The floating-point types are:
float... whose default value is positive zero
double... whose default value is positive zero
2.4. Reference Types and Values
...The null reference initially has no run-time type, but may be cast to any type. The default value of a referencetype is null.
总结来说,在 Java 中的基本类型和引用类型的 Field 都会在 Class 被加载的同时赋予一个默认值,byte、short、int、long、float、double类型都会被赋为 0,char 类型会被赋为'u0000',引用类型会被赋为 null 。
我们将开头那段代码通过命令行java -p -v转化为字节码:
public com.bytedance.android.dexoptimizer.MyClass();Code:0: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: aload_05: iconst_06: putfield#2// Field aBoolean:Z9: returnstatic {};Code:0: iconst_01: putstatic#6// Field aBooleanStatic:Z4: returnprivate void boo();Code:0: aload_01: getfield#2// Field aBoolean:Z4: ifne157: getstatic#4// Field java/lang/System.out:Ljava/io/PrintStream;10: ldc#5// String in aBoolean false!12: invokevirtual #6// Method java/io/PrintStream.println:(Ljava/lang/String;)V15: aload_016: getfield#3// Field aBooleanStatic:Z19: ifne3022: getstatic#4// Field java/lang/System.out:Ljava/io/PrintStream;25: ldc#7// String in aBooleanStatic false!27: invokevirtual #6// Method java/io/PrintStream.println:(Ljava/lang/String;)V30: return
通过上述字节码发现,虽然 JVM 会在运行时将 aBoolean 赋值为 0,但是我们在字节码中仍然会再赋值一次 0 给到 aBoolean,aBooleanStatic 同理 。
public com.bytedance.android.dexoptimizer.MyClass();Code:0: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: aload_05: iconst_06: putfield#2// Field aBoolean:Z9: return
以上标红部分出现了重复赋值,去除了不影响运行时逻辑 。因此,我们考虑在 Class 字节码处理阶段,将这种冗余的字节码移除来获取包大小收益 。
优化思路理解了问题产生的原因后,就很容易得到对应的解决方案 。首先,能够被优化的 Field 赋值,需要满足这三个条件:
- Field 是属于其直接定义的 Class 的,而非在父类定义过的;
- Field 赋值是在 Class 的clinit、init方法中,这样做很大程度是为了降低复杂度(因为只在这两个方法中调用的 private 方法也是能做这样的优化,但分析这样的方法复杂度很高);
- Field 赋值是默认值,当出现多个赋值时,在非默认赋值后的赋值都无法被优化 。
Class MyClass {// 可以优化,直接定义的,且是默认值private boolean aBoolean = false;// 不可优化,因为赋值为非默认值private boolean bBoolean = true;// 可以优化,直接定义的,且是默认值private static boolean aBooleanStatic = false;static {// 可以优化,第一处出现,且是默认值aBooleanStatic = false;// 其他代码...// 可以优化,前面没有非默认值赋值,且是默认值aBooleanStatic = false;// 其他代码...// 不可优化,因为赋值为非默认值aBooleanStatic = true;// 其他代码...// 不可优化,因为之前出现了非默认值的赋值aBooleanStatic = false;}private void boo() {// 不可优化,因为函数为非clinit或initaBoolean = false;}}
具体实现上,我们的优化思路是这样的:- 遍历 Class 所有方法,找到<clinit>和<init>方法,从上往下进行字节码指令遍历
- 遍历这两种方法的所有字节码指令,找到所有的 putfield 指令,将 putfield 指令的目标 ClassName 和 FieldName 使用-连接,构建一个唯一的 Key,如果
- putfield 目标 Class 不是当前 Class,跳过
- putfield 前的 load 指令不为iconst_0,fconst_0,dconst_0,lconst_0,aconst_null,并将该 putfield 所关联的唯一的 Key 放入已经遍历过的 Key 的集合中
- putfield 前的 load 指令为iconst_0,fconst_0,dconst_0,lconst_0,aconst_null,且该 putfield 所关联的唯一的 Key 没有在遍历过的 Key 的集合出现过,则标记为可清除的字节码指令
- 遍历完成后,删除所有被标记为可清除的字节码指令
public com.bytedance.android.dexoptimizer.MyClass();// 1. 判断是<init>方法,进入优化逻辑Code: // 2. 从上往下进行代码遍历0: aload_01: invokespecial #Method java/lang/Object."<init>":()V4: aload_05: iconst_06: putfield#Field MyClass.aBoolean:Z. // 3.发现是该Class的域,且赋值为iconst_0,标记往上三个指令可以删除7: aload_08: iconst_19: putfield#Field MyClass.aBoolean:Z// 4.发现是该Class的域,且赋值不为iconst_0,则在遍历过的Key的集合中添加MyClass-aBoolean,继续往下10: aload_011: iconst_012: putfield#Field MyClass.aBoolean:Z// 5.发现是该Class的域,但在遍历过的Key的集合中发现存在MyClass-aBoolean,继续往下15: return
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Android WebRTC 对 AudioRecord 的使用
- TikTok/国际版抖音/海外版抖音2万字干货教程,新手必看
- 作为Android开发,这个知识点一定要知道,官方也改了 2 次
- Android logcat日志封装
- 抖音防烧屏脚本 – Tasker 脚本分享,适用于 OLED 屏幕
- Android开发:使用Kotlin+协程+自定义注解+Retrofit的网络框架
- Android开发:当前项目以Module形式引用别的项目的步骤
- Android恶意木马伪装成游戏APP,通过华为AppGallery分发
- 2021年Android开发新技术动向,未来的路该怎么走?
- Uni-app离线打包Android APK详细教程