ART虚拟机 | 接口方法调用的具体实现

Java语言中,一个新创建的类只能继承一个父类,但是可以实现多个接口。这两种不同的语言特性使得多态在虚拟机中的实现也不相同。具体而言,当我们调用virtual方法时,可以使用对象所属类的virtual table进行派发,其中的元素为ArtMethod。父类方法在前,子类方法拼接在后,因此不论经过多少次继承,每个方法在vtable中的偏移都是固定的。所谓的覆盖(override),无非就是替换掉父类方法所在位置的指针值,使其指向子类实现的新的ArtMethod。那么调用接口方法呢?当一个类实现多个接口时,我们又该用怎样的数据结构进行派发呢?

先看一个具体的例子。

protected static final void checkOffset(int offset, CharacterIterator text) {if (offset < text.getBeginIndex() || offset > text.getEndIndex()) {throw new IllegalArgumentException("offset out of bounds");}
}public interface CharacterIterator extends Cloneable {...
}

传入的参数text为接口类型,它的真实类型一定是某个实现了CharacterIterator的类。此外,调用的具体方法为CharacterIterator.getBeginIndex()。因此,ART在调用发生前可以知道CharacterIterator.getBeginIndex()对应的ArtMethod(注意是接口的ArtMethod,而不是子类最终实现的ArtMethod),以及text这个对象。方法派发的目的就是根据text和CharacterIterator.getBeginIndex()的ArtMethod找到子类最终实现的ArtMethod,从而跳转过去。这句话很重要,值得全体复诵。

回到开头那个问题:当一个类实现多个接口时,我们又该用怎样的数据结构进行方法派发呢?一个自然的想法是:既然一个类可以实现多个接口,那为何不为每个接口创建一个单独的vtable?然后通过二级列表的方式进行派发?事实上,ART也是这么做的。

在这里插入图片描述

art::mirror::Class中有一个字段名为iftable_,它是一个用HeapReference封装的指针,指向一个art::mirror::IfTable对象。这里的If指的便是interface。IfTable中每个接口占用两个slot,第一个slot存储的是该接口对应的art::mirror::Class指针,第二个slot存储的是一个Array指针,其中存储了一系列ArtMethod*,表示该接口中方法的具体实现。当ART采用这种方式进行方法派发时,它会做如下几步:

  1. 通过text这样的art::mirror::Object拿到它的类指针,Class*存储在Object的第一个字段klass_中。
  2. 通过CharacterIterator.getBeginIndex()这样的ArtMethod拿到它的类指针,Class*存储在ArtMethod的第一个字段declaring_class_中。
  3. 找到对象所属Class的IfTable,遍历其中的接口,判断它和第二步拿到的接口方法的类是否相等,从而取出接口所对应的Method Array。
  4. 将接口方法中的method_index_字段作为index,找出Method Array中对应位置的ArtMethod*。

如此一来便可以找到子类中最终实现的接口方法。

属于Class的IfTable会在类加载的时候创建并填充,具体在LinkMethods阶段。值得注意的是,一个类所实现的接口包含以下三种含义,其中重复的接口会在填充时被去重。

  1. 该类直接implements的所有接口。
  2. 上述接口所继承的所有接口。(super-interface)
  3. 父类所实现的所有接口。(superclass’s interfaces)

这种派发方式较为简单,且IfTable包含了所有的派发信息。但是它的性能并不好,尤其是当一个类实现了很多接口时。因此,ART只会迫不得已才fallback到这种方式。这个概念有点像CPU的多级缓存,L0、L1虽然速度快,但是存储的信息少,当无法满足访问要求时,CPU将不得不从L2、L3甚至主存中读取信息。这里我们将IfTable类比为L2 Cache,那么ART中接口方法派发的“L0 Cache”、“L1 Cache”又在哪里呢?

下面我们引入了一个新的概念:IMTable,它的全称是interface method table。通过该结构进行接口方法派发的方式首次发表在2001年的论文《Efficient Implementation of Java Interfaces: Invokeinterface Considered Harmless》1中。这种方式综合考虑了内存占用和性能开销,做到了两方面的平衡和高效,因此一直沿用至今。

在这里插入图片描述

