CN113535137A - 加载和运行应用程序的方法、装置及相关产品 - Google Patents
加载和运行应用程序的方法、装置及相关产品 Download PDFInfo
- Publication number
- CN113535137A CN113535137A CN202010294919.1A CN202010294919A CN113535137A CN 113535137 A CN113535137 A CN 113535137A CN 202010294919 A CN202010294919 A CN 202010294919A CN 113535137 A CN113535137 A CN 113535137A
- Authority
- CN
- China
- Prior art keywords
- class
- version
- offset
- increment
- incremental
- Prior art date
- Legal status (The legal status is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the status listed.)
- Pending
Links
Images
Classifications
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/30—Creation or generation of source code
- G06F8/31—Programming languages or programming paradigms
- G06F8/315—Object-oriented languages
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/40—Transformation of program code
- G06F8/41—Compilation
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/70—Software maintenance or management
- G06F8/71—Version control; Configuration management
Landscapes
- Engineering & Computer Science (AREA)
- Software Systems (AREA)
- General Engineering & Computer Science (AREA)
- Theoretical Computer Science (AREA)
- Physics & Mathematics (AREA)
- General Physics & Mathematics (AREA)
- Computer Security & Cryptography (AREA)
- Computing Systems (AREA)
- Devices For Executing Special Programs (AREA)
Abstract
本申请涉及加载和运行应用程序的方法、装置及相关产品。所述方法通过加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;根据所述对象大小创建所述类对应的对象。本申请提供的实施例,由于增量布局不改变存量布局,发布了类库的新版本时,即使不对应用程序进行重新编译,也不需要重新计算存量成员的偏移量,可以减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,缓解了应用程序启动时的卡顿,运行时访问存量成员不需要访问间接表,而且不会出现二进制兼容的问题,提高了应用程序运行效率。
Description
技术领域
本申请涉及计算机技术领域,尤其涉及一种加载和运行应用程序的方法、装置及相关产品。
背景技术
面向对象软件的二进制兼容问题,也称脆弱基类(Fragile Base Class)问题,是困扰软件维护的重要问题。在面向对象软件发布后,面向对象软件依赖的库或第三方代码中部分父类(基类)发生了改变,而导致已发布的面向对象软件需要重新进行编译、调试、测试和部署。
发明内容
本申请提出了一种加载和运行应用程序的方法及装置、生成字节码文件的方法、字节码文件、终端设备以及存储介质。
第一方面,本申请的实施例提供了一种加载和运行应用程序的方法,应用于终端设备,所述方法包括:
加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
根据所述对象大小创建所述类对应的对象。
结合第一方面的第一种可能的实现方式中,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。
由于增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
结合第一方面的第一种可能的实现方式,在第二种可能的实现方式中,所述存量布局中包括每个类的主版本的存量成员,所述增量布局中包括每个类的子版本相对于所述每个类的主版本增加的增量成员,其中,所述主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
结合第一方面或者第一方面的第一种可能的实现方式,在第三种可能的实现方式中,加载所述应用程序的类还包括:
根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量,所述类的反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
结合第一方面的第三种可能的实现方式,在第四种可能的实现方式中,所述方法还包括:
根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第一方面的第四种可能的实现方式,在第五种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从高地址到低地排布,
所述根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属的类的反向偏移量。
结合第一方面的第四种可能的实现方式,在第六种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从低地址到高地排布,
所述根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset2+offset1;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属类的反向偏移量。
根据上述实施例,对于存量成员和增量成员,在类加载时都不需要重新计算偏移量,类加载阶段只需要计算对象大小和每个类的反向偏移量,因此,可以进一步减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,缓解了应用程序启动卡顿的问题。另外,由于子版本的增量布局不改变存量布局,因此,访问存量成员不会出现二进制兼容的问题,访问增量成员时采用上述计算地址的方式可以计算出增量成员的实际地址,也不会出现二进制兼容的问题,采用本申请上述实施例的访问增量成员的方法也不会出现二进制兼容的问题。
同样的,虽然访问增量成员时需要负担2次内存读取的开销,但应用程序编译完成后,若基础类库发布了新版本,应用程序运行时访问增量成员的频率远比访问存量成员的频率低,因此,访问增量成员负担的上述开销不会对运行效率产生较大的影响,但访问存量成员减少的开销可以明显提高应用程序的运行效率。
由于访问存量成员不需要多访问一次内容,因此访问存量成员的访问指令也会减少,从而可以使得字节码文件或者应用程序的二进制可执行文件占用空间更小,另外,也无需存储间接表,这些都可以减小内存开销,节省内存。
另外,对于应用程序运行时,访问增量成员负担的读取内存的开销,可以通过调整发布策略发布新的主版本,提高应用程序的运行效率。
针对上述第五种和第六种两种不同的访问增量成员的方式,在访问第五种实现方式中的增量成员时,对于最高层祖先类可以不计算反向偏移量,访问最高层祖先类的增量成员时,相比于第六种实现方式也可以减少一次访问内存的过程,提高了访问效率。
结合第一方面或第一方面的第一种或第三种或第四种可能的实现方式,在第七种可能的实现方式中,所述加载应用程序的类还包括:
根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表,
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第一方面的第七种可能的实现方式,在第八种可能的实现方式中,根据所述对象的基地址、所述对象大小、所述增量成员所属类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3;
其中,add表示增量成员的起始地址,obj表示对象的基地址,OBs表示所述对象大小, offset3表示所述增量成员的第二偏移量。
根据以上第七种和第八种实现方式的过程可知,在类加载阶段,通过更新偏移量表,也就是对偏移量表进行初始化操作,根据实际运行环境计算出增量成员的第二偏移量。这样,终端设备可以根据第二偏移量以及对象大小和基地址正确的计算出增量成员的实际地址,能够解决二进制兼容的问题。但相比于相关技术中要计算所有成员的偏移量,本申请的实施例中是不需要重新计算存量成员的偏移量的,因此,可以减少由于类加载的过程重新计算偏移量导致的计算资源消耗,缓解了应用程序启动卡顿的问题。
也就是说,根据本申请上述实施例的加载和运行应用程序的方法,由于子版本的增量布局不改变存量布局,因此,即使不对应用程序进行重新编译,也不需要在类加载时重新计算存量布局,即不需要重新计算存量成员的偏移量,并且,采用本申请实施例的访问增量成员的方法也不会出现二进制兼容的问题。相比于现有技术中解决二进制兼容问题的方式,在类加载时不需要重新计算存量成员的偏移量,可以减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,缓解了应用程序启动卡顿的问题。
另外,应用程序运行时,访问存量成员不需要读取间接表,而且访问存量成员和增量成员也不会出现二进制兼容的问题,虽然访问增量成员时需要负担2次内存读取的开销,但应用程序编译完成后,若基础类库发布了新版本,应用程序运行时访问增量成员的频率远比访问存量成员的频率低,因此,访问增量成员负担的上述开销不会对运行效率产生较大的影响,但访问存量成员减少的开销可以明显提高应用程序的运行效率。
第二方面,本申请的实施例提供了一种生成字节码文件的方法,所述方法应用于编译器,所述方法包括:
若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件;
所述字节码文件中包括所述类代码的新版本的实例成员的信息,若所述新版本是类库中类代码的主版本的子版本,则所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
由于子版本的实例成员分为存量布局和增量布局,子版本的存量布局与主版本的存量布局相同,子版本的增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
结合第二方面的第一种可能的实现方式中,若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件,包括:
若所述新版本是类库中类代码的主版本的子版本,则根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成所述子版本的字节码文件,其中,所述类代码的子版本和所述前一版本都是基于类代码的同一个主版本发布的。
结合第二方面的第一种可能的实现方式,在第二种可能的实现方式中,所述实例成员的增量布局信息中包括子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第二方面的第二种可能的实现方式,在第三种可能的实现方式中,根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成子版本的字节码文件,包括:
加载所述前一版本的实例成员的信息;
比较所述类代码的子版本和前一版本的实例成员的信息以判断所述子版本相对于前一版本是否存在实例成员为增量成员;
若所述子版本相对于前一版本存在增量成员,则计算所述子版本相对于前一版本的增量成员的第一偏移量,并将前一版本的实例成员的信息和所述第一偏移量保存到所述子版本的实例成员的信息。
结合第二方面的第三种可能的实现方式,在第四种可能的实现方式中,根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成子版本的字节码文件,还包括:
若所述子版本相对于前一版本删除了实例成员,则在所述子版本的实例成员的信息中保留被删除了的实例成员的信息。
在本申请的上述实现方式中,子版本与前一版本存在的相同的增量成员的增量布局是相同的,而且子版本相对于前一版本增加的增量成员的增量布局不改变存量布局,也不改变前一版本已有的增量布局,也就是说,子版本与前一版本存在的相同的增量成员的增量布局相同,可以只对子版本相对于前一版本增加的增量成员计算第一偏移量,能够提高编译器编译的效率。
结合第二方面的第一种至第四种可能的实现方式中的任意一种,在第五种可能的实现方式中,对所述子版本进行编译生成子版本的字节码文件,包括:生成用于访问增量成员的第一访问指令,
所述第一访问指令用于根据应用程序的类对应的对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述类对应的对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
结合第二方面的第五种可能的实现方式,在第六种可能的实现方式中,所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
结合第二方面或者第二方面的第一种至第六种可能的实现方式中的任意一种,在第七种可能的实现方式中,若发布了类库中类代码的新版本,则对所述新版本的类代码进行编译生成字节码文件还包括:
若所述新版本是类库中类代码的主版本,对所述类代码的主版本进行编译生成主版本的字节码文件,其中,所述主版本的字节码文件中包括主版本的实例成员的存量布局信息。
结合第二方面或者第二方面的第一种至第六种可能的实现方式中的任意一种,在第八种可能的实现方式中,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
第三方面,本申请的实施例提供了一种字节码文件,所述文件为通过编译器对类库中的类代码进行编译生成的,所述字节码文件中包括所述字节码文件对应的类的实例成员的信息,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
结合第三方面的第一种可能的实现方式,所述实例成员的增量布局信息中包括类代码的子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第三方面的第一种可能的实现方式,在第二种可能的实现方式中,所述字节码文件中还包括用于访问增量成员的第一访问指令,所述第一访问指令用于根据对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时,由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
结合第三方面的第二种可能的实现方式,在第三种可能的实现方式中,所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
结合第三方面或者第三方面的第一种至第三种可能的实现方式中的任意一种,在第四种可能的实现方式中,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
第四方面,本申请的实施例提供了一种加载和运行应用程序的装置,应用于终端设备,所述装置包括:
加载模块,用于加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
对象创建模块,用于根据所述对象大小创建所述类对应的对象。
结合第四方面的第一种可能的实现方式中,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。
结合第四方面或第四方面的第一种可能的实现方式,在第二种可能的实现方式中,所述加载模块还用于根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量,所述反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
结合第四方面的第二种可能的实现方式,在第三种可能的实现方式中,所述装置还包括:
访问模块,用于根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第四方面的第三种可能的实现方式,在第四种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从高地址到低地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属的类的反向偏移量。
结合第四方面的第三种可能的实现方式,在第五种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从低地址到高地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset2+offset1;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属类的反向偏移量。
结合第四方面或者第四方面的第一种至第三种可能的实现方式中的任意一种,在第六种可能的实现方式中,所述加载模块还用于:
根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表,
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第四方面的第六种可能的实现方式,在第七种可能的实现方式中,所述访问模块还用于:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3;
其中,add表示增量成员的起始地址,obj表示对象的基地址,OBs表示所述对象大小, offset3表示所述增量成员的第二偏移量。
结合第四方面的第一种可能的实现方式,在第八种可能的实现方式中,所述存量布局中包括每个类的主版本的存量成员,所述增量布局中包括每个子版本相对于所述每个类对应的主版本增加的增量成员,其中,所述主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
第五方面,本申请的实施例提供了一种编译装置,所述编译装置应用于编译器,所述编译装置包括:
编译模块,用于若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件;
所述字节码文件中包括所述类代码的新版本的实例成员的信息,若所述新版本是类库中类代码的主版本的子版本,则所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
结合第五方面的第一种可能的实现方式中,编译模块包括:
第一编译单元,用于若所述新版本是类库中类代码的主版本的子版本,则根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成所述子版本的字节码文件,其中,所述类代码的子版本和所述前一版本都是基于类代码的同一个主版本发布的。
结合第五方面的第一种可能的实现方式,在第二种可能的实现方式中,所述实例成员的增量布局信息中包括子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
结合第五方面的第二种可能的实现方式,在第三种可能的实现方式中,第一编译单元还用于:加载所述前一版本的实例成员的信息;
比较所述类代码的子版本和前一版本的实例成员的信息以判断所述子版本相对于前一版本是否存在实例成员为增量成员;
若所述子版本相对于前一版本存在增量成员,则计算所述子版本相对于前一版本的增量成员的第一偏移量,并将前一版本的实例成员的信息和所述第一偏移量保存到所述子版本的实例成员的信息。
结合第五方面的第三种可能的实现方式,在第四种可能的实现方式中,第一编译单元还用于:若所述子版本相对于前一版本删除了实例成员,则在所述子版本的实例成员的信息中保留被删除了的实例成员的信息。
结合第五方面的第一种、第二种、第三种或者第四种可能的实现方式,在第五种可能的实现方式中,第一编译单元还用于生成用于访问增量成员的第一访问指令,
所述第一访问指令用于根据应用程序的类对应的对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述类对应的对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
结合第五方面的第五种可能的实现方式,在第六种可能的实现方式中,所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
结合第五方面或者第五方面的第一种至第六种可能的实现方式种的任意一种,在第七种可能的实现方式中,编译模块还包括:
第二编译单元,用于若所述新版本是类库中类代码的主版本,对所述类代码的主版本进行编译生成主版本的字节码文件,其中,所述主版本的字节码文件中包括主版本的实例成员的存量布局信息。
结合第五方面或者第五方面的第一种至第六种可能的实现方式种的任意一种,在第八种可能的实现方式中,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
第六方面,本申请的实施例提供了一种终端设备,包括:
处理器;
用于存储字节码文件的存储器;
其中,所述处理器被配置为执行所述字节码文件时实现上述第一方面或者第一方面的几种可能的实现方法之中的一种方法。
第七方面,本申请的实施例提供了一种编译装置,包括:
处理器;
用于处理器可执行指令的存储器;
其中,所述处理器被配置为执行所述可执行指令时实现第二方面或者第二方面的几种可能的实现方法之中的一种方法。
第八方面,本申请的实施例提供了一种非易失性计算机可读存储介质,其上存储有计算机程序指令,所述计算机程序指令被处理器执行时实现上述第一方面或者第一方面的几种可能的实现方法之中的一种方法。
第九方面,本申请的实施例提供了一种非易失性计算机可读存储介质,其上存储有计算机程序指令,所述计算机程序指令被处理器执行时实现第二方面或者第二方面的几种可能的实现方法之中的一种方法。
根据下面参考附图对示例性实施例的详细说明,本申请的其它特征及方面将变得清楚。
附图说明
包含在说明书中并且构成说明书的一部分的附图与说明书一起示出了本申请的示例性实施例、特征和方面,并且用于解释本申请的原理。
图1示出根据本申请一实施例的应用场景的示意图。
图2示出根据本申请一实施例的终端设备的结构示意图。
图3示出根据本申请一实施例的终端设备的软件结构框图。
图4示出根据本申请一实施例的生成字节码文件的方法的流程图。
图5示出根据本申请一实施例的类的存量成员的布局的示意图。
图6示出根据本申请一实施例的步骤S201的方法的流程图。
图7a示出根据本申请一实施例的类的实例成员的布局的示意图。
图7b示出根据本申请一实施例的类的实例成员的布局的示意图。
图8示出根据本申请一实施例的编译类代码的过程中计算类的实例成员的第一偏移量的流程图。
图9示出根据本申请一实施例的类的实例成员的布局的示意图。
图10a示出根据本申请一实施例的加载和运行应用程序的方法流程图。
图10b示出根据本申请一实施例的对象布局的示意图。
图10c示出根据本申请一实施例的加载和运行应用程序的方法流程图。
图10d示出根据本申请一实施例的类加载过程的流程图。
图11a示出根据本申请一实施例的对象布局的示意图。
图11b示出根据本申请一实施例的对象布局的示意图。
图11c示出根据本申请一实施例的加载和运行应用程序的方法的流程图。
图12示出根据本申请一实施例的加载和运行应用程序的方法流程图。
图13示出根据本申请一实施例的加载和运行应用程序的装置的框图。
具体实施方式
以下将参考附图详细说明本申请的各种示例性实施例、特征和方面。附图中相同的附图标记表示功能相同或相似的元件。尽管在附图中示出了实施例的各种方面,但是除非特别指出,不必按比例绘制附图。
在这里专用的词“示例性”意为“用作例子、实施例或说明性”。这里作为“示例性”所说明的任何实施例不必解释为优于或好于其它实施例。
另外,为了更好的说明本申请,在下文的具体实施方式中给出了众多的具体细节。本领域技术人员应当理解,没有某些具体细节,本申请同样可以实施。在一些实例中,对于本领域技术人员熟知的方法、手段、元件和电路未作详细描述,以便于凸显本申请的主旨。
术语解释:
Object layout,对象布局,是指类或者对象中的实例成员变量在内存中排布的方式、实例成员变量的大小等关于实例成员变量的属性信息,具体表达的方式可以包括实例成员变量的偏移量、尺寸等。实例成员变量的偏移量可以是指实例成员变量存储的起始地址相对于其所属类的对象布局的基地址的偏移量。编译器在对类代码进行编译时,会计算类的Object layout。
Object Size,对象大小,类对应的实例对象的大小。终端设备在进行类加载时,会计算应用程序的类对应的对象大小。
对象的基地址,终端设备在对类进行实例化时,根据对象大小分配内存空间创建对象,返回的内存空间的起始地址为对象的基地址。
对象的结束地址,上述内存空间的结束地址,也等于对象的基地址与对象大小的和。
实例成员,类代码中未使用static修饰的成员,可以包括实例成员变量(instancefield) 和虚函数。
存量成员,类代码的主版本中包括的实例成员。
增量成员,类代码的子版本相对于主版本增加的实例成员。
存量布局,存量成员的布局。
增量布局,增量成员的布局。
类的反向偏移量,在对象布局的增量布局中类的起始地址相对于对象的结束地址的偏移量,也可以理解为,在对象布局的增量布局中,类的增量布局的起始地址相对于对象的结束地址的偏移量。
增量成员的第一偏移量,增量成员的地址相对于增量成员所属类的增量布局的起始地址的偏移量。
目前常见的面向对象语言编译器在表达子类(派生类)的对象布局(Objectlayout)和虚函数表(VTable)时,往往采用与父类表达紧密耦合的方式,即,将父类的Object layout和虚函数表全部或者部分导入到子类中。对于这种紧密耦合的方式,无论父类的公共接口的原型和语义是否改变,只要父类的Object layout或虚函数表发生变化就会影响变化前已经发布的子类软件在运行时的表现,导致运行结果不符合预期。
具体来说,在编译程序的时候,编译器会计算每个实例成员变量在对象布局中的偏移量并将该偏移量赋值给访问实例成员变量的访问指令、编译器还可以计算每个虚函数的指针在虚函数表中的偏移量,并将该偏移量赋值给访问虚函数的访问指令。当父类代码的变化导致父类的Object layout和虚函数表发生变化时,子类中至少部分实例成员变量或者虚函数的指针的实际偏移量与变化之前应该是不同的,如果不进行重新编译的话,上述根据变化之前的父类进行编译计算得到的每个实例成员变量的偏移量或者虚函数的指针的偏移量并没有随着父类的Object layout和虚函数表的变化而变化,因此,在运行访存指令访问子类对象的实例成员变量或者虚函数时,使用的仍然是原来的偏移量,会导致运行的结果不符合预期。
示例性的,表1展示了目前大多数面向对象语言编译器在处理类继承时采用的Object layout方案,面向对象语言可以包括C++、Java、C#、Objective-C、SWIFT等等。
表1
如表1所示,假设类MyObject派生于类NSObject,类NSObject包含一个实例成员变量 isa,类MyObject包含两个实例成员变量students和teachers。在编译应用程序的时候,编译器会计算每个实例成员变量在对象中的偏移量,此时,类MyObject中三个实例成员变量的偏移量分别是:isa:0、students:4、teachers:8。在编译应用程序生成可执行文件时,这些偏移量被作为立即数操作数编码到机器指令当中,例如赋值给访问实例成员变量的访问指令。
如果在此后,类NSObject更新了版本,添加了实例成员变量secretAry和secretImg,如表2所示,表2中示出类NSObject的新版的的Object layout,以及未重新编译的应用程序对应的对象的Object layout(即类MyObject对应的对象布局)。
表2
如果不重新编译类MyObject,在类加载时,加载类NSObject的新版可执行文件和类 MyObject的旧版可执行文件来运行,在创建类MyObject的对象时,得到的实际的布局如表3 所示。
表3
那么在访问students时,实际上访问到的是secretAry、访问teachers时实际上访问到的是secretImg,应用程序将运行错误,因为访问实例成员变量students和teachers所用的偏移量不符合类NSObject的新版定义。以上就是静态编译、动态语言的应用场景下,在维护面向对象应用软件时的二进制兼容问题。
相关技术中采用“间接表”技术或者modern runtime技术在运行时维护NonFragile ivars 来解决二进制兼容的技术问题。发明人发现相关的解决二进制兼容问题的技术至少存在以下一些技术问题,在应用启动时,初始化Object layout要消耗一定的计算资源,从而导致应用程序启动的卡顿,另外,在访问对象的成员时读取内存的开销或者将调用虚函数转为调用另一个函数的过程都会导致访问效率比较低,影响应用程序运行效率。
比如说,开源静态Java编译器GCJ(GNU Compiler for Java)使用“间接表”技术来解决二进制兼容问题。在BC(Binary Compatible)ABI(Application Binary Interface,应用程序二进制接口)中,将实例成员变量和虚函数的指针的偏移量或者地址作为数据存放在特殊的全局数据结构中,例如otable和atable。在访问实例成员变量或者虚函数时,首先从otable 或者atable的固定位置读取实例成员变量或者虚函数的指针的偏移量或者地址,再根据读取的偏移量或者地址访问实例成员变量或者虚函数,其中,固定位置的地址可以编译时确定的,并赋值给访问实例成员变量或者虚函数的指令。
在“间接表”技术中,在启动应用程序时,需要对otable和atable进行初始化操作,根据运行环境计算出实例成员变量在对象布局中的偏移量或者地址、虚函数的指针在虚函数表中的偏移量,也就是说要重新计算Object layout,并写入otable和atable中上述成员变量或者虚函数对应的项。
也就是说,上述过程在类加载时计算对象大小和Object layout都将消耗一定的计算资源,并且,除了新增的实例成员变量,对于原有的实例成员变量还会导致大量的重复计算,会导致应用程序启动的卡顿;应用程序运行时访问成员变量或者虚函数时,为了获取偏移量或者地址,需要多读取一次内存(先读取otable或者atable得到偏移量或者地址),访问效率比较低,影响应用程序运行效率。
Objective-C的modern runtime在运行时维护Non Fragile ivars(健壮的实例成员变量),在运行时,如果父类的实际大小超过此前在子类中记录的父类大小,将进行对象布局的重新计算,也就是说,Objective-C的modern runtime可以根据程序变化动态计算对象大小和Object layout。
在编译时,LLVM(英文全称:Low Level Virtual Machine,中文:底层虚拟机)可以计算出父类的对象大小为XX字节,将父类的对象大小记录在子类MyClass定义中。仍然以上述表1和表2所示的示例为例进行说明。
class_ro_t class_ro_MyClass={
.instanceStart=40;//MyClass示例成员变量的起始地址偏移量,也就是父类的对象大小
.instanceSize=48;//类MyClass的对象大小
}
如果父类的大小增加到48字节,那么应用程序启动后,modern runtime加载类MyClass 的定义时,计算父类的对象大小为48,与类MyClass的instanceStart不相符,判断父类的大小发生了改变,子类的instanceStart小于父类的instanceSize,说明父类新增了实例成员变量,子类的实例成员变量需要进行偏移。
modern runtime将遍历类MyClass的所有实例成员变量,将实例成员变量的偏移量指向的值加N进行偏移量修正,类MyClass定义的instanceStart和instanceSize也要增加N。其中, N表示增加的成员变量的大小,且修正偏移值N按照8字节对齐的值,在该示例中,N为8 字节。
modern runtime在应用程序启动时也要检查Object layout、初始化全局变量和实例成员变量的偏移量,从而消耗一定的计算资源,导致应用程序启动时的卡顿。
另外,Objective-C的modern runtime在进行虚函数调用时,要将所有虚函数调用转为对 objc_msgSend()的调用,传递一个对象、方法选择器(method selector)和实际调用参数作为参数。 objc_msgSend先根据方法选择器查找实际要调用的方法,然后发起调用,运行效率远低于使用虚函数表的调用方式。
综上可知,为了解决二进制兼容的问题,相关技术方案中初始化Object layout需要消耗一定的计算资源,这些都导致应用程序启动卡顿,应用程序运行时,访问对象实例成员变量需要额外读取内存的开销或者将调用虚函数需要转为调用另一个函数的过程,都会影响应用程序运行效率。为了解决上述技术问题,本申请提供了一种生成字节码文件的方法、一种字节码文件以及一种加载和运行应用程序的方法。
本申请的生成字节码文件的方法用于在对类库中的类代码进行编译时执行,在此过程中计算类的实例成员的Object layout(计算实例成员变量的偏移量和虚函数的指针的偏移量),采用本申请生成字节码文件的方法以及计算Object layout的方式,既可以解决二进制兼容的问题,又可以减轻由于类加载的过程进行偏移或地址的重新计算引起的程序启动卡顿,以及降低访问实例成员变量时读取内存的开销或者调用虚函数的过程对应用程序运行效率的影响。
通过约束类库中类的实例成员的更新,使类库中的类的实例成员只能增加,不能减少,并且对于增加的实例成员采用特殊的布局方式以及访问方式既解决了二进制兼容的问题、又可以减少类加载时的计算的负担以及提高运行效率。图1示出根据本申请一实施例的应用场景的示意图。如图1所示,本申请的生成字节码文件的方法应用于编译器,可以生成字节码文件,加载和运行应用程序的方法可以应用于运行应用程序的终端设备。
图1中的编译器可以是能够对采用面向对象语言编写的程序源代码进行编译的编译器,比如说,Java编译器、C++编译器等。图1中的终端设备可以是指能够运行二进制可执行文件的各类终端设备,比如说,计算机、智能手机、平板电脑等等,还可以是虚拟机。
如图1所示,编译器可以对类库中的类代码进行编译生成类代码的字节码文件,并在生成类代码的字节码文件的过程中计算类的实例成员的Objectlayout(即计算类的实例成员的偏移量)。编译器也可以读取应用程序的源代码,根据类库中的类代码以及类代码对应的字节码文件对应用程序的源代码进行编译生成应用程序的二进制可执行文件,并输出到终端设备。终端设备可以执行应用程序的二进制可执行文件以运行应用程序,终端设备在应用程序启动时,执行本申请提供的加载和运行应用程序的方法。
图2示出根据本申请一实施例的终端设备的结构示意图。以终端设备是手机为例,图2 示出了手机200的结构示意图。
手机200可以包括处理器210,外部存储器接口220,内部存储器221,USB接口230,充电管理模块240,电源管理模块241,电池242,天线1,天线2,移动通信模块251,无线通信模块252,音频模块270,扬声器270A,受话器270B,麦克风270C,耳机接口270D,传感器模块280,按键290,马达291,指示器292,摄像头293,显示屏294,以及SIM卡接口295等。其中传感器模块280可以包括陀螺仪传感器280A,加速度传感器280B,接近光传感器280G、指纹传感器280H,触摸传感器280K(当然,手机200还可以包括其它传感器,比如温度传感器,压力传感器、距离传感器、磁传感器、环境光传感器、气压传感器、骨传导传感器等,图中未示出)。
可以理解的是,本申请实施例示意的结构并不构成对手机200的具体限定。在本申请另一些实施例中,手机200可以包括比图示更多或更少的部件,或者组合某些部件,或者拆分某些部件,或者不同的部件布置。图示的部件可以以硬件,软件或软件和硬件的组合实现。
处理器210可以包括一个或多个处理单元,例如:处理器210可以包括应用处理器(application processor,AP),调制解调处理器,图形处理器(graphics processingunit,GPU),图像信号处理器(image signal processor,ISP),控制器,存储器,视频编解码器,数字信号处理器(digital signal processor,DSP),基带处理器,和/或神经网络处理器(Neural-network Processing Unit,NPU)等。其中,不同的处理单元可以是独立的器件,也可以集成在一个或多个处理器中。其中,控制器可以是手机200的神经中枢和指挥中心。控制器可以根据指令操作码和时序信号,产生操作控制信号,完成取指令和执行指令的控制。
处理器210中还可以设置存储器,用于存储指令和数据。在一些实施例中,处理器210 中的存储器为高速缓冲存储器。该存储器可以保存处理器210刚用过或循环使用的指令或数据。如果处理器210需要再次使用该指令或数据,可从所述存储器中直接调用。避免了重复存取,减少了处理器210的等待时间,因而提高了系统的效率。
处理器210可以运行本申请实施例提供的二进制可执行文件,并且可以对静态编译、动态语言的父类对象进行兼容,也就是说,在静态编译、动态语言的应用场景下,终端设备的处理器210执行本申请提供的加载和运行应用程序的方法,既可以解决二进制兼容的问题、又能够减少类加载阶段计算的负担,提高运行效率。处理器210可以包括不同的器件,比如集成CPU和GPU时,CPU和GPU可以配合执行本申请实施例提供的加载和运行应用程序的方法,以得到较快的处理效率。
显示屏294用于显示图像,视频等。显示屏294包括显示面板。显示面板可以采用液晶显示屏(liquid crystal display,LCD),有机发光二极管(organic light-emittingdiode,OLED),有源矩阵有机发光二极体或主动矩阵有机发光二极体(active-matrixorganic light emitting diode 的,AMOLED),柔性发光二极管(flex light-emittingdiode,FLED),Miniled,MicroLed, Micro-oLed,量子点发光二极管(quantum dot lightemitting diodes,QLED)等。在一些实施例中,手机200可以包括1个或N个显示屏294,N为大于1的正整数。显示屏294可用于显示由用户输入的信息或提供给用户的信息以及各种图形用户界面(graphical user interface, GUI)。例如,显示器294可以显示照片、视频、网页、或者文件等。再例如,显示器294可以显示图形用户界面。其中,图形用户界面上包括状态栏、可隐藏的导航栏、时间和天气小组件(widget)、以及应用的图标,例如浏览器图标等。状态栏中包括运营商名称(例如中国移动)、移动网络(例如4G)、时间和剩余电量。导航栏中包括后退(back)键图标、主屏幕 (home)键图标和前进键图标。此外,可以理解的是,在一些实施例中,状态栏中还可以包括蓝牙图标、Wi-Fi图标、外接设备图标等。还可以理解的是,在另一些实施例中,图形用户界面中还可以包括Dock栏,Dock栏中可以包括常用的应用图标等。当处理器210检测到用户的手指(或触控笔等)针对某一应用图标的触摸事件后,响应于该触摸事件,打开与该应用图标对应的应用的用户界面,并在显示器294上显示该应用的用户界面。
在本申请实施例中,显示屏294可以是一个一体的柔性显示屏,也可以采用两个刚性屏以及位于两个刚性屏之间的一个柔性屏组成的拼接显示屏。
摄像头293(前置摄像头或者后置摄像头,或者一个摄像头既可作为前置摄像头,也可作为后置摄像头)用于捕获静态图像或视频。通常,摄像头293可以包括感光元件比如镜头组和图像传感器,其中,镜头组包括多个透镜(凸透镜或凹透镜),用于采集待拍摄物体反射的光信号,并将采集的光信号传递给图像传感器。图像传感器根据所述光信号生成待拍摄物体的原始图像。
内部存储器221可以用于存储计算机可执行程序代码,例如存储本申请实施例提供的二进制可执行文件,所述可执行程序代码包括指令。处理器210通过运行存储在内部存储器221 的指令,从而执行手机200的各种功能应用以及数据处理。内部存储器221可以包括存储程序区和存储数据区。其中,存储程序区可存储操作系统,应用程序(比如相机应用,微信应用等)的代码等。存储数据区可存储手机200使用过程中所创建的数据(比如相机应用采集的图像、视频等)等,例如,存储类的实例化对象。
内部存储器221还可以存储本申请实施例提供的加载和运行应用程序的方法对应的一个或多个计算机程序1310。该一个或多个计算机程序1310被存储在上述存储器221中并被配置为被该一个或多个处理器210执行,该一个或多个计算机程序1310包括指令,上述指令可以用于执行如图10a、图10c、图11c或图12相应实施例中的各个步骤。
此外,内部存储器221可以包括高速随机存取存储器,还可以包括非易失性存储器,例如至少一个磁盘存储器件,闪存器件,通用闪存存储器(universal flash storage,UFS)等。
当然,本申请实施例提供的加载和运行应用程序的方法的代码还可以存储在外部存储器中。这种情况下,处理器210可以通过外部存储器接口220运行存储在外部存储器中的加载和运行应用程序的方法的代码。
下面介绍传感器模块280的功能。
陀螺仪传感器280A,可以用于确定手机200的运动姿态。在一些实施例中,可以通过陀螺仪传感器280A确定手机200围绕三个轴(即,x,y和z轴)的角速度。即陀螺仪传感器280A 可以用于检测手机200当前的运动状态,比如抖动还是静止。
当本申请实施例中的显示屏为可折叠屏时,陀螺仪传感器280A可用于检测作用于显示屏294上的折叠或者展开操作。陀螺仪传感器280A可以将检测到的折叠操作或者展开操作作为事件上报给处理器210,以确定显示屏294的折叠状态或展开状态。
加速度传感器280B可检测手机200在各个方向上(一般为三轴)加速度的大小。即陀螺仪传感器280A可以用于检测手机200当前的运动状态,比如抖动还是静止。当本申请实施例中的显示屏为可折叠屏时,加速度传感器280B可用于检测作用于显示屏294上的折叠或者展开操作。加速度传感器280B可以将检测到的折叠操作或者展开操作作为事件上报给处理器210,以确定显示屏294的折叠状态或展开状态。
接近光传感器280G可以包括例如发光二极管(LED)和光检测器,例如光电二极管。发光二极管可以是红外发光二极管。手机通过发光二极管向外发射红外光。手机使用光电二极管检测来自附近物体的红外反射光。当检测到充分的反射光时,可以确定手机附近有物体。当检测到不充分的反射光时,手机可以确定手机附近没有物体。当本申请实施例中的显示屏为可折叠屏时,接近光传感器280G可以设置在可折叠的显示屏294的第一屏上,接近光传感器280G可根据红外信号的光程差来检测第一屏与第二屏的折叠角度或者展开角度的大小。
陀螺仪传感器280A(或加速度传感器280B)可以将检测到的运动状态信息(比如角速度)发送给处理器210。处理器210基于运动状态信息确定当前是手持状态还是脚架状态(比如,角速度不为0时,说明手机200处于手持状态)。
指纹传感器280H用于采集指纹。手机200可以利用采集的指纹特性实现指纹解锁,访问应用锁,指纹拍照,指纹接听来电等。
触摸传感器280K,也称“触控面板”。触摸传感器280K可以设置于显示屏294,由触摸传感器280K与显示屏294组成触摸屏,也称“触控屏”。触摸传感器280K用于检测作用于其上或附近的触摸操作。触摸传感器可以将检测到的触摸操作传递给应用处理器,以确定触摸事件类型。可以通过显示屏294提供与触摸操作相关的视觉输出。在另一些实施例中,触摸传感器280K也可以设置于手机200的表面,与显示屏294所处的位置不同。
示例性的,手机200的显示屏294显示主界面,主界面中包括多个应用(比如相机应用、微信应用等)的图标。用户通过触摸传感器280K点击主界面中相机应用的图标,触发处理器210启动相机应用,打开摄像头293。显示屏294显示相机应用的界面,例如取景界面。
手机200的无线通信功能可以通过天线1,天线2,移动通信模块251,无线通信模块252,调制解调处理器以及基带处理器等实现。
天线1和天线2用于发射和接收电磁波信号。手机200中的每个天线可用于覆盖单个或多个通信频带。不同的天线还可以复用,以提高天线的利用率。例如:可以将天线1复用为无线局域网的分集天线。在另外一些实施例中,天线可以和调谐开关结合使用。
移动通信模块251可以提供应用在手机200上的包括2G/3G/4G/5G等无线通信的解决方案。移动通信模块251可以包括至少一个滤波器,开关,功率放大器,低噪声放大器(lownoise amplifier,LNA)等。移动通信模块251可以由天线1接收电磁波,并对接收的电磁波进行滤波,放大等处理,传送至调制解调处理器进行解调。移动通信模块251还可以对经调制解调处理器调制后的信号放大,经天线1转为电磁波辐射出去。在一些实施例中,移动通信模块 251的至少部分功能模块可以被设置于处理器210中。在一些实施例中,移动通信模块251 的至少部分功能模块可以与处理器210的至少部分模块被设置在同一个器件中。在本申请实施例中,移动通信模块251还可以用于与其它终端设备进行信息交互。
调制解调处理器可以包括调制器和解调器。其中,调制器用于将待发送的低频基带信号调制成中高频信号。解调器用于将接收的电磁波信号解调为低频基带信号。随后解调器将解调得到的低频基带信号传送至基带处理器处理。低频基带信号经基带处理器处理后,被传递给应用处理器。应用处理器通过音频设备(不限于扬声器270A,受话器270B等)输出声音信号,或通过显示屏294显示图像或视频。在一些实施例中,调制解调处理器可以是独立的器件。在另一些实施例中,调制解调处理器可以独立于处理器210,与移动通信模块251或其他功能模块设置在同一个器件中。
无线通信模块252可以提供应用在手机200上的包括无线局域网(wireless localarea networks,WLAN)(如无线保真(wireless fidelity,Wi-Fi)网络),蓝牙(bluetooth,BT),全球导航卫星系统(global navigation satellite system,GNSS),调频(frequencymodulation,FM),近距离无线通信技术(near field communication,NFC),红外技术(infrared,IR)等无线通信的解决方案。无线通信模块252可以是集成至少一个通信处理模块的一个或多个器件。无线通信模块252经由天线2接收电磁波,将电磁波信号调频以及滤波处理,将处理后的信号发送到处理器210。无线通信模块252还可以从处理器210接收待发送的信号,对其进行调频,放大,经天线2转为电磁波辐射出去。
另外,手机200可以通过音频模块270,扬声器270A,受话器270B,麦克风270C,耳机接口270D,以及应用处理器等实现音频功能。例如音乐播放,录音等。手机200可以接收按键290输入,产生与手机200的用户设置以及功能控制有关的键信号输入。手机200可以利用马达291产生振动提示(比如来电振动提示)。手机200中的指示器292可以是指示灯,可以用于指示充电状态,电量变化,也可以用于指示消息,未接来电,通知等。手机200中的SIM卡接口295用于连接SIM卡。SIM卡可以通过插入SIM卡接口295,或从SIM卡接口295拔出,实现和手机200的接触和分离。
应理解,在实际应用中,手机200可以包括比图2所示的更多或更少的部件,本申请实施例不作限定。图示手机200仅是一个范例,并且手机200可以具有比图中所示出的更多的或者更少的部件,可以组合两个或更多的部件,或者可以具有不同的部件配置。图中所示出的各种部件可以在包括一个或多个信号处理和/或专用集成电路在内的硬件、软件、或硬件和软件的组合中实现。
终端设备的软件系统可以采用分层架构,事件驱动架构,微核架构,微服务架构,或云架构。本申请实施例以分层架构的Android系统为例,示例性说明终端设备的软件结构。
图3是本申请实施例的终端设备的软件结构框图。
分层架构将软件分成若干个层,每一层都有清晰的角色和分工。层与层之间通过软件接口通信。在一些实施例中,将Android系统分为四层,从上至下分别为应用程序层,应用程序框架层,安卓运行时(Android runtime)和系统库,以及内核层。
应用程序层可以包括一系列应用程序包。
如图3所示,应用程序包可以包括电话、相机,图库,日历,通话,地图,导航,WLAN,蓝牙,音乐,视频,短信息等应用程序。
应用程序框架层为应用程序层的应用程序提供应用编程接口(applicationprogramming interface,API)和编程框架。应用程序框架层包括一些预先定义的函数。
如图3所示,应用程序框架层可以包括窗口管理器,内容提供器,视图系统,电话管理器,资源管理器,通知管理器等。
窗口管理器用于管理窗口程序。窗口管理器可以获取显示屏大小,判断是否有状态栏,锁定屏幕,截取屏幕等。
内容提供器用来存放和获取数据,并使这些数据可以被应用程序访问。所述数据可以包括视频,图像,音频,拨打和接听的电话,浏览历史和书签,电话簿等。
视图系统包括可视控件,例如显示文字的控件,显示图片的控件等。视图系统可用于构建应用程序。显示界面可以由一个或多个视图组成的。例如,包括短信通知图标的显示界面,可以包括显示文字的视图以及显示图片的视图。
电话管理器用于提供终端设备的通信功能。例如通话状态的管理(包括接通,挂断等)。
资源管理器为应用程序提供各种资源,比如本地化字符串,图标,图片,布局文件,视频文件等等。
通知管理器使应用程序可以在状态栏中显示通知信息,可以用于传达告知类型的消息,可以短暂停留后自动消失,无需用户交互。比如通知管理器被用于告知下载完成,消息提醒等。通知管理器还可以是以图表或者滚动条文本形式出现在系统顶部状态栏的通知,例如后台运行的应用程序的通知,还可以是以对话窗口形式出现在屏幕上的通知。例如在状态栏提示文本信息,发出提示音,终端设备振动,指示灯闪烁等。
Android Runtime包括核心库和虚拟机。Android runtime负责安卓系统的调度和管理。
核心库包含两部分:一部分是java语言需要调用的功能函数,另一部分是安卓的核心库。
应用程序层和应用程序框架层运行在虚拟机中。虚拟机将应用程序层和应用程序框架层的java文件执行为二进制文件。虚拟机用于执行对象生命周期的管理,堆栈管理,线程管理,安全和异常的管理,以及垃圾回收等功能。
系统库可以包括多个功能模块。例如:表面管理器(surface manager),媒体库(Media Libraries),三维图形处理库(例如:OpenGL ES),2D图形引擎(例如:SGL)等。
表面管理器用于对显示子系统进行管理,并且为多个应用程序提供了2D和3D图层的融合。
媒体库支持多种常用的音频,视频格式回放和录制,以及静态图像文件等。媒体库可以支持多种音视频编码格式,例如:MPEG4,H.264,MP3,AAC,AMR,JPG,PNG等。
三维图形处理库用于实现三维图形绘图,图像渲染,合成,和图层处理等。
2D图形引擎是2D绘图的绘图引擎。
内核层是硬件和软件之间的层。内核层至少包含显示驱动,摄像头驱动,音频驱动,传感器驱动。
生成字节码文件的方法
图4示出根据本申请一实施例的生成字节码文件的方法的流程图。该方法可以应用于如图1所示的编译器中,用于对类库中的类代码进行编译。
本申请的生成字节码文件的方法可以包括:若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件,其中,所述新版本的字节码文件中包括所述类代码的新版本的实例成员的信息。
若所述新版本为类库中类代码的主版本,则所述新版本的实例成员的信息包括实例成员的存量布局信息。若所述新版本是类库中类代码的主版本的子版本,则所述新版本的实例成员的信息包括实例成员的存量布局信息和增量布局信息。在对基础类库中的类代码进行修改时,会根据发布策略发布不同的版本。在本申请的实施例中,根据发布策略会发布基础类库的主版本以及基于该主版本的一个或多个子版本,其中,主版本发布后不再兼容根据此主版本之前的版本编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序,也就是说主版本的子版本发布后,基于该主版本或者主版本的任意一个子版本编译的应用程序均可兼容运行。
类代码为用于对类的定义进行描述的代码,类代码中定义了类成员和实例成员,其中,类成员可以是指使用static修饰的成员,实例成员可以是指没有使用static修饰的成员。在本申请的实施例中,实例成员可以包括实例成员变量(instance field)和虚函数(或者叫虚方法)。
对类代码进行的修改可以包括对实例成员的以下操作中的任意一种或多种:删除、增加、更换名称、更换类型,等等。需要说明的是,上述更换名称或者更换类型可以认为是删除了原来的实例成员的同时增加了新的实例成员。
根据实例成员被发布的时间可以将实例成员分为存量成员和增量成员,首次被发布时间与主版本发布时间相同的实例成员为存量成员,基于该主版本发布的子版本相对于该主版本增加的实例成员都属于增量成员。也就是说,主版本类代码包括的实例成员,例如实例成员变量和虚函数,都是存量成员;子版本类代码相对于主版本类代码增加的实例成员,例如增加的实例成员变量和虚函数,都是增量成员。
其中,实例成员的信息可以是指实例成员的布局、布局类型标识、类型、数量、布局大小等布局信息、实例成员的属性等。
实例成员的布局可以包括实例成员的偏移量,比如说,对于实例成员变量来说,实例成员变量的布局可以包括实例成员变量的偏移量,对于虚函数来说,虚函数的布局可以包括虚函数的指针的偏移量。
对于实例成员变量来说,不同的类型存储需要的内存空间大小不同。实例成员的布局大小可以通过实例成员的类型以及数量表示或者根据实例成员的类型和数量计算得到,举例来说,对于实例成员变量而言,不同类型的实例成员变量在存储时占用的内存空间的大小是确定的,例如,实例成员变量的类型为整型时占用4字节、为浮点型时占用8字节。因此,根据类包含的实例成员变量的类型和数量可以计算实例成员的布局大小。成员变量还可以是除了上述示例以外的其他类型,本发明对此不作限定。
实例成员的布局类型标识可以用于表示实例成员属于存量成员还是增量成员,比如说,可以用“0”表示成员为存量成员,“1”表示成员为增量成员,还可以采用其他方式设置布局类型标识,本申请不限于上述示例。实例成员的属性可以是指private、public、protect等。
需要说明的是,上述信息仅仅是本申请实施例中关于实例成员的信息的几个示例,本申请不限于此。举例来说,对于一个实例成员变量来说,其信息可以包括实例成员变量的名称、实例成员变量的数据类型、实例成员变量的数量、实例成员变量的偏移量、实例成员变量的属性、实例成员变量的布局类型标识、所有存量实例成员变量的大小等等。对于虚函数来说,参数可以包括虚函数的名称、虚函数的类型、虚函数的属性、虚函数的个数,等等。其中,虚函数的类型可以用于确定在虚函数表中存储虚函数的指针需要的内存空间。
上述实例成员的存量布局信息可以是指存量成员的布局信息,存量成员的布局信息可以包括存量成员的布局(简称存量布局)、存量成员的布局类型标识、存量成员的类型、数量或者存量布局的大小等信息。其中,存量成员的布局可以包括存量成员的偏移量,比如说对于存量实例成员变量来说,存量实例成员变量的布局可以包括存量实例成员变量的偏移量;对于存量虚函数来说,存量虚函数的布局可以包括存量虚函数的指针的偏移量,存量虚函数的指针的偏移量可以是指存量虚函数的指针在虚函数表中存储的地址相对于虚函数表的指针指向的地址的偏移量。
上述实例成员的增量布局信息可以是指增量成员的布局信息,增量成员的布局信息可以包括增量成员的布局(简称增量布局)、增量成员的布局类型标识、增量成员的类型、数量,或者增量布局的大小等信息。增量成员的布局可以包括增量成员的第一偏移量,存量成员的偏移量的计算方式和增量成员的第一偏移量的计算方式在下文中详细介绍。
本申请的实施例中,在基础类库发布类代码的新版本时,不管发布的新版本是主版本还是子版本,都要对类代码的新版本进行重新编译得到新版本的实例成员的信息、字节码等,经过编译后生成新版本的字节码文件。对类代码进行编译得到新版本的实例成员的信息可以包括计算实例成员的布局信息、数量、类型,确定实例成员的布局类型标识等等,生成新版本的字节码可以包括生成访问实例成员的访问指令等。
在一种可能的实现方式中,实例成员的信息可以以元数据(meta-data)的形式存储,新版本的字节码文件中可以包括元数据,也就是类代码的新版本的元数据,新版本的元数据中存储有新版本的实例成员的信息。举例来说,实例成员变量的信息都保存在meta-data中,虚函数的指针保存在虚函数表中,虚函数的其他信息都可以保存在meta-data中,也就是说,虚函数的布局、指向虚函数表的指针保存在meta-data中,虚函数的指针在虚函数表中存储时布局的方式即虚函数的布局。当然,也可以采用其他存储实例成员的信息的方式,本申请对此不作限定。
如图4所示,若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件可以包括步骤S200和S201。
步骤S200,若发布的类代码的新版本是类库中类代码的主版本,对所述类代码的主版本进行编译生成主版本的字节码文件,其中,所述主版本的字节码文件中包括主版本的实例成员的存量布局信息。
步骤S201,若发布的类代码的新版本是类库中类代码的主版本的子版本,则根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成所述子版本的字节码文件,所述子版本的字节码文件中包括子版本的实例成员的信息,子版本的实例成员的信息包括存量布局信息和增量布局信息。
在一种可能的实现方式中,主版本的字节码文件以及子版本的字节码文件中都可以包括元数据,元数据用于保存实例成员的信息。
比如说,步骤S200中的主版本的字节码文件中包括主版本的元数据,主版本的元数据用于保存主版本的实例成员的信息,也就是用于保存主版本的实例成员的存量布局信息;例如,存量布局、存量布局的大小或者存量成员的类型和数量、存量成员的布局类型标识、属性等等。
步骤S201中的子版本和前一版本都是基于类代码的同一个主版本。步骤S201中的子版本的字节码文件中包括子版本的元数据,子版本的元数据用于保存子版本的实例成员的信息,如果子版本相对于主版本存在增量成员,也就是用于保存实例成员的存量布局信息和增量布局信息。举例来说,子版本的元数据用于保存子版本的实例成员的存量布局、增量布局、存量布局的大小或者存量成员的类型和数量、增量布局的大小或者增量成员的类型和数量,等等。所述实例成员的增量布局中包括子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量用于表示所述增量成员的地址相对于该增量成员所属的类的增量布局的起始地址的偏移量,增量成员的第一偏移量为编译器对类代码进行编译时确定的。
在一种可能的实现方式中,实例成员的存量布局中包括存量成员的偏移量,存量成员的偏移量可以为存量成员的地址相对于存量成员所属的类对应的对象的基地址的偏移量。在本申请的实施方式中,子版本的实例成员的存量布局信息和主版本的实例成员的布局信息可以是相同的。也就是说,子版本的实例成员的存量布局、存量布局的大小都与主版本相同。也就是子版本的存量成员的布局或者说存量成员的偏移量与主版本的存量成员的偏移量相同。
由于子版本的存量布局与主版本的存量布局是相同的,也就是说子版本的增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
在一种可能的实现方式中,子版本的存量布局的大小和增量布局的大小可以在编译器编译时计算得到,并记录在元数据中,这样,终端设备在加载类的字节码文件时,可以直接确定类的存量布局的大小和增量布局的大小。本申请不限于此,比如说,元数据(字节码文件) 中还可以记录存量成员的类型、各类型的存量成员的数量、增量成员的类型、各类型的增量成员的数量等可以计算存量布局的大小和增量布局的大小的参数,这样,终端设备在获取元数据(字节码文件)后,也可以根据上述参数计算存量布局的大小和增量布局的大小。
在本申请的实施例中,类的子版本相对于前一版本的成员不能减少,而且子版本相对于前一版本增加的增量成员的增量布局不改变存量布局,也不改变前一版本已有的增量布局,也就是说,子版本与前一版本存在的相同的增量成员的增量布局相同,而且子版本与前一版本存在的相同的增量成员的增量布局是相同的,也可以提高编译器编译的效率。
除了上述的元数据保存的实例成员的信息,主版本和子版本的字节码文件中还可以包括可执行程序(字节码),可执行程序中可以包括用于访问增量成员的第一访问指令。在本申请的实施方式中,步骤S201对所述子版本进行编译生成所述子版本的字节码文件可以包括:生成用于访问增量成员的第一访问指令。
其中,所述第一访问指令用于根据应用程序的类对应的对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,其中,所述对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的。其中,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,或者说,在对象的增量布局中所述类的增量布局的起始地址相对于对象的结束地址的偏移量,对象的结束地址可以是指对象的实例成员布局结束的地址。所述对象的基地址在终端设备根据对象大小创建所述对象时由终端设备确定的。
也就是说,终端设备在进行类加载时可以计算应用程序的类对应的对象大小,根据对象大小可以创建应用程序的类对应的对象,获得对象的基地址,根据对象的基地址和对象大小还可以确定对象的结束地址。在对象的对象布局中可以包括存量布局和增量布局,增量布局中存储增量成员,对于应用程序的类以及类的祖先类,如果类存在增量成员,那么在对象布局中有类对应的增量布局。在对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。类对应的增量布局的起始地址相对于对象的结束地址的偏移量即类的反向偏移量。
这样,终端设备访问所述增量布局的增量成员时,从所述对象的结束地址开始向基地址方向查找到所述增量成员所属的类的增量布局,由于在编译时已经确定了增量成员的第一偏移量,第一偏移量为增量成员的地址相对于增量成员所属的类的增量布局的起始地址的偏移量,因此,在确定增量成员所述的类的增量布局后,可以根据第一偏移量在增量布局中查找到增量成员。通过上述方式不仅可以解决访问增量成员的二进制兼容的技术问题,在类加载时也不需要对增量成员的偏移量进行初始化,也就是说,不需要重新计算增量成员的布局,进一步降低应用程序启动时的计算量,加快应用程序启动速度,具体解决的原理在下文的示例中详细介绍。
字节码文件中还可以包括用于访问存量成员的访问指令,访问存量成员的访问指令根据对象的基地址和存量成员的偏移量可以计算得到存量成员的起始地址。由于对象的基地址在终端设备创建所述对象时由终端设备确定的,根据上述对象布局中存量布局和增量布局排布的方式可知,增量布局不改变存量布局,因此,存量成员的偏移量不变,类库中的类代码更新版本后,终端设备访问存量成员时也不存在二进制兼容的技术问题,并且终端设备启动应用程序进行类加载时不需要重新计算存量成员的偏移量,降低应用程序启动时的计算量,加快应用程序启动速度。相对于间接表的方式,本申请实施例在访问存量成员时可以减少一次读取内存的过程,访问效率更高,由于不需要多访问一次内容,因此访问存量成员的访问指令也会减少,从而可以使得字节码文件或者应用程序的二进制可执行文件占用空间更小,另外,也无需存储间接表,这些都可以减小内存开销,节省内存。
在一种可能的实现方式中,编译器可以在对主版本或者子版本的类代码进行编译生成字节码的过程中计算实例成员的偏移量、确定实例成员的类型、实例成员的数量、实例成员的布局类型标识等信息,将实例成员的信息保存在元数据中,根据元数据与字节码生成字节码文件。
类的实例成员的布局示例
A存量成员
以发布类库中类代码的主版本为例,编译器可以按照主版本中类代码描述的顺序逐个计算实例成员变量的偏移量、虚函数的指针的偏移量。在本申请的实施例中,存量成员的偏移量等于存量成员之前的存量成员的大小,以存量实例成员变量为例,存量实例成员变量的偏移量等于位于其之前的存量实例成员变量的大小。编译器还可以确定类中包括的实例成员的类型,各类型的实例成员的数量,或者根据实例成员的类型和各类型的实例成员的数量也可以获得所有存量成员的大小,也就是,存量布局的大小。
若发布的类代码为子类,则编译器可以查找到其所有父类(祖先类),若父类的类代码还没有进行编译,则先编译父类的类代码得到父类的字节码文件。在编译子类的类代码时获取所有父类的实例成员的信息,基于所有父类的实例成员的信息进一步计算子类的存量实例成员的偏移量得到子类的实例成员的存量布局,具体为,从祖先类按照继承链的顺序依次进行布局。
图5示出根据本申请一实施例的类的存量成员的布局的示意图。假设图5所示的示意图为类APPClass对应的对象布局,上端为对象的基地址,假设基地址为低地址端,越向下地址越高,在对象布局中从对象的基地址开始按照如下顺序排布:按照继承链从祖先类依次到类 APPClass的存量布局。如图5所示,从基地址开始依次为:祖先类A0、祖先类B0、子类AppClass 的存量布局。
在一种可能的实现方式中,在一个类的存量布局内部,从低地址到高地址,存量成员可以按照类代码中定义的顺序排布。
若当前编译的类代码为祖先类的主版本类代码,例如图5所示的父类A0的主版本类代码,则对父类A0的主版本类代码进行编译得到父类A0的主版本的字节码,按照类代码主版本中存量成员定义的顺序计算存量成员的偏移量得到存量成员的布局,确定存量成员的数量、类型、属性、布局类型表示等信息,将存量成员的布局以及存量成员的其他信息保存在元数据中,根据主版本的字节码和元数据可以生成主版本的字节码文件。父类A0的主版本的元数据保存了父类A0的主版本的存量成员的布局,也就是如图5中的A0部分的存量成员的偏移量。
若当前编译的类代码为子类的主版本类代码,例如图5所示的子类B0,则对子类B0的主版本类代码进行编译时,获得父类A0的元数据,基于父类A0的元数据计算子类B0的存量成员的偏移量等参数保存在元数据中,比如说对于子类B0中的实例成员变量B01,在计算偏移量时,应该根据其父类A0包括的所有存量实例成员变量的大小以及子类B0中上述实例成员变量B01之前的实例成员变量的大小计算得到实例成员变量B01的偏移量,如图5所示存量成员B01的偏移量offset为存量布局A0的大小与存量成员B00的大小的和。生成子类B0的主版本的字节码,根据子类主版本的元数据和字节码生成子类主版本的字节码文件。子类B0的主版本的元数据保存了子类B0的主版本的存量成员的布局,也就是如图5中的B0部分的存量成员的偏移量。
采用与上述子类A1类似的方式对类AppClass进行编译可以得到类AppClass的存量成员的布局,如图5中的AppClass部分所示。
也就是说,对类库中的类代码的主版本进行编译后生成主版本的字节码文件,类的主版本的字节码文件中包括类的实例成员的存量布局信息。
主版本的字节码(文件)中包括访问存量成员的访问指令,访问存量成员的访问指令用于根据对象的基地址和存量成员的偏移量计算存量成员在内存中存储的起始地址,在生成字节码的过程中将计算得到的存量成员的偏移量以立即数的形式赋值给存量成员的访问指令。这样,如图5所示,以B01为例,在访问存量成员时计算对象的基地址和存量成员的偏移量的和(基地址+B01:offset)即可得到存量成员的地址。
B存量成员+增量成员
若发布的类代码的新版本为子版本,图6示出根据本申请一实施例的步骤S201的方法的流程图。如图6所示,步骤S201可以包括:
步骤S2011,加载所述前一版本的实例成员的信息;
步骤S2012,比较所述类代码的子版本和所述前一版本的实例成员的信息以判断所述子版本相对于前一版本是否存在实例成员为增量成员;
步骤S2013,若所述子版本相对于前一版本存在增量成员,则计算所述子版本相对于前一版本的增量成员的第一偏移量,并将所述前一版本的实例成员的信息和所述第一偏移量保存到所述子版本的实例成员的信息;
步骤S2014,若所述子版本相对于前一版本删除了实例成员,则在所述子版本的实例成员的信息中保留被删除了的实例成员的信息。
编译器可以按照子版本类代码中描述的成员的顺序逐一与前一版本的实例成员的信息进行比较,若子版本相对于前一版本存在新的实例成员,那么子版本相对于前一版本就存在增量成员。通过比较可以确定出子版本相对于前一版本的所有增量成员,计算子版本相对于前一版本的增量成员的第一偏移量、类型、数量、布局类型标识等信息,将前一版本的实例成员的信息与计算得到的子版本相对于前一版本的增量成员的信息作为子版本的实例成员的信息保存,生成子版本的字节码,根据子版本的字节码和实例成员的信息可以生成子版本的字节码文件。
若子版本相对于前一版本的元数据不存在增量成员,则可以直接将前一版本的实例成员的信息作为子版本的实例成员的信息。
若子版本相对于前一版本存在被删除了的实例成员,则编译器保留所述子版本的实例成员的信息中被删除了的实例成员的信息,被删除了的实例成员可能是存量成员,也可能是增量成员,不管是存量成员或增量成员被删除,都保留子版本的实例成员的信息中被删除了的实例成员的信息。这样,可以保证类的任一子版本相对于任一子版本之前的版本的实例成员不减少。基于主版本发布的任一子版本相对于任一子版本之前发布的子版本以及主版本的实例成员只能增加不减少,这样,任一子版本才能兼容根据任一子版本之前发布的子版本或主版本编译的应用程序,且启动应用程序时不需要对应用程序的类的所有实例成员的布局进行初始化。
对于上述增量成员的第一偏移量,不同的增量布局方式,计算第一偏移量的方式也不同。以应用程序中的一个类对应的对象为例,对对象布局中增量布局的方式进行直观的说明。在一种可能的实现方式中,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。若其中一个类不存在增量布局,则在对象布局的增量布局中也不存在这个类的增量布局。
假设对象的基地址为低地址端,那么在对象布局中,从基地址开始向高地址方向根据继承链连续排布所述应用程序的祖先类以及类的存量布局,从对象的结束地址开始向低地址方向,根据继承链连续排布所述应用程序的祖先类以及类的增量布局。也就是说,对于存在继承关系的多个类的增量布局,所有增量布局都位于对象的所有存量布局所在的地址段之外的高地址段,对于父类来说,同一个父类的存量布局和增量布局是不连续的。
需要说明的是,本申请不限于此,比如说,在存量布局之外的高地址段中从低地址到高地址,类的增量布局按照从祖先类到子类的顺序排布。不同的排布方式导致计算访问指令中需要的偏移量时的计算方法不同。
图7a和图7b分别示出根据本申请一实施例的类的实例成员的布局的示意图。假设应用程序中的类为APPClass,类APPClass的祖先类包括类A和类B,类A为类B的父类,其中,类A包括存量布局A0和增量布局A1,类B包括存量布局B0和增量布局B1。如图7a和图 7b所示,上端表示对象布局的基地址,从基地址开始,按照A0、B0、APPClass、B1、A1 的顺序排布,也就是,先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。假设基地址为低地址端,越向下地址越高,增量布局中从高地址到低地址按照从A1到B1的顺序排布。A1和B1的布局不改变存量布局A0 和B0,且位于存量布局A0和B0之外的高地址段。
在一个类的增量布局内部,按照增量成员增加的时间顺序,可以从高地址到低地址排布,也可以从低地址到高地址排布。举例来说,如图7a所示,对于增量布局B1的增量成员,从高地址到低地址,增量成员可以按照增加的时间顺序排布,如图7a所示的增量成员B10、 B11……B1x的排布方式,其中,“B1”表示类B的增量成员,序号“0”、“1”、“x”可以代表增量成员增加的时间顺序,序号越小表示该增量成员发布的越早,在图7a中在增量布局 B1内部,从下到上(从高地址到低地址)增量成员B10、B11……B1x按照增加的时间顺序排布。
本申请不限于图7a所示的示例,例如,在一个类的增量布局内部,也可以从低地址到高地址,增量成员按照增加的时间顺序排布,如图7b所示,在增量布局B1内部,从上到下(从低地址到高地址)增量成员B10、B11……B1x按照增加的时间顺序排布。
对于第一偏移量,类的增量布局内部增量成员的排布方式不同,第一偏移量的计算方式也不相同。以图7a和图7b所示的示例中类的增量布局按照类的继承顺序从高地址向低地址排布的方式为例:如图7a所示,对于类的增量成员内部,如果增量成员按照增加的时间顺序从高地址到低地址排布,那么增量成员的第一偏移量为增量类成员所属的类在所述增量成员以前增加的增量成员的大小加上所述增量成员的大小,以增量成员B11为例,B11的第一偏移量为增量成员B10和B11的大小之和,如图7a中标示的增量成员B11的第一偏移量offset1 (B11:offset1),为从B10布局的结束地址(增量布局B1的起始地址)开始到B11布局开始的位置的距离;如图7b所示,如果增量成员按照增加的时间顺序从低地址到高地址排布,那么增量成员的第一偏移量为增量成员所属的类在所述增量成员以前增加的增量成员的大小,仍然以增量成员B11为例,B11的第一偏移量offset1(B11:offset1)为B10的大小。
在子版本的字节码文件(程序)中还包括对存量成员的访问指令,与主版本的字节码文件(程序)中对存量成员的访问指令相同,都是根据对象的基地址和存量成员的偏移量计算存量成员在内存中存储的起始地址。
需要说明的是,以上虽然只介绍了计算存量成员的偏移量和增量成员的第一偏移量,但本申请的实施例中对类库中类代码的编译过程不限于此,如前文所示,还需要确定实例成员的其他信息,比如,类型、数量、存量布局大小、增量布局大小、实例成员的布局类型标识等等。
图8示出根据本申请一实施例的编译类代码的过程中计算类的实例成员的第一偏移量的流程图。在发布类代码主版本或者子版本时,可以根据如图8所示的过程对类的实例成员进行布局。
如图8所示,步骤S10中发布了类代码新版本,发布的新版本可能是主版本,也可能是基于主版本的子版本。
在发布类代码的新版本时,可以对发布的新版本进行重新编译生成新的字节码文件,并且可以重新计算新版本的实例成员的偏移量。
步骤S11,编译器可以判断当前发布的新版本是否为主版本:如果是,则执行步骤S12;如果否,则编译器可以执行步骤S13。
在一种可能的实现方式中,编译器可以根据新版本的版本号确定是否为主版本,比如说,版本号定义为X.Y.Z的格式,其中,X为主版本号,Y为子版本号,Z为当前子版本下未修改任何类定义(即,不需要重新计算布局)的小改版本号,编译器可以根据版本定义文件中的定义确定的版本号,并根据版本号确定版本。
步骤S12,编译器可以计算主版本中类的实例成员的偏移量得到存量布局,确定存量成员的类型、数量、属性、布局类型标识等参数,将计算得到的存量成员的存量布局和其他参数保存在元数据,然后执行步骤S23;
步骤S13,编译器可以加载根据前一版本类代码编译得到的前一版本的元数据,然后执行步骤S14;
在步骤S14-S17中,编译器可以将上述新版本中的实例成员逐个进行处理(与前一版本的元数据进行比较),确定是否存在新增成员(即增量成员)以及存在哪些增量成员。
步骤S14,编译器可以判断新版本中是否有实例成员未处理:如果是,则执行步骤S15,如果否,则执行步骤S18;
步骤S15,编译器可以获取下一个实例成员;
步骤S16,编译器可以判断前一版本的元数据中是否包含上述下一个实例成员;如果是,可以不对上述下一个实例成员进行处理,返回步骤S14;如果否,则编译器可以执行步骤S17;
步骤S17,编译器可以将上述下一个实例成员添加到新增成员列表,然后返回步骤S14;
步骤S18,在对新版本的实例成员处理完后,编译器可以判断上述新增成员列表中是否存在新增成员;如果不存在,则编译器可以执行步骤S22;如果存在,则编译器可以执行步骤S19;
步骤S19,编译器可以将前一版本的元数据全部添加到当前新版本的元数据中,然后执行步骤S20;
步骤S20,编译器可以判断新增成员列表中是否存在新增成员未处理,这里的处理可以是指计算新增成员的第一偏移量、确定新增成员的类型、属性、布局类型标识等参数;如果新增成员列表中不存在新增成员未处理,则编译器完成了新增成员的布局,可以执行步骤S23;如果新增成员列表中存在新增成员未处理,则编译器可以执行步骤S21;
步骤S21,编译器可以为计算下一个新增成员的第一偏移量,确定新增成员的类型、属性、布局类型标识等参数,并生成元数据添加到新版本的元数据中,然后返回步骤S20;
步骤S22,编译器可以将前一版本的元数据全部添加到新版本的元数据中,执行步骤S23;也就是说,当前版本相对于前一版本的类代码没有增加类成员,此时,可以直接将前一版本的元数据作为当前版本的元数据。
步骤S23,结束布局的过程。
需要说明的是,图8示出的布局的过程仅仅是本申请的一个示例,不以任何方式限制本申请。比如说,图8中步骤的顺序是可以调整的,步骤S19和步骤S22也可以在步骤S13之后直接执行。图8中有些步骤也是可以省略的,比如说步骤S17,编译器在确定类成员为增量成员后,可以直接为增量成员进行布局并生成元数据。
在一种可能的实现方式中,根据发布策略还可以发布新的主版本(相对于上文的主版本的下一个主版本),在发布新的主版本时,对所述新的主版本进行编译的过程中,计算所述新的主版本中各个类的存量成员的偏移量得到存量布局。
上述下一个主版本相对于上述主版本的最新子版本中类的成员可能有变化,也可能没有变化,不管有没有变化,根据新的主版本进行编译,将新的主版本的所有成员作为存量成员,并计算新的主版本的存量成员的偏移量得到新的存量布局。
假设类APPClass的主版本的最新子版本中的对象布局如图7a或者图7b所示,发布的新的主版本相对于之前的主版本的最新子版本的类成员没有变化,那么,以图7a为例,此时为上述新的主版本的类成员进行布局得到存量布局,结果如图9所示,在图9中,从基地址开始按照如下顺序连续排布:A0、A1、B0、B1、APPClass子类。在图7a中位于增量布局的部分在重新进行布局后位于存量布局部分。
加载和运行应用程序的方法
本申请还提供了一种加载和运行应用程序的方法,图10a示出根据本申请一实施例的加载和运行应用程序的方法流程图,该方法可以应用于图1中的终端设备。如图10a所示,所述方法可以包括:
步骤S100、加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
步骤S101、根据所述对象大小创建所述类对应的对象。
在一种可能的实现方式中,终端设备在检测到应用程序启动时,先进行应用程序的类加载。加载应用程序的类时,终端设备从应用程序中的类开始按照继承链向上查找到第一层祖先类,然后从第一层祖先类开始自上而下遍历应用程序类的祖先类以及应用程序的类,并加载遍历的类到内存中。
在一种可能的实现方式中,类加载时,终端设备可以将类的字节码文件加载到内存中,如上文所述,类的字节码文件中包括类的实例成员的信息,例如,实例成员的存量布局信息和增量布局信息。
在一种可能的实现方式中,如上所述,类的字节码文件中包括元数据,元数据中保存了类的实例成员的存量布局信息和增量布局信息,存量布局信息包括存量布局、存量布局的大小或者用于计算存量布局的大小的参数等,增量布局信息包括增量布局、增量布局的大小或者用于计算增量布局的大小的参数。终端设备在将类的字节码文件加载到内存中时,可以根据类的实例成员的存量布局的大小和增量布局的大小计算类对应的对象的大小。
终端设备在进行类加载的过程中,可以计算应用程序的类对应的对象大小(Object size),然后根据计算得到的对象大小创建所述类对应的对象。
在一种可能的实现方式中,终端设备每加载遍历的一个类到内存中,根据已加载的类的存量布局的大小和增量布局的大小计算所述类对应的对象大小,然后加载下一个类。也就是说,每加载遍历的一个类的字节码文件到内存中,根据已加载的类的存量布局的大小和增量布局的大小计算所述类对应的对象大小,然后加载下一个类的字节码文件。
对于步骤S100,若应用程序仅包括一个类,那么终端设备可以在检测到应用程序启动时,从闪存的基础类库中查找该类的字节码文件,并将该类的字节码文件加载到内存中,根据字节码中类的元数据可以确定应用程序的类对应的对象大小。
在一种可能的实现方式中,元数据中可以直接记录了类的存量布局的大小和增量布局的大小,终端设备获取字节码文件后可以直接根据元数据记录的类的存量布局的大小和增量布局的大小计算得到对象大小。
在另一种可能的实现方式中,元数据中也可以是记录了类的存量成员的类型和数量、增量成员的类型和数据,终端设备可以根据存量成员的类型和数量、增量成员的类型和数据计算得到对象大小。举例来说,对于类中的实例成员变量,所有实例成员变量的大小等于所有存量实例成员变量的大小和所有增量实例成员变量的大小的和。
以上仅仅是本申请计算对象大小的一些实例,本申请对具体计算对象大小的方式不作限定。
在确定应用程序的类对应的对象大小后,终端设备可以根据对象大小分配内存空间并返回对象的基地址。在所述内存空间中,从基地址开始按照如下顺序连续排布:先是类的存量布局,然后是类的增量布局。
图10b示出根据本申请一实施例的对象布局的示意图。假设上述示例中的应用程序中包括类A,类A的最新版本的类代码中的实例成员的信息包括存量布局信息和增量布局信息,也就是类A的实例成员的布局包括存量布局A0和增量布局A1。那么终端设备在加载类A的字节码文件后,根据字节码文件中的元数据保存的实例成员的信息可以计算得到对象大小为 A0+A1,根据对象大小为对象分配内存地址并返回对象的基地址,对象布局如图10b所示,从基地址开始,先是存量布局A0,然后是增量布局A1。
若应用程序的类继承了其他的类,也就是应用程序的类具有祖先类,那么,终端设备可以在检测到应用程序启动时,从应用程序中的类开始按照继承链向上查找到第一层祖先类,从第一层祖先类开始自上而下遍历,并加载遍历的类。也就是说,终端设备在进行类加载时要先从应用程序的类上溯到继承链的最上层节点,然后再从上而下遍历每个节点对应的类,加载遍历的类。如图7a所示,终端设备在加载应用程序的类APPClass之前,先上溯到类A0,加载类A,根据类A的存量布局A0和增量布局A1计算对象大小为A0+A1。然后再加载类 B,根据类B的存量布局B0和增量布局B1以及已加载的类A计算对象大小为A0+A1+B0 +B1,然后再加载类APPClass,计算对象大小为A0+A1+B0+B1+APPClass。根据对象大小即可创建对象。创建对象之后,对象布局如图7a所示,在对象布局中从基地址开始,按照 A0、B0、APPClass、B1、A1的顺序排布。
每一个类的存量布局中包括每个类的主版本的存量成员,每一个类的增量布局中包括每个类的子版本相对于所述每个类的主版本增加的增量成员。若一个类的子版本相对于子版本不存在增量成员,这个类也就不存在增量布局,那么在对象布局的增量布局部分没有这个类的增量布局,在对象布局中直接跳过这个类的增量布局,也就是说在对象布局的增量布局部分这个类的父类的增量布局与这个类的子类的增量布局相邻,或者这个类的父类的增量布局与存量布局相邻。以图7a为例,如果类B不存在增量布局,那么在对象布局中从基地址开始,按照A0、B0、APPClass、A1的顺序排布。
如图7a和图10b所示,由于增量布局不改变存量布局,而且子版本的存量布局与主版本的存量布局相同,因此,对于存量成员来讲,在启动应用程序进行类加载时不需要重新计算存量成员的偏移量,对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。
另外,由于增量布局不改变存量布局,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。访问存量成员不需要增加一次读取内存的过程,访问效率提高,应用程序的运行效率提高。而且访问增量成员不需要增加一次读内存的过程,使得访问存量成员的访问指令也会减少,从而可以使得字节码文件或者应用程序的二进制可执行文件占用空间更小,另外,也无需存储间接表,这些都可以减小内存开销,节省内存。
在一种可能的实现方式中,加载所述应用程序的类还包括:根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量。其中,所述反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
图10c示出根据本申请一实施例的加载和运行应用程序的方法流程图。如图10c所示,步骤S100可以包括:
步骤S1001,加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
步骤S1002,根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量。
需要说明的是,上述计算对象大小和类的反向偏移量的过程是同时进行的,比如说,在加载完一个类后,根据已加载的类的增量布局的大小和/或当前加载的类的增量布局的大小计算所述当前加载的类的反向偏移量。若当前加载的类不存在增量布局,也可以不加算当前加载的类的反向偏移量。
也就是说,在本申请的实施例中,终端设备每加载遍历的一个类到内存中,根据已加载的类的存量布局的大小和增量布局的大小计算对象大小,根据已加载的类的增量布局的大小计算所述类的反向偏移量,然后加载下一个类。这样,在加载完应用程序的类的祖先类以及应用程序的类之后,才能够计算得到应用程序的类对应的对象大小。
在一种可能的实现方式中,类的反向偏移量的计算方式与增量成员在增量布局中的排布方式以及不同类的增量布局的排布方式相关。举例来说,以图7a所示为例:在一个类的增量布局的内部,如果增量成员按照增加的时间顺序从高地址到低地址排布,那么类的反向偏移量为该类的所有父类的增量布局的大小,如图7a中的类B的反向偏移量为A1的大小,如图 7a中标出的(B:offset2)为类B的反向偏移量。在一个类的增量布局的内部,如果增量成员按照增加的时间顺序从低地址到高地址排布,那么类的反向偏移量为该类的所有父类的增量布局的大小+该类的增量布局的大小,如图7b中的类B的反向偏移量为A1的大小+B1的大小,如图7b中标出的(B:offset2)为类B的反向偏移量。
图10d示出根据本申请一实施例的类加载过程的流程图。如图10d所示,终端设备可以将应用程序的类作为当前遍历的类,终端设备可以判断当前遍历的类是否存在祖先类(父类),若不存在祖先类,则终端设备可以加载当前遍历的类的字节码文件到内存中,并计算对象大小、类的反向偏移量。
若当前遍历的类存在祖先类,则终端设备可以判断当前遍历的类的祖先类是否已加载,如果未加载,则终端设备可以将当前遍历的类的祖先类作为当前遍历的类,并返回判断当前遍历的类是否存在祖先类的步骤。如果当前遍历的类的祖先类已加载,则终端设备可以加载当前遍历的类的字节码文件到内存中,并计算对象大小、类的反向偏移量。
然后终端设备可以判断当前遍历的类是否存在子类,如果存在子类,则将子类作为当前遍历的类,并返回判断当前遍历的类是否存在祖先类的步骤;如果不存在子类,则可以结束类加载的过程。
对于上述计算对象大小和类的反向偏移量的过程,根据已加载的类和当前遍历的类的存量布局的大小、增量布局的大小进行计算。
图11a和图11b分别示出根据本申请一实施例的对象布局的示意图。在本申请的实施例中,假设应用程序被编译器编译完后,由终端设备下载到本地并安装在终端设备上,之后,基础类库的类代码发布了新版本,新版本类代码对应的对象布局如图11a或者图11b所示。编译器编译应用程序时可以是基于图5所示布局的主版本类代码,或者也可以是基于图5所示的主版本类代码和图11a(图11b)所示的子版本类代码中间的任一版本类代码,本申请对此不作限定。
在本申请的实施例中,编译应用程序时基础类库中类代码对应的实例成员的布局如图5 所示,启动应用程序时,基础类库中类代码对应的示例成员的布局如图11a或图11b所示为例。
假设对象大小为OBs,终端设备在检测到应用程序启动后,在加载APPClass之前,将 APPClass作为当前遍历的类,判断APPClass存在父类B,判断父类B未加载,则将类B作为当前遍历的类,判断类A1存在父类A,继续判断父类A未加载,将父类A作为当前遍历的类。此时,判断父类A不存在父类,也就是上溯到了继承链的最上层节点,加载父类A的字节码文件到内存中,此时,计算对象大小OBs=A0+A1,由于在本实施例中,在类的增量布局内,增量成员是按照增加的时间从高地址到低地址排布的,因此,父类A的反向偏移量为0。
判断父类A存在子类B,将类B作为当前遍历的类,判断类B存在父类A,但父类A 已加载,因此终端设备可以继续加载类B,加载父类B的字节码文件到内存中,此时,OBs= A0+A1+B0+B1,由于在本实施例中,在类的增量布局内,增量成员是按照增加的时间从高地址到低地址排布的,因此,父类B的反向偏移量为A1。
判断类B存在子类APPClass,将APPClass作为当前遍历的类,判断类APPClass存在父类且父类已加载,则终端设备可以继续加载类APPClass,加载类APPClass的字节码文件到内存中,此时,OBs=A0+A1+B0+B1+APPClass。终端设备判断类APPClass不存在子类,终端设备完成类加载的过程。也就是说,在加载类APPClass之后,才能计算得到类APPClass 对应的对象大小OBs。
在类加载完成后,终端设备可以根据对象大小OBs在内存中开辟与OBs对应的内存空间实例化对象,并分配基地址。在内存空间中,对象布局的一个示例可以如图11a或图11b所示。
图11c示出根据本申请一实施例的加载和运行应用程序的方法的流程图,如图11c所示,
在本申请的实施例中,所述方法还包括:
步骤S102,根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
访问增量成员时,计算增量成员的地址的方式与不同类的增量布局的排布方式和类的增量布局内部增量成员的排布方式相关。如上所述,字节码文件中可以包括用于访问增量成员的第一访问指令,终端设备在执行第一访问指令时可以根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员。对于增量成员的访问,为了解决二进制兼容的问题,本申请的实施例提供了步骤S102的方法,可以一步降低应用程序启动时的计算量,加快应用程序启动速度。根据增量布局排布的不同方式,访问增量成员的方法也不同。下面介绍本申请实施例提供的访问增量成员的方法。
首先说明一下,应用程序运行时,访问编译应用程序时类代码中还没有的增量成员的情况可以为:以图5、图11a和图11b为例,假设图5、图11a和图11b中为类的实例成员变量的布局,编译应用程序时存量实例成员变量的布局如图5所示,之后,发布了子版本的类代码,父类A和B都存在增量成员,如图11a或图11b所示。若APPClass调用了父类的方法,父类的方法中存在对增量成员的访问指令,这时,应用程序运行时会访问增量成员变量。
在本实施方式中,对于增量成员也不需要设置间接表,而是在访问增量成员时,直接据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算增量成员的地址。针对增量成员的不同排布方式,计算增量成员的地址的方法不同。下面分别以图11a和图11b为例对计算增量成员的地址的方法进行说明。
示例1,图11a中,在对象布局中,从基地址开始按照如下顺序连续排布:A0、B0、APPClass、 B1、A1子类,在一个类的增量布局中从高地址到低地址,增量成员按照增加的时间顺序排布,如图11a中,在增量布局A1中,从下往上依次为A10、A11、……A1x,其中,“A1”表示类A的增量布局,序号“0”、“1”、“x”可以代表增量成员增加的时间顺序,序号越小表示该增量成员发布的越早。
在本实施例中,步骤S102可以包括:根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1 为增量成员的第一偏移量,offset2表示增量成员所属类的反向偏移量,offset2等于增量成员所属类的所有父类的增量布局的大小。
如图11a所示,以A11为例,增量成员A11的第一偏移量offset1(A11)=A10+A11,类A的反向偏移量(A:offset2)为0。因此,
add(A11)=obj+OBs-(A10+A11)。
在一种可能的实现方式中,编译器在编译类代码生成二进制可执行程序时,对于增量成员可以生成以下的第一访问指令:
ldr R1,this.ObjectSize//对象大小赋值给R1
Sub R1,R1,A_offset//R1减去类A的反向偏移量
Sub R1,R1,16H//R1减去A11的第一偏移量,第一偏移量为立即数16H,因为在编译阶段已经可以根据A10的大小和A11的大小确定A11的第一偏移量的数值
ldr R0,[R4+R1]//R4=this(obj)(基地址),R1加上基地址计算得到的地址赋值给R0,也就是A11的实际地址。
示例2,图11b中,在对象布局中,从基地址开始按照如下顺序连续排布:A0、B0、APPClass、 B1、A1子类,在一个类的增量布局中从低地址到高地址,增量成员按照增加的时间顺序排布,如图11b所示,在增量布局A1中,从上往下依次为A10、A11、……A1x。
在本实施例中,步骤S102可以包括:根据以下方式计算增量成员的起始地址,
add=obj+OBs+offset1-offset2;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1 为增量成员的第一偏移量,offset2表示增量成员所属类的反向偏移量,offset2等于增量成员所属类的所有父类的增量布局的大小+增量成员所属类的增量布局的大小。
如图11b所示,以A11为例,增量成员A11的第一偏移量offset1(A11)=A10的大小,类A的反向偏移量为A1的大小。因此,
add(A11)=obj+OBs+A10-A1。
根据上述实施例,对于存量成员和增量成员,在类加载时都不需要重新计算偏移量,类加载阶段只需要计算对象大小和每个类的反向偏移量,因此,可以进一步减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,缓解了应用程序启动卡顿的问题。另外,由于子版本的增量布局不改变存量布局,因此,访问存量成员不会出现二进制兼容的问题,访问增量成员时采用上述计算地址的方式可以计算出增量成员的实际地址,也不会出现二进制兼容的问题,采用本申请上述实施例的访问增量成员的方法也不会出现二进制兼容的问题。
同样的,虽然访问增量成员时需要负担2次内存读取的开销,但应用程序编译完成后,若基础类库发布了新版本,应用程序运行时访问增量成员的频率远比访问存量成员的频率低,因此,访问增量成员负担的上述开销不会对运行效率产生较大的影响,但访问存量成员减少的开销可以明显提高应用程序的运行效率。
由于访问存量成员不需要多访问一次内容,因此访问存量成员的访问指令也会减少,从而可以使得字节码文件或者应用程序的二进制可执行文件占用空间更小,另外,也无需存储间接表,这些都可以减小内存开销,节省内存。
另外,对于应用程序运行时,访问增量成员负担的读取内存的开销,可以通过调整发布策略发布新的主版本,提高应用程序的运行效率。针对上述示例1和示例2中两种不同的访问增量成员的方式,在访问示例1中的增量成员时,对于最高层祖先类可以不计算反向偏移量,访问最高层祖先类的增量成员时,相比于示例2中的方式也可以减少一次访问内存的过程,提高了访问效率。
需要说明的是,本申请中计算增量成员的地址的方式不限于上述两个示例,还可以根据增量布局的其他排布方式以及增量布局内部增量成员的其他排布方式确定计算增量成员的起始地址的方式。
在另一种可能的实现方式中,所述第一访问指令中包括用于访问所述偏移量表的第二访问指令,偏移量表表用于存储增量成员的第二偏移量,增量成员的第二偏移量可以由终端设备根据增量成员的第一偏移量和增量成员所属类的反向偏移量计算得到、并将所述增量成员的第二偏移量保存至偏移量表。所述第二偏移量表示增量成员布局的地址相对于所述对象的结束地址的偏移量。
第二访问指令可以是对类库中的类代码编译时生成字节码文件时生成的,偏移量表中的一个的表项可以用于存储一个对应的增量成员的第二偏移量,因此,在编译时可以将访问偏移量表表的地址或者偏移量赋值给第二访问指令,这样通过执行第二访问指令就可以访问偏移量表中一个表项获取对应的增量成员的第二偏移量。
具体地,图12示出根据本申请一实施例的加载和运行应用程序的方法流程图。如图12 所示,
步骤S100还可以包括:
步骤S1003,根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表。
其中,步骤S1001、S1002、S101、S102的具体内容参见上文的描述,不再赘述。
对于步骤S1003,在计算完当前加载类的反向偏移量后,加载下一个类的字节码文件之前,针对当前加载类的增量成员,根据当前加载类的反向偏移量、以及增量成员的第一偏移量计算增量成员的第二偏移量,并将第二偏移量存储至间接表。
针对不同的增量成员的布局方式,计算增量成员的第二偏移量的方法不同。
以图11a所示的示例为例,增量成员的第二偏移量=增量成员的第一偏移量+增量成员所属类的反向偏移量。以父类A为例,父类A的反向偏移量为0。因此,对于A1的增量成员,增量成员的第二偏移量等于增量成员的第一偏移量,即图11a中的offset1+offset2,offset2 为0,所以A1的增量成员的第二偏移量等于第一偏移量。以父类B为例,父类B的反向偏移量为A1,父类B的增量成员的第二偏移量=父类A的增量成员的第一偏移量+A1。
在另一个示例中,如果在增量布局中,从低地址到高地址,增量成员按照增加的时间顺序排布。增量成员的第二偏移量等于增量成员所属类的反向偏移量减去增量成员的第一偏移量。如图11b所示的示例,此时,父类A的反向偏移量为A1,父类A的增量成员的第二偏移量=A1-父类A的增量成员的第一偏移量。
如图11b中的增量成员A10,A10的第一偏移量为0,A10的第二偏移量为A1;增量成员A11的第一偏移量为A10的大小,因此,增量成员A11的第二偏移量为A1-A10,即图 11b中的offset2-offset1。
终端设备在计算得到增量成员的第二偏移量后,可以将第二偏移量存储至偏移量表中对应的表项。需要说明的是,以上以图11a和图11b为例介绍的第二偏移量的计算方法仅仅是本申请的示例,本申请不限于此。
这样,终端设备在执行第二指令时,可以访问偏移量表得到的增量成员的第二偏移量,然后再执行第一指令,根据基地址、所述对象大小和增量成员的第二偏移量计算增量成员的起始地址。
假设对象的基地址为obj,增量成员的第二偏移量为offset3,步骤S102可以包括:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3,其中,add表示增量成员的地址。
如图11b所示,以增量成员A11为例,
add(A11)=obj+OBs-(A1-A10)
=obj+OBs-A1+A10;
如图11b所示,obj+OBs的地址为图11b所示的对象布局的结束地址,再减去A1为类A 的增量布局的起始地址,再加上A10的大小即增量成员A11存储的起始地址。
根据以上示例的过程可知,在类加载阶段,通过更新偏移量表,也就是对偏移量表进行初始化操作,根据实际运行环境计算出增量成员的第二偏移量。这样,终端设备可以根据第二偏移量以及对象大小和基地址正确的计算出增量成员的实际地址,能够解决二进制兼容的问题。但相比于相关技术中要计算所有成员的偏移量,本申请的实施例中是不需要重新计算存量成员的偏移量的,因此,可以减少由于类加载的过程重新计算偏移量导致的计算资源消耗,缓解了应用程序启动卡顿的问题。
也就是说,根据本申请上述实施例的加载和运行应用程序的方法,由于子版本的增量布局不改变存量布局,因此,即使不对应用程序进行重新编译,也不需要在类加载时重新计算存量布局,即不需要重新计算存量成员的偏移量,并且,采用本申请实施例的访问增量成员的方法也不会出现二进制兼容的问题。相比于现有技术中解决二进制兼容问题的方式,在类加载时不需要重新计算存量成员的偏移量,可以减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,缓解了应用程序启动卡顿的问题。
另外,应用程序运行时,访问存量成员不需要读取间接表,而且访问存量成员和增量成员也不会出现二进制兼容的问题,虽然访问增量成员时需要负担2次内存读取的开销,但应用程序编译完成后,若基础类库发布了新版本,应用程序运行时访问增量成员的频率远比访问存量成员的频率低,因此,访问增量成员负担的上述开销不会对运行效率产生较大的影响,但访问存量成员减少的开销可以明显提高应用程序的运行效率。
下面结合应用示例说明本申请的生成字节码文件的方法和加载和运行应用程序的方法可以解决本申请提出的技术问题。以图5、图9和图11a中的示例为例。
假设对应用程序进行编译时,应用程序对应的对象布局如图5所示。之后类库中的类代码更新了子版本,终端设备在启动应用程序时,加载更新后的字节码文件,并根据更新后的类的成员布局在内存中开辟内存空间实例化为对象,对象布局如图9所示。应用程序在运行时,根据旧版本的类库编译的应用程序访问成员时将会出错。结合图5和图9,在访问类A 更新子版本之前的类成员时还不会出错,但是当访问类B的至少部分类成员时计算得到的地址的存储空间存储了类A的增量成员,所以访问会出错。对于类库类代码更新子版本时删除了类的实例成员的情况,也是类似的,由于是根据新的布局进行实例化,但未重新编译的应用程序中成员的偏移量并没有随之改变。
而根据本申请的实施方式可以避免出现上述二进制兼容的问题。终端设备在启动应用程序时,根据本申请实施例的加载和运行应用程序的方法进行类加载和对象实例化,得到的对象布局如图11a所示。
在类加载时,由于增量布局不改变存量布局,而且子版本的存量布局与主版本的存量布局相同,因此,对于存量成员来讲,在启动应用程序进行类加载时不需要重新计算存量成员的偏移量,对于增量成员,根据本申请不同的实施方式,可以不需要重新计算偏移量或者也可以更新偏移量表。但由于不需要计算存量成员的偏移量,降低应用程序启动时的计算量,加快应用程序启动速度。
在访问存量成员时,由于根据本申请的实施例不允许删除类的成员,发布类库子版本时,即使删除了类的成员,编译器强制保留删除的类成员的布局(位置也不变),因此,在对对象进行实例化时,开辟的内存空间中仍然保留了删除的成员的实例成员的存储空间,增量成员也不改变存量成员的布局。所以访问存量成员时是不会出错的,可以解决二进制兼容的问题。
应用程序运行时,访问存量成员不需要读取间接表,相比于“间接表”的方式不需要额外付出一次读取内存的开销,提高了运行效率。
在访问增量成员时,终端设备通过更新偏移量表的方式或者直接执行二进制可执行程序中的第一访问指令,都可以解决二进制兼容的问题。在访问增量成员时,同样的由于根据本申请的实施例不允许删除类的成员,这里的不允许删除类成员包括不允许删除增量成员,也就是说,在发布类库子版本时,若删除了增量成员,编译器会强制保留删除的增量成员的布局,因此,对于任意一个增量成员来说,增量成员的第一偏移量是不会发生变化的,也就是在增量成员增加时进行计算得到的第一偏移量在以后的发布了类库子版本时仍然不变。
如图11a所示,如果又发布了类代码的新的子版本,新的子版本中类A删除了增量成员 A10,但是编译器会强制保留增量成员A10,这样,A11的第一偏移量不会发生改变。
虽然增加类的实例成员会对对象大小和增量成员所属类的反向偏移量产生影响,但对象的基地址、对象大小和增量成员所属的类的反向偏移量都是在应该程序启动时类加载的过程中确定的,因此,也不会导致访问增量成员出错。
举例来说,如图11a所示,在访问增量成员A11时,计算A11存储的地址的方式为obj+OBs-offset1-offset2,其中,obj为对象实例化时确定的,OBs是类加载时计算的对象大小,包括新增的增量成员在内,offset1的实际值与编译时相同,offset2为0。因此,访问增量成员时也不会出错,可以解决二进制兼容的问题。
终端设备直接执行第一访问指令根据基地址、对象大小、增量成员所属类的反向偏移量、以及增量成员的第一偏移量计算增量成员的起始地址访问增量成员的方式,使得类加载时也不需要重新计算增量成员的偏移量,可以进一步减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,从进一步缓解了应用程序启动时的卡顿。
应用示例
为了更有助于理解本申请,下面结合具体的应用示例进行说明。
类库中类代码的主版本中包括父类Parent和用户自定义类Child,
父类Parent代码为:
假设实例成员变量father的大小为4H,根据本申请的实施例对类库类代码的主版本进行编译时,计算类Child的存量成员的偏移量得到的类的成员的布局如下表所示:
father | 0 |
boy | 4H |
基于此主版本类代码编译的应用程序中,类Child派生于Parent,该Child类实例化之后得到的对象的布局如上表所示。其中,father是继承得到的成员变量,boy是自定义的成员变量。“boy”在高地址段,father和boy的偏移量,都可以在编译时确定。father的偏移量为0, boy的偏移量可以为4H,对成员变量father和boy的访问方法都是:obj(对象的基地址)+ 偏移量。
父类Parent代码从v1.0版本升级到v1.1版本后为:
增加了类的实例成员变量mother,根据本申请的实施例,将父类Parent前一版本的实例成员的信息与升级后版本的类代码比较,可以确定存在一个增量成员mother,增量成员mother 的第一偏移量为4。对父类Parent升级版本后的类代码进行编译生成字节码,例如编译上述的函数GetMother()可以生成访问增量成员mother的第一访问指令,访问增量成员mother的第一访问指令可以为:
ldr R1,this.ObjectSize//获取对象Child的大小
sub R1,R1,Parent_offset//减去类Parent的反向偏移量
sub R1,R1,4H//再减去第一偏移量
ldr R0,[R4+R1]//加上基地址R4得到成员变量mother存储的起始地址
假设father、mother和boy的大小都为4H,应用程序再次启动时,先加载父类Parent的字节码文件到内存中,并计算得到对象大小为8H,父类Parent的反向偏移量为0。然后加载类Child的字节码文件,计算得到对象大小为12H。在内存中开辟12H大小的内存空间并分配基地址,内存空间中对象的布局如下表所示:
father | 0H↓ |
boy | 4H↓ |
mother | 4H↑ |
其中,箭头表示的意思为成员的偏移量参照的起始地址,其中,father和boy都是相对于对象的基地址的偏移量,mother是相对于增量布局的起始地址的偏移量。
father的偏移量为0,boy的偏移量为4H,对存量成员变量father和boy的访问方法都是:obj+偏移量。举例来说,通过GetBoy()访问存量成员变量boy时,通过基地址加上boy的偏移量4H即可得到成员变量boy的起始地址。
mother的第一偏移量为4H,父类Parent的反向偏移量为0,若执行以上第一访问指令访问增量成员mother,计算得到mother的起始地址为R4+(12H-0H-4H)=R4+8H。
根据以上过程可知,根据本申请的实施例,既可以解决二进制兼容的问题,又不需要像“间接表”或者modern runtime技术中一样在运行之前重新计算成员的偏移量,可以减少由于类加载的过程进行偏移量的重新计算导致的计算资源消耗,从而缓解了应用程序启动时的卡顿。
在一种可能的实现方式中,本申请还提供了一种字节码文件,所述文件为通过编译器对类库中的类代码进行编译生成的,所述字节码文件中包括所述字节码文件对应的类的实例成员的信息,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
由于实例成员分为存量布局和增量布局,子版本的存量布局与主版本的存量布局相同,子版本的增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
在一种可能的实现方式中,上述字节码文件可以为类代码的子版本的字节码文件,所述实例成员的增量布局信息中包括类代码的子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。其中,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
在一种可能的实现方式中,所述字节码文件中还包括用于访问增量成员的第一访问指令,所述第一访问指令用于根据对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时,由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
在一种可能的实现方式中,所述第一访问指令中包括用于访问偏移量表的第二访问指令,所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
在一种可能的实现方式中,本申请还提供了一种加载和运行应用程序的装置,应用于终端设备。图13示出根据本申请一实施例的加载和运行应用程序的装置的框图,如图13所示,所述装置可以包括:
加载模块91,用于加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
对象创建模块92,用于根据所述对象大小创建所述类对应的对象。
在一种可能的实现方式中,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。所述存量布局中包括每个类的主版本的存量成员,所述增量布局中包括每个子版本相对于所述每个类对应的主版本增加的增量成员,其中,所述主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
由于子版本的实例成员分为存量布局和增量布局,子版本的存量布局与主版本的存量布局相同,子版本的增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
在一种可能的实现方式中,所述加载模块还用于根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量,所述反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
在一种可能的实现方式中,所述装置还包括:
访问模块,用于根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
在一种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从高地址到低地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属的类的反向偏移量。
在一种可能的实现方式中,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从低地址到高地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset2+offset1;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1 表示所述第一偏移量,offset2表示所述增量成员所属类的反向偏移量。
在一种可能的实现方式中,所述加载模块还用于:
根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表,
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
在一种可能的实现方式中,所述访问模块还用于:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset3 表示所述增量成员的第二偏移量。
在一种可能的实现方式中,本申请还提供了一种编译装置,应用于编译器。所述编译装置包括:
编译模块,用于若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件;
所述字节码文件中包括所述类代码的新版本的实例成员的信息,若所述新版本是类库中类代码的主版本的子版本,则所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
由于子版本的实例成员分为存量布局和增量布局,子版本的存量布局与主版本的存量布局相同,子版本的增量布局不改变存量布局、也不允许删除存量成员,存量布局不变。因此,即使类代码更新了子版本,对于存量成员来讲,不管是在编译时,还是在启动应用程序进行类加载时都不需要重新计算存量成员的偏移量。降低应用程序启动时的计算量,加快应用程序启动速度,同时还提高了编译器编译的效率。
另外,由于不改变存量布局,也就是说,即使父类的类代码更新了子版本,也不影响根据更新之前的版本编译的应该程序在运行时访问存量成员,能够解决二进制兼容的技术问题。对于增量成员,仍然可以采用类加载时对增量成员的偏移量进行初始化的方式来解决访问增量成员的二进制兼容的问题,但如上所述,由于不需要对存量成员的偏移量进行初始化,降低应用程序启动时的计算量,加快应用程序启动速度。另外,由于不改变存量布局,因此访问存量成员时也不需要采用先访问间接表的方式获取存量成员的偏移量,不需要额外读取内存,提高访问效率,应用程序的运行效率更高。
在一种可能的实现方式中,编译模块包括:
第一编译单元,用于若所述新版本是类库中类代码的主版本的子版本,则根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成所述子版本的字节码文件,其中,所述类代码的子版本和所述前一版本都是基于类代码的同一个主版本发布的。
结合第五方面的第一种可能的实现方式,在第二种可能的实现方式中,所述实例成员的增量布局信息中包括子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
在一种可能的实现方式中,第一编译单元还用于:加载所述前一版本的实例成员的信息;
比较所述类代码的子版本和前一版本的实例成员的信息以判断所述子版本相对于前一版本是否存在实例成员为增量成员;
若所述子版本相对于前一版本存在增量成员,则计算所述子版本相对于前一版本的增量成员的第一偏移量,并将前一版本的实例成员的信息和所述第一偏移量保存到所述子版本的实例成员的信息。
在一种可能的实现方式中,第一编译单元还用于:若所述子版本相对于前一版本删除了实例成员,则在所述子版本的实例成员的信息中保留被删除了的实例成员的信息。
在一种可能的实现方式中,第一编译单元还用于生成用于访问增量成员的第一访问指令,
所述第一访问指令用于根据应用程序的类对应的对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述类对应的对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
在一种可能的实现方式中,所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
在一种可能的实现方式中,编译模块还包括:
第二编译单元,用于若所述新版本是类库中类代码的主版本,对所述类代码的主版本进行编译生成主版本的字节码文件,其中,所述主版本的字节码文件中包括主版本的实例成员的存量布局信息。
在一种可能的实现方式中,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
本申请可以是系统、方法和/或计算机程序产品。计算机程序产品可以包括计算机可读存储介质,其上载有用于使处理器实现本申请的各个方面的计算机可读程序指令。
计算机可读存储介质可以是可以保持和存储由指令执行设备使用的指令的有形设备。计算机可读存储介质例如可以是――但不限于――电存储设备、磁存储设备、光存储设备、电磁存储设备、半导体存储设备或者上述的任意合适的组合。计算机可读存储介质的更具体的例子(非穷举的列表)包括:便携式计算机盘、硬盘、随机存取存储器(RAM)、只读存储器(ROM)、可擦式可编程只读存储器(EPROM或闪存)、静态随机存取存储器(SRAM)、便携式压缩盘只读存储器(CD-ROM)、数字多功能盘(DVD)、记忆棒、软盘、机械编码设备、例如其上存储有指令的打孔卡或凹槽内凸起结构、以及上述的任意合适的组合。这里所使用的计算机可读存储介质不被解释为瞬时信号本身,诸如无线电波或者其他自由传播的电磁波、通过波导或其他传输媒介传播的电磁波(例如,通过光纤电缆的光脉冲)、或者通过电线传输的电信号。
这里所描述的计算机可读程序指令可以从计算机可读存储介质下载到各个计算/处理设备,或者通过网络、例如因特网、局域网、广域网和/或无线网下载到外部计算机或外部存储设备。网络可以包括铜传输电缆、光纤传输、无线传输、路由器、防火墙、交换机、网关计算机和/或边缘服务器。每个计算/处理设备中的网络适配卡或者网络接口从网络接收计算机可读程序指令,并转发该计算机可读程序指令,以供存储在各个计算/处理设备中的计算机可读存储介质中。
用于执行本申请操作的计算机程序指令可以是汇编指令、指令集架构(ISA)指令、机器指令、机器相关指令、微代码、固件指令、状态设置数据、或者以一种或多种编程语言的任意组合编写的源代码或目标代码,所述编程语言包括面向对象的编程语言—诸如Smalltalk、 C++等,以及常规的过程式编程语言—诸如“C”语言或类似的编程语言。计算机可读程序指令可以完全地在用户计算机上执行、部分地在用户计算机上执行、作为一个独立的软件包执行、部分在用户计算机上部分在远程计算机上执行、或者完全在远程计算机或服务器上执行。在涉及远程计算机的情形中,远程计算机可以通过任意种类的网络—包括局域网(LAN)或广域网(WAN)—连接到用户计算机,或者,可以连接到外部计算机(例如利用因特网服务提供商来通过因特网连接)。在一些实施例中,通过利用计算机可读程序指令的状态信息来个性化定制电子电路,例如可编程逻辑电路、现场可编程门阵列(FPGA)或可编程逻辑阵列(PLA),该电子电路可以执行计算机可读程序指令,从而实现本申请的各个方面。
这里参照根据本申请实施例的方法、装置(系统)和计算机程序产品的流程图和/或框图描述了本申请的各个方面。应当理解,流程图和/或框图的每个方框以及流程图和/或框图中各方框的组合,都可以由计算机可读程序指令实现。
这些计算机可读程序指令可以提供给通用计算机、专用计算机或其它可编程数据处理装置的处理器,从而生产出一种机器,使得这些指令在通过计算机或其它可编程数据处理装置的处理器执行时,产生了实现流程图和/或框图中的一个或多个方框中规定的功能/动作的装置。也可以把这些计算机可读程序指令存储在计算机可读存储介质中,这些指令使得计算机、可编程数据处理装置和/或其他设备以特定方式工作,从而,存储有指令的计算机可读介质则包括一个制造品,其包括实现流程图和/或框图中的一个或多个方框中规定的功能/动作的各个方面的指令。
也可以把计算机可读程序指令加载到计算机、其它可编程数据处理装置、或其它设备上,使得在计算机、其它可编程数据处理装置或其它设备上执行一系列操作步骤,以产生计算机实现的过程,从而使得在计算机、其它可编程数据处理装置、或其它设备上执行的指令实现流程图和/或框图中的一个或多个方框中规定的功能/动作。
附图中的流程图和框图显示了根据本申请的多个实施例的系统、方法和计算机程序产品的可能实现的体系架构、功能和操作。在这点上,流程图或框图中的每个方框可以代表一个模块、程序段或指令的一部分,所述模块、程序段或指令的一部分包含一个或多个用于实现规定的逻辑功能的可执行指令。在有些作为替换的实现中,方框中所标注的功能也可以以不同于附图中所标注的顺序发生。例如,两个连续的方框实际上可以基本并行地执行,它们有时也可以按相反的顺序执行,这依所涉及的功能而定。也要注意的是,框图和/或流程图中的每个方框、以及框图和/或流程图中的方框的组合,可以用执行规定的功能或动作的专用的基于硬件的系统来实现,或者可以用专用硬件与计算机指令的组合来实现。
以上已经描述了本申请的各实施例,上述说明是示例性的,并非穷尽性的,并且也不限于所披露的各实施例。在不偏离所说明的各实施例的范围和精神的情况下,对于本技术领域的普通技术人员来说许多修改和变更都是显而易见的。本文中所用术语的选择,旨在最好地解释各实施例的原理、实际应用或对市场中的技术的技术改进,或者使本技术领域的其它普通技术人员能理解本文披露的各实施例。
Claims (34)
1.一种加载和运行应用程序的方法,应用于终端设备,其特征在于,所述方法包括:
加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
根据所述对象大小创建所述类对应的对象。
2.根据权利要求1所述的方法,其特征在于,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。
3.根据权利要求1或2所述的方法,其特征在于,加载所述应用程序的类还包括:
根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量,所述类的反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
4.根据权利要求3所述的方法,其特征在于,所述方法还包括:
根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
5.根据权利要求4所述的方法,其特征在于,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从高地址到低地排布,
所述根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的地址,obj表示所述基地址,OBs表示所述对象大小,offset1表示所述第一偏移量,offset2表示所述增量成员所属的类的反向偏移量。
6.根据权利要求4所述的方法,其特征在于,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从低地址到高地排布,
所述根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset2+offset1;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1表示所述第一偏移量,offset2表示所述增量成员所属类的反向偏移量。
7.根据权利要求1-4任意一项所述的方法,其特征在于,所述加载应用程序的类还包括:
根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表,
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
8.根据权利要求7所述的方法,其特征在于,根据所述对象的基地址、所述对象大小、所述增量成员所属类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,包括:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3;
其中,add表示增量成员的起始地址,obj表示对象的基地址,OBs表示所述对象大小,offset3表示所述增量成员的第二偏移量。
9.根据权利要求2所述的方法,其特征在于,
所述存量布局中包括每个类的主版本的存量成员,所述增量布局中包括每个类的子版本相对于所述每个类的主版本增加的增量成员,其中,所述主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
10.一种生成字节码文件的方法,其特征在于,所述方法应用于编译器,所述方法包括:
若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件;
所述字节码文件中包括所述类代码的新版本的实例成员的信息,若所述新版本是类库中类代码的主版本的子版本,则所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
11.根据权利要求10所述的方法,其特征在于,若发布了类库中类代码的新版本,则对所述类代码的新版本进行编译生成所述新版本的字节码文件,包括:
若所述新版本是类库中类代码的主版本的子版本,则根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成所述子版本的字节码文件,其中,所述类代码的子版本和所述前一版本都是基于类代码的同一个主版本发布的。
12.根据权利要求11所述的方法,其特征在于,
所述实例成员的增量布局信息中包括子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
13.根据权利要求12所述的方法,其特征在于,根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成子版本的字节码文件,包括:
加载所述前一版本的实例成员的信息;
比较所述类代码的子版本和前一版本的实例成员的信息以判断所述子版本相对于前一版本是否存在实例成员为增量成员;
若所述子版本相对于前一版本存在增量成员,则计算所述子版本相对于前一版本的增量成员的第一偏移量,并将前一版本的实例成员的信息和所述第一偏移量保存到所述子版本的实例成员的信息。
14.根据权利要求13所述的方法,其特征在于,根据所述子版本的前一版本的实例成员的信息对所述子版本进行编译生成子版本的字节码文件,还包括:
若所述子版本相对于前一版本删除了实例成员,则在所述子版本的实例成员的信息中保留被删除了的实例成员的信息。
15.根据权利要求11-14任意一项所述的方法,其特征在于,对所述子版本进行编译生成子版本的字节码文件,包括:生成用于访问增量成员的第一访问指令,
所述第一访问指令用于根据应用程序的类对应的对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述类对应的对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
16.根据权利要求15所述的方法,其特征在于,所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
17.根据权利要求10-16任意一项所述的方法,其特征在于,若发布了类库中类代码的新版本,则对所述新版本的类代码进行编译生成字节码文件还包括:
若所述新版本是类库中类代码的主版本,对所述类代码的主版本进行编译生成主版本的字节码文件,其中,所述主版本的字节码文件中包括主版本的实例成员的存量布局信息。
18.根据权利要求10-16任意一项所述的方法,其特征在于,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
19.一种字节码文件,所述文件为通过编译器对类库中的类代码进行编译生成的,其特征在于,
所述字节码文件中包括所述字节码文件对应的类的实例成员的信息,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息。
20.根据权利要求19所述的字节码文件,其特征在于,所述实例成员的增量布局信息中包括类代码的子版本相对于主版本增加的增量成员的第一偏移量,所述第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
21.根据权利要求20所述的字节码文件,其特征在于,
所述字节码文件中还包括用于访问增量成员的第一访问指令,所述第一访问指令用于根据对象的基地址、对象大小、增量成员所属的类的反向偏移量以及增量成员的第一偏移量访问增量成员,
其中,所述对象大小、增量成员所属的类的反向偏移量是在所述字节码文件被终端设备加载时,由终端设备根据所述类以及所述类的祖先类的实例成员的信息确定的,所述类的反向偏移量用于表示在所述对象的增量布局中所述类的起始地址相对于所述对象的结束地址的偏移量,
所述对象的基地址在终端设备创建所述对象时由终端设备确定的。
22.根据权利要求21所述的字节码文件,其特征在于,
所述第一访问指令中包括用于访问偏移量表的第二访问指令,
所述第一访问指令用于根据所述基地址、所述对象大小和基于第二访问指令访问偏移量表得到的增量成员的第二偏移量访问增量成员,
其中,增量成员的所述第二偏移量为所述字节码文件被终端设备加载时,由终端设备在加载应用程序的类时根据增量成员的第一偏移量、增量成员所属的类的反向偏移量计算得到并保存在所述偏移量表中的。
23.根据权利要求19-22任意一项所述的字节码文件,其特征在于,主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
24.一种加载和运行应用程序的装置,应用于终端设备,其特征在于,所述装置包括:
加载模块,用于加载所述应用程序的类时,根据所述类及所述类的祖先类的实例成员的信息计算得到所述类对应的对象大小,其中,所述实例成员的信息包括实例成员的存量布局信息和增量布局信息;
对象创建模块,用于根据所述对象大小创建所述类对应的对象。
25.根据权利要求24所述的装置,其特征在于,在所述对象的对象布局中,从所述对象的基地址开始按照如下顺序连续排布:先是按照继承链从祖先类依次到所述类的存量布局,然后是按照继承链从所述类依次到所述祖先类的增量布局。
26.根据权利要求24或25所述的装置,其特征在于,所述加载模块还用于根据所述增量布局信息计算得到所述对象的增量布局中每个类的反向偏移量,所述反向偏移量用于表示在所述增量布局中每个类的起始地址相对于所述对象的结束地址的偏移量。
27.根据权利要求26所述的装置,其特征在于,所述装置还包括:
访问模块,用于根据所述对象的基地址、所述对象大小、所述增量成员所属的类的反向偏移量以及所述增量成员的第一偏移量计算所述增量成员的地址,以访问所述增量成员;
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
28.根据权利要求27所述的装置,其特征在于,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从高地址到低地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset1-offset2;
其中,add表示增量成员的地址,obj表示所述基地址,OBs表示所述对象大小,offset1表示所述第一偏移量,offset2表示所述增量成员所属的类的反向偏移量。
29.根据权利要求27所述的装置,其特征在于,所述对象的基地址为低地址端,在所述类的增量布局内部,增量成员按照增加的时间顺序从低地址到高地排布,
所述访问模块还用于:
根据以下公式计算增量成员的地址,
add=obj+OBs-offset2+offset1;
其中,add表示增量成员的起始地址,obj表示所述基地址,OBs表示所述对象大小,offset1表示所述第一偏移量,offset2表示所述增量成员所属类的反向偏移量。
30.根据权利要求24-27任意一项所述的装置,其特征在于,所述加载模块还用于:
根据增量成员的第一偏移量和所述增量成员所属类的反向偏移量计算所述增量成员的第二偏移量,并将所述增量成员的第二偏移量保存至偏移量表,
其中,所述增量成员的第一偏移量是指所述增量成员的地址相对于所述增量成员所属类的增量布局的起始地址的偏移量。
31.根据权利要求30所述的装置,其特征在于,所述访问模块还用于:
从所述偏移量表中获取所述增量成员的第二偏移量;
根据以下公式计算所述增量成员的地址:
add=obj+OBs-offset3;
其中,add表示增量成员的起始地址,obj表示对象的基地址,OBs表示所述对象大小,offset3表示所述增量成员的第二偏移量。
32.根据权利要求25所述的装置,其特征在于,
所述存量布局中包括每个类的主版本的存量成员,所述增量布局中包括每个子版本相对于所述每个类对应的主版本增加的增量成员,其中,所述主版本发布后不再兼容根据主版本之前的版本类代码编译的应用程序,子版本发布后兼容根据所述主版本或者所述主版本的任一子版本的类代码编译的应用程序。
33.一种终端设备,其特征在于,包括:
处理器;
用于存储字节码文件的存储器;
其中,所述处理器被配置为执行所述字节码文件时实现权利要求1至9任意一项所述的方法。
34.一种非易失性计算机可读存储介质,其上存储有计算机程序指令,其特征在于,所述计算机程序指令被处理器执行时实现权利要求1至9中任意一项所述的方法。
Priority Applications (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202010294919.1A CN113535137A (zh) | 2020-04-15 | 2020-04-15 | 加载和运行应用程序的方法、装置及相关产品 |
Applications Claiming Priority (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202010294919.1A CN113535137A (zh) | 2020-04-15 | 2020-04-15 | 加载和运行应用程序的方法、装置及相关产品 |
Publications (1)
Publication Number | Publication Date |
---|---|
CN113535137A true CN113535137A (zh) | 2021-10-22 |
Family
ID=78088259
Family Applications (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN202010294919.1A Pending CN113535137A (zh) | 2020-04-15 | 2020-04-15 | 加载和运行应用程序的方法、装置及相关产品 |
Country Status (1)
Country | Link |
---|---|
CN (1) | CN113535137A (zh) |
-
2020
- 2020-04-15 CN CN202010294919.1A patent/CN113535137A/zh active Pending
Similar Documents
Publication | Publication Date | Title |
---|---|---|
JP2023514631A (ja) | インタフェースレイアウト方法、装置、及び、システム | |
US12061885B2 (en) | Cross-language compilation method and device | |
CN112612386B (zh) | 移动终端及其应用卡片的显示方法 | |
CN113867848A (zh) | 图形接口的调用方法、装置、设备及可读存储介质 | |
CN114327437A (zh) | 插件运行系统、插件运行方法以及电子设备 | |
CN116166256A (zh) | 界面生成方法及电子设备 | |
WO2023005751A1 (zh) | 渲染方法及电子设备 | |
CN116166259A (zh) | 界面生成方法及电子设备 | |
CN116166255B (zh) | 界面生成方法及电子设备 | |
CN112035153B (zh) | 应用更新方法、装置、终端及存储介质 | |
CN113535137A (zh) | 加载和运行应用程序的方法、装置及相关产品 | |
EP4343533A1 (en) | Screen projection method and related apparatus | |
KR20130012603A (ko) | 복수의 운영체제와 호환 가능한 가상머신 생성 방법 및 가상머신 프로그램을 저장한 기록매체 | |
CN107402749A (zh) | 实现图片加载库的方法及装置 | |
CN115994006A (zh) | 动画效果显示方法及电子设备 | |
CN116166257A (zh) | 界面生成方法及电子设备 | |
EP4216052A1 (en) | Method for developing mvvm architecture-based application, and terminal | |
CN114610417A (zh) | 接口调用方法、装置及存储介质 | |
CN116700740B (zh) | 软件修复方法和相关装置 | |
WO2024066976A1 (zh) | 控件显示方法及电子设备 | |
CN115543334B (zh) | 编译优化方法及电子设备 | |
CN117707453B (zh) | 一种节点信息的读取方法、设备及存储介质 | |
US20230376289A1 (en) | Method and apparatus for implementing batch system call | |
WO2024193215A1 (zh) | 一种自适应应用页面显示方法及设备 | |
CN118210496A (zh) | 一种跨语言交互方法及相关装置 |
Legal Events
Date | Code | Title | Description |
---|---|---|---|
PB01 | Publication | ||
PB01 | Publication | ||
SE01 | Entry into force of request for substantive examination | ||
SE01 | Entry into force of request for substantive examination |