关于应用性能优化之VerifyClass详解


为了加速应用冷启动过程且不过度涉及业务改动,本文从虚拟机加载类的过程中找到优化项,且与业界的方案作了对比,并实现了半自动化的分析功能。类在使用或实例化之前需要被加载到虚拟机中并进行初始化。整个过程如下图所示:主要由loadingclass和initializingclass两部分组合。
loadingclass旨在把class从dex加载到虚拟机中,但不涉及类的使用或执行流程。initializingclass旨在保证使用类前已经经过了初始化流程,此流程嵌入类的使用或执行过程中。
加载类
defineclass主要通过setupclass、insertclass以及loadclass将一个类加载到虚拟机中,最后返回mirror:class对象指针。
setupclass:设置类的访问标志以及classloader。
insertclass:将类插入到对应classloader的classtable中,以便查找。
loadclass:将类的属性及方法加载到类中。
类初始化
类的属性或方法在使用前必须经过类的初始化。
initializeclass:核验类、初始化父类、接口方法以及静态属性。
verifyclass:核验类的合法性,在下一节详细分析。
核验类
verifyclass使用verifyclassusingoatfile或performclassverification方法之一去核查class。其中performclassverification就包含了systrace中耗时verifyclass的tag,如下图所示:
verifyclassusingoatfile:通过oat文件中的class状态位去核验class,当状态位等于kstatusverified时,核查流程到此为止,直接快速返回。否则需要进入耗时的performclassverification流程。
performclassverification:主要核验类中的直接方法和虚方法。
computewidthsandcountops:判断pc值与dalvik指令数是否相等。
scantrycatchblocks:检查try语句开始地址、结束地址以及try开始操作符的合法性。检查catch中handler语句开始操作符的合法性。
verifyinstructions:检查各种dalvik指令,同时将gc检查点插入到括号、switch、throw指令中。
verifycodeflow:检查每条dalvik指令的寄存器以及参数的合法性。
提前发现
从上面的分析可以看出,应该尽可能让核查走verifyclassusingoatfile流程,即通过oat文件状态位核查成功。oat文件中类的状态位是什么以及为什么状态位不等于kstatusverified是问题的突破点。
通过oatdump命令去dump相应的odex文件,可以查看类的状态位,操作方式如下:
vlog默认是不会被打印的,需要动态开启,开启的方式可以通过:art::glogverbosity.class_linker = true而打开,因为本项目需要看到dex2oat和其他进程的打印情况,本人是在系统源码中进行编译生成的so,然后,通过ptrace注入so到zygote的,此方法需要root设备,如果只需要查看本进程,应不需要这么麻烦,具体方法还未探索,但思路应该是一致的。举例如下,本人碰到的问题是appcompat包中的类不能被核验通过。
解决方案
将runtime对象中的verify_设置成verifier::verifymode::knone。
需要通过runtime对象首地址遍历查找verify_属性,魔改厂商可能带来兼容性问题。
缺少verifyclass过程,可能会后置发现非法指令问题。
对zygote中值verify_进行修改将造成cow内存消耗。
将多出ensureskipaccesschecksmethods一步处理逻辑,将类中每个函数flag进行修改,此处逻辑没有对单个类进行处理,所以,每个类的每个函数的flag都将被无谓修改,如下图所示:
直面问题本身,通过vlog的输出信息,去修正源码,具体到本案例,是由于appcompat库中使用了系统不支持的语句,如下图所示:
本app运行环境是在8.1(api27)上,textview没有方法setfirstbaselinetotopheight,所以,因为指令非法导致类核验失败。(注意build.version.sdk_int是不会被编译优化的,它本身是final类型,但它的取值是等于systemproperties.getint(“ro.build.version.sdk”, 0),所以,必须运行时,才能确定)。本人尝试了如下方法:
将系统源码sdk中的build.version.sdk_int值设置成27进行编译出新的sdk,然后,将此sdk覆盖源生的android.jar,希望编译时将appcompat中的build.version.sdk_int 》= 28判断逻辑优化掉,但实际aar不会参与sdk的编译,此项只能优化项目自身的逻辑。
将appcompat源码下载下来,去掉非法指令,重新编译成aar使用。
直接在android8.1源码中编译support v7包使用。
以上两种方法,能定制自己所需的aar,甚至能裁剪资源,但碰到了致命的问题:新生成的aar不能发布到maven了,这样的话,需要推动业务修改包名,另一个问题是,如果是项目中的第三方aar依赖了appcompat的话,问题又会出现。所以,最终通过制作asm插件,将build.version.sdk_int值设置成固定27,问题解决了,且使得本项目中apk size减少了22k。
如果是应用需要兼容多个不同版本的rom,也可以按照rom版本的不同,使用app bundle下发“最合适”的app。
平台化
为了降低方案实施难度,现已将方案平台化,只要将apk拖入网页中即可看到类核验不通过的原因。


ChinaSourcing第一届优秀外包企业ITO20强
一种在光子逻辑门内部的硬件纠错方式
江淮汽车因动力电池安全隐患召回iEV5
世界各地5G的发展状况和我国5G产业发展前景预测与产业链投资机会分析
初创企业PolyCrypt将区块链应用于物联网实时流程
关于应用性能优化之VerifyClass详解
电容器太阳能草坪灯的设计
笔记本内存检测技巧
五大国际联盟集体封杀华为
!销售/收购/维修HP53131A频率计HP 53131A
UPS系统原理及蓄电池组充放电试验
PCB布线规则和技巧图解(下)
AI如何献力疫情中
计算机体系架构获“图灵奖”
首款5G手机 中兴AXON10 Pro 即将亮相
STM32WB55_NUCLEO开发(10)----接收手机特定数据点亮LED
大数据工具的普及与流行势在必行
供应原装Agilent安捷伦33220A函数信号发生器
事件:伽利略导航24颗卫星全崩溃 给予Facebook数字货币最严监管
LG360Cam全景相机评测 适合非专业的休闲玩家