IMTable是一个长度固定为43的数组,其中的元素是ArtMethod*。当我们拿到一个接口方法时,可以将它里面的特殊字段imt_index_作为index,从而找到数组中对应位置的ArtMethod*。之所以说imt_index_特殊,是因为ART只会在加载抽象方法时才给它赋值,而invoke-interface的那些接口方法恰好属于抽象方法。

imt_index_的计算方式如下,通过类名、方法名和签名综合计算出一个哈希值,再用哈希值余上43,得到[0,42]范围内的值。

// Magic configuration that minimizes some common runtime calls.
static constexpr uint32_t kImTableHashCoefficientClass = 427;
static constexpr uint32_t kImTableHashCoefficientName = 16;
static constexpr uint32_t kImTableHashCoefficientSignature = 14;mixed_hash = kImTableHashCoefficientClass * class_hash + kImTableHashCoefficientName * name_hash + kImTableHashCoefficientSignature * signature_hash;
imt_index_ = mixed_hash % ImTable::kSize;

至于IMTable的长度为什么取43,以及三个哈希值的系数为什么这么取,我并没有准确的答案。但我有一个猜测:它们的最终目的是为了让不同接口方法计算出的imt_index_在[0,42]范围内均匀分布,或者更加智能一些,让频繁调用的接口方法独占某些序号,让不频繁的接口方法共享一些序号。至于原因,后面会给出。

和IfTable相同,IMTable也会在类加载的过程中创建并填充。填充结束的IMTable中会存在三种类型的ArtMethod*:

  1. Unimplemented method,表示没有接口方法的imt_index_等于这个序号,对应上图的白色。
  2. Implemented method,表示只有一个接口方法的imt_index_等于这个序号,对应上图的绿色。
  3. Conflict method,表示有多个(≥2)接口方法的imt_index_等于这个序号,这种情况称为“碰撞”,或者“冲突”。对应上图的紫色。

当我们拿着接口方法的imt_index_作为序号去寻找时,如果找到implemented method,那么也就找到了最终的实现。可是如果找到的是Conflict method,那么还得去解冲突。Implemented method和Conflict method都是一个ArtMethod对象,作为调用方而言,它从IMTable中拿回一个ArtMethod可不会管它是什么类型,而是直接跳转到它的entry_point_from_quick_compiled_code_去。区别在于,Implemented method的entry_point_from_quick_compiled_code_指向最终实现的函数入口,而Conflict method的entry_point_from_quick_compiled_code_指向一个Conflict Resolution Function。

解冲突需要一个新的数据结构:ImtConflictTable。在这个结构里,每个接口方法和它的最终实现方法组成一对,顺次排列。解冲突的过程就是拿着接口方法的指针遍历ImtConflictTable,从而找出最终实现方法。

ImtConflictTable中的数据并非在类加载过程中填充,而是在第一次方法调用时填充,这个概念有点类似于"lazy load"。当第一次调用发生时,ImtConflictTable中搜索不到对应的接口方法,ART会选择fallback到IfTable中,根据接口方法的类型,选择对应的method array并找出最终实现。之后再将接口方法和找到的实现方法填入到ImtConflictTable中,方便下次寻找。

至此,整个接口方法的调用过程便阐述完毕。回到三级缓存的比喻中,IMTable相当于L0 Cache,ImtConflictTable相当于L1 Cache,而IfTable则相当于L2 Cache。此外,IMTable和IfTable中的数据在类加载的过程中填充完毕,而ImtConflictTable中的数据则等到第一次调用发生时才填充,属于lazy load。

下面我们通过一段Java代码和对应的汇编代码来验证上述分析。

[Java代码]

protected static final void checkOffset(int offset, CharacterIterator text) {if (offset < text.getBeginIndex() || offset > text.getEndIndex()) {throw new IllegalArgumentException("offset out of bounds");}
}public interface CharacterIterator extends Cloneable {...
}

[Dex字节码]

1: void java.text.IcuIteratorWrapper.checkOffset(int, java.text.CharacterIterator) (dex_method_idx=13041)DEX CODE:0x0000: 7210 b931 0300           	| invoke-interface {v3}, int java.text.CharacterIterator.getBeginIndex() // method@127290x0003: 0a00                     	| move-result v00x0004: 3402 0900                	| if-lt v2, v0, +90x0006: 7210 ba31 0300           	| invoke-interface {v3}, int java.text.CharacterIterator.getEndIndex() // method@12730

[text.getBeginIndex()的汇编代码,通过oatdump生成]

//checkOffset是静态方法,x0存储checkOffset方法对应的ArtMethod*,x1存储第一个参数offset,x2存储第二个参数text。这条指令将x2里的内容复制到x1中。
0x0022817c: aa0203e1	mov x1, x2
//将x1中的内容复制到x23中,其他地方会使用x23,不过与本话题无关。
0x00228180: aa0103f7	mov x23, x1
//参数text作为引用类型,传递的实际上是art::mirror::Object*,Object的第一个字段为art::mirror::Class*,也即该对象所属的类。因此这条指令执行后,w0里将存有art::mirror::Class*。之所以用32位的w0,而不是64位的x0,是因为Java Heap位于虚拟地址中的低位,32位地址表示的4G空间足够容纳Heap。
0x00228184: b9400020	ldr w0, [x1]
//adrp和add指令会计算出CharacterIterator.checkOffset方法所对应的ArtMethod的地址,注意是接口的方法,而不是继承接口的类中的方法。该地址位于boot.art范围内,表示该方法的内存结构在.art文件中被提前创建。
0x00228188: b0ffe051	adrp x17, #-0x3f7000 (addr -0x1cf000)
0x0022818c: 912ee231	add x17, x17, #0xbb8 (3000)
//art::mirror::Class中偏移128的位置存储的是(interface method table)IMTable*,该table长度固定,当前版本为43。
0x00228190: f9404000	ldr x0, [x0, #128]
//取出IMTable中第14个元素,序号由接口方法的名称哈希得出,偏移为14*8=112。取出的元素为ArtMethod*。
0x00228194: f9403800	ldr x0, [x0, #112]
//取出ArtMethod的字段entry_point_from_quick_compiled_code_,该字段存储方法的汇编入口。
0x00228198: f9400c1e	ldr lr, [x0, #24]
//跳转到目标方法的汇编入口处。
0x0022819c: d63f03c0	blr lr

前文我们提到,接口方法派发需要两个信息,一个是调用的对象,另一个是接口方法的ArtMethod。通过上面的汇编代码可知,调用对象可以通过参数传递,而ArtMethod则通过adrp和add指令计算得出。至于两条指令后面跟的偏移值为什么是这个数字,这又牵扯到ArtMethod的加载过程。对于大部分boot class,它们里面的ArtMethod会通过adrp定位到boo.art文件里。而对于大部分APP而言,它们的ArtMethod通常通过ODEX文件中的.bss段来寻找。至于具体细节,以后再写一篇文章详述吧。

// Determines how to load an ArtMethod*.
enum class MethodLoadKind {// Use a String init ArtMethod* loaded from Thread entrypoints.kStringInit,// Use the method's own ArtMethod* loaded by the register allocator.kRecursive,// Use PC-relative boot image ArtMethod* address that will be known at link time.// Used for boot image methods referenced by boot image code.kBootImageLinkTimePcRelative,// Load from an entry in the .data.bimg.rel.ro using a PC-relative load.// Used for app->boot calls with relocatable image.kBootImageRelRo,// Load from an entry in the .bss section using a PC-relative load.// Used for methods outside boot image referenced by AOT-compiled app and boot image code.kBssEntry,// Use ArtMethod* at a known address, embed the direct address in the code.// Used for for JIT-compiled calls.kJitDirectAddress,// Make a runtime call to resolve and call the method. This is the last-resort-kind// used when other kinds are unimplemented on a particular architecture.kRuntimeCall,
};

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/23262.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[MATLAB学习笔记]peaks函数1013(2)

>> Z peaksZ 1 至 10 列0.0001 0.0001 0.0002 0.0004 0.0007 0.0011 0.0017 0.0025 0.0034 0.00430.0001 0.0002 0.0004 0.0006 0.0010 0.0017 0.0026 0.0037 0.0051 0.00640.0002 0.0003 0.0005 0.000…

chatgpt赋能python:Python代码转为C语言——提高效率的必经之路

Python代码转为C语言——提高效率的必经之路 Python是一种高级编程语言&#xff0c;具有易学易用的优点&#xff0c;因此越来越多的程序员选择使用Python来开发应用程序和脚本。但是&#xff0c;在开发高性能应用程序时&#xff0c;Python的效率问题会成为拦路虎。因此&#x…

chatgpt赋能python:Python数据转换:介绍和使用方法

Python数据转换&#xff1a;介绍和使用方法 在Python编程中&#xff0c;数据转换是一项常用的操作&#xff0c;通过数据转换&#xff0c;可以把某种数据类型转换成其他数据类型。这对于处理数据和提高代码的可读性和可维护性都是非常有帮助的。在本文中&#xff0c;我们将介绍…

快讯 | 微软开源“傻瓜式”类ChatGPT模型训练工具;汤恩智能获数千万元A轮融资

一分钟速览新闻点 小米机器人公司成立&#xff0c;注册资本5000万元 国产移动机器人“大脑”杀出黑马 汤恩智能获数千万元A轮融资 微软开源“傻瓜式”类ChatGPT模型训练工具 俄制“马克”机器人将能发射多达100 架无人机 现代联手科研机构开发探月机器人&#xff0c;计划2…

自然语言模型的哲学小谈

近期&#xff0c;以chatGPT为代表的大语言模型表现非常惊艳。“In Context Learning”、“Instruct”1&#xff0c;以及推理能力&#xff0c;很难不让我们期待未来人工智能的发展&#xff0c;同时冷静思考一下为什么自然语言模型能够取得巨大进步。 文章目录 1 放空大脑从0开始…

从GPT-1到ChatGPT及最新的GPT-4,GPT系列技术的发展过程

从GPT-1到GPT4的技术发展过程 GPT-1&#xff1a;GPT-1 是 OpenAI 在 2018 年发布的第一个基于 Transformer 的预训练模型&#xff0c;采用了单向 Transformer 架构&#xff0c;包含了 12 层和 117M 个参数。GPT-1 可以用于生成文本、问答和文本分类等任务。 GPT-2&#xff1a;G…

HarmonyOS开发第一天 鸿蒙3.0环境搭建

一、下载与安装 下载地址&#xff1a;https://developer.harmonyos.com/cn/develop/deveco-studio/ 二、常见的问题解答 1、 解决办法&#xff1a;卸载node.js&#xff0c;再安装鸿蒙开发工具 或者安装deveco-studio-3.0.0.601版本&#xff0c;大家可以试试3.0.06的 2、工具栏…

华为鸿蒙系统HarmonyOS学习之二:鸿蒙HarmonyOS系统架构

华为鸿蒙系统HarmonyOS学习之二&#xff1a;鸿蒙HarmonyOS系统架构 鸿蒙HarmonyOS整体遵从分层的层次化设计&#xff0c;从下向上依次为&#xff1a;内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 功能/模块”逐级展开&#xff0c;在多设备部署场景…

HarmonyOS(鸿蒙)——单击事件

目录 一、简介 1.1 什么是组件 1.2 什么是事件 1.3 什么是单击事件 1.4 实现步骤 二、案例 2.1 创建项目 2.2 定义组件 2.3 定义的组件绑定单击事件 2.4 实现ClickedListener接口并重写onClick方法 2.5 实现onClick方法中的具体逻辑&#xff0c;以此完成点击事件的相…

【鸿蒙】HarmonyOS认证学习资料整理

第1章、HarmonyOS概述 概念 HarmonyOS是全场景分布式智慧系统。 HarmonyOS是一款面向万物互联时代、全新的分布式操作系统。 超级终端 功能机&#xff1a;软件整体升级不可分割&#xff0c;预装应用与操作系统绑定&#xff0c;有限功能 智能机&#xff1a;应用与操作系统分离…

HarmonyOS实战—鸿蒙OS在新能源领域发展前景

【本文正在参与“有奖征文 | HarmonyOS征文大赛”活动】 https://marketing.csdn.net/p/ad3879b53f4b8b31db27382b5fc65bbc 我的观点是肯定可以 华为也是这两年开启了鸿蒙系统的研发&#xff0c;其实单就操作系统&#xff0c;国内其实有很多&#xff0c;不过基本都是基于Linu…

鸿蒙系统概述(HarmonyOS)学习这一篇就够了!

鸿蒙系统概述&#xff08;HarmonyOS&#xff09; 我们可以从以下三个主要方面进行概述&#xff1a;系统定义、技术特征、系统安全。 目录 鸿蒙系统概述&#xff08;HarmonyOS&#xff09; 系统定义 系统定位 技术架构 内核层 系统服务层 框架层 应用层 技术特性 硬…

国内有哪些公司参与鸿蒙系统,终于有手机厂商接入鸿蒙系统了!

今天&#xff0c;微博“魅族智享生活”发布消息&#xff1a;你好&#xff0c;鸿蒙&#xff0c;一起拥抱全场景智能生活。业界纷纷认为&#xff0c;这代表魅族正式宣布接入鸿蒙系统。 打开APP&#xff0c;查看更多精彩图片 魅族手机沦为市场others&#xff0c;为了制造热度&…

(1.3)HarmonyOS鸿蒙启动程序运行流程

程序启动运行流程&#xff1a; ①解析config.json文件 ②初始化 ③获取入口Ability的全类名&#xff08;config.json里的module里的mainAbility&#xff09; ④找到Ability并运行 ⑤运行Ability中的子界面 ⑥加载xml文件&#xff0c;展示内容&#xff08;xml在resources里面&am…

鸿蒙应用开发培训笔记01:HarmonyOS介绍

文章目录 零、本讲学习目标一、鸿蒙系统理念与关键技术&#xff08;一&#xff09;智能终端产业飞速发展带来历史性机遇&#xff08;二&#xff09;HarmonyOS超级终端&#xff0c;带给消费者的不一样&#xff08;三&#xff09;HarmonyOS 开启万物互联时代的一把钥匙&#xff0…

OpenHarmony轻量系统开发【12】OneNET云接入

摘要&#xff1a;本文简单介绍如何接入OneNET云平台适合群体&#xff1a;适用于润和Hi3861开发板 文中所有代码仓库&#xff1a;OpenHarmony润和3861智能家居套件代码仓库: 润和3861智能家居套件代码仓库 12.1 OneNET云介绍 通常来说&#xff0c;一个物联网产品应当包括设备、…

【鸿蒙 HarmonyOS】UI 组件 ( 文本输入框 TextField 组件 )

文章目录 一、布局中设置 TextField 组件二、代码中获取并设置 TextField 组件 一、布局中设置 TextField 组件 TextField 组件是文本输入框 , 允许用户在界面中输入指定的文字信息 ; 布局文件中配置 TextField 组件 : <?xml version"1.0" encoding"utf-8…

【鸿蒙 HarmonyOS】HarmonyOS 开发环境搭建 ( 下载 | 安装 | 启动 | 支持的设备与开发语言 )

文章目录 一、HarmonyOS 开发环境下载二、HarmonyOS 开发环境安装三、启动 DevEco Studio四、HarmonyOS 支持的设备与开发语言 一、HarmonyOS 开发环境下载 HarmonyOS 开发者相关网站 : HarmonyOS 开发者官网 : https://developer.harmonyos.com/cn/home/HarmonyOS 开发文档 :…

harmonyos鸿蒙,HarmonyOS鸿蒙之设置圆角

1、同时设置四个角 a、静态设置 graphic下新建 background_supertext_red.xml ohos:shape="rectangle"> ohos:color="$color:white"/> ohos:width="2" ohos:color="$color:red"/> ohos:radius="20"/> layout.xm…

Appium:配置华为手机鸿蒙HarmonyOS系统参数

简介&#xff1a;安卓手机厂商林立造成的各系统占山为王&#xff0c;差异大&#xff0c;难以获取相关参数。HarmonyOS本质是基于安卓进行的封装优化。 获取方式有两种&#xff0c;推荐方式2 方式1&#xff1a; 鸿蒙安卓版本号&#xff1a;进入应用和服务 - 应用管理 - 搜索&…