变量句柄的制作方法

文档序号:11160873阅读:410来源:国知局
变量句柄的制造方法与工艺

实施例一般涉及用于在编程语言内支持和/或利用改进的存储器访问的技术。



背景技术:

在多处理器系统中,处理器常常具有一个或多个存储器高速缓存层,该一个或多个存储器高速缓存层通过加速对数据的访问和减少共享存储器总线上的业务二者来提高性能。然而,虽然存储器高速缓存可以大大提高性能,但是它们也提出了新的挑战。例如,检查同一存储器位置的两个处理器可能接收到不同的结果,因为一个处理器可能使用陈旧的(stale)高速缓存值,然而另一个处理器可以从主存储器拉取更新的值。此外,许多编译器和计算机架构重写代码,以优化执行。例如,处理器可以重写或重新排序代码,以利用存储在其高速缓存中的当前数据。然而,这些优化中的许多优化仅针对单个处理器/线程正在执行程序的情况确保一致的程序语义。因此,在多处理器/多线程环境中,重新排序可能导致非预期的行为和不一致的程序状态。例如,假如直到指令的原始程序索引才依赖变量,则计算机架构可能在最方便的时候提前执行加载/存储。然而,在多个线程或多个处理器的情况下,提前执行由其它线程依赖的操作可能导致否则将不可能遇到的状态。

附图说明

在附图的图中通过示例的方式而不是通过限制的方式示出本发明,在附图中相似的附图标记指代相似的元件并且其中:

图1是示出根据各种实施例的、在其中可以实现本文描述的某些技术的示例计算架构的逻辑框图。

图2是示出根据实施例的示例类文件的框图。

图3是示出根据实施例的用于虚拟机运行时环境的示例结构的框图。

图4是示出根据实施例的用于虚拟机栈上的帧的示例结构的框图。

图5示出了根据实施例的、以框图形式的、用于使用VarHandle执行原子操作的示例过程。

图6是示出适合于实现本文所描述的方法和特征的计算机系统的一个实施例的框图。

具体实施方式

在以下描述中,为了解释的目的,阐述了许多具体细节,以便提供对本发明的透彻理解。然而,将明显的是,可以在没有这些具体细节的情况下实践本发明。在其它实例中,以框图形式示出了公知的结构和设备,以避免不必要地使本发明模糊。

本文根据以下提纲来描述实施例:

1.0总体概述

1.1受防护的操作

1.2原子操作

1.3暴露受约束的操作

2.0示例操作架构

2.1示例类文件结构

2.2示例虚拟机架构

2.3加载、链接和初始化

3.0VarHandle

3.1示例受约束的操作

3.2示例接口

3.3VarHandle过程流字段示例

3.4VarHandle过程流数组示例

3.5优化

3.6放宽关于多态签名方法的返回类型

3.7通用多态签名

3.8VarHandle和MethodHandle之间的代码的合并

3.9存储器围栏

3.10VarHandle功能的扩展

4.0硬件概述

5.0扩展和可替代物

6.0附加公开

7.0第二附加公开

1.0.总体概述

本文描述了用于通过使用支持各种不同存储器访问模式的“句柄”来提供对存储器位置的安全和高效的受约束访问的技术。

1.1受防护的(fenced)操作

存储器模型定义了用于知道何时由其它处理器对存储器的写入对当前处理器可见以及由当前处理器的写入对其它处理器可见的条件。例如,一些处理器展现出强的存储器模型,其中所有处理器对于任何给定的存储器位置都看到完全相同的值。其它处理器展现出较弱的存储器模型,其中被称为存储器“屏障”或“围栏(fence)”的特殊指令冲刷(flush)本地处理器高速缓存或使本地处理器高速缓存无效,以便看到由其它处理器做出的写入或者使处理器的写入对其它处理器可见。

上面提到的存储器屏障的效果是,使得在围栏之前的操作的存储器效果在围栏之后的操作之前可见。因此,围栏可以被看作防止操作被跨围栏重新排序。在一些情况下,计算架构提供对一般围栏的支持,该一般围栏防止在围栏之前的任何加载/存储操作与在围栏之后的另一加载/存储操作的重新排序。然而,其它计算架构提供对细粒度围栏的支持,该细粒度的围栏防止某些类型的操作的重新排序,诸如加载-加载围栏防止在围栏之前的加载指令与围栏之后的其它加载指令的重新排序、加载-存储围栏防止在围栏之前的加载操作与在围栏之后的存储操作的重新排序,等等。细粒度的围栏通常比一般围栏需要更少的开销,从而允许开发者通过使用提供所需功能的最便宜的围栏来优化其程序的执行。某些类型的存储器访问操作利用围栏,以便提高程序一致性以及避免竞争条件(race condition)。这些操作此后将被称为“受防护的操作”或“受防护的访问”,其包括诸如放宽的访问(relaxed access)、易失性访问(volatile access)、懒惰的访问(lazy access)等等之类的技术。

1.2原子操作

原子操作还可以被用来确保程序一致性。原子操作是像访问的单个单元一样被执行的操作。例如,获得和设置(get-and-set)原子操作替换变量的当前值并且“同时”返回变量的旧值。在一些实例中,原子操作经由自旋锁(spinlock)和存储器围栏实现。然而,许多计算机架构支持可以由底层硬件高效执行的本机(native)原子操作,而不需要在软件级经由自旋锁和存储器围栏来实现。例如,处理器可以被配置为在相同的总线操作中读取存储器位置并且写入存储器位置,从而防止其它处理器在原子操作的中间访问那个存储器位置。如本文所使用的,“受约束的”操作/功能可以指代“受防护的”操作/功能和“原子”操作/功能中的任一或二者,因为“受防护的”和“原子”操作/功能都对如何访问和/或操纵表示变量的存储器位置应用约束。

1.3暴露受约束的操作

在一些情况下,如何以高效和方便的方式向这样的受约束的操作暴露接口可以是重大的挑战。例如,受约束的操作可以经由对与任意存储器位置交互的“不安全”库的访问来执行。然而,对于许多语言和虚拟机开发者来说,允许编码者访问任意存储器地址可能破坏平台否则希望做到的安全保证,从而导致该语言/虚拟机被设计为最小化或彻底防止其发生的非期望的行为,诸如分段错误或者更糟。例如,“不安全”库可以是针对特定处理器类型优化的本机库,但是可以不检查“更高级”语义,诸如确保对数组位置的写入在界限内。由于在使用“不安全”库时出现的复杂性,语言和虚拟机开发者常常将防止或最小化非预期行为的可能性的责任放在编码者手中。

另一个潜在的解决方案是将对“不安全”库的访问限制为已知具有以安全和负责的方式使用“不安全”库的专门知识的受信任的开发者。然而,将对受约束的操作的访问限制为开发者的小子集阻碍了其他开发者创建健壮和高效的软件产品的能力。

1.4VARHANDLE

在实施例中,VarHandle(“变量句柄”的缩写)是对存储器的可执行引用,诸如对由(一个或多个)(受管理或不受管理的)存储器位置定义的存储器、(一个或多个)类的(一个或多个)对象或(一个或多个)实例化、(一个或多个)对象的(一个或多个)字段、(一个或多个)类的(一个或多个)静态字段、或者(一个或多个)数组的(一个或多个)元素的可执行引用。存储在被引用的存储器中的值可以被称为(一个或多个)变量,并且VarHandle提供开发者可以通过其访问要对(一个或多个)变量执行的受约束的操作的安全接口。在实施例中,VarHandle经由保持变量的接收者(诸如保持字段的对象实例或保持元素的数组)来访问变量。在低级别,经由对与底层计算机硬件架构通信并且访问任意存储器位置的“不安全”方法的调用来执行受约束的操作。在一些实施例中,VarHandle被实现为类,并且该类的实例化被称为VarHandle实例或对象。

例如,在Java的背景下,受约束的操作可以通过对“sun.misc.Unsafe”的调用来执行,该“sun.misc.Unsafe”实现用于对表示各种变量(诸如对象、整型(int)、浮点型(float)、长整型(long)、双精度型(double)等)的任意存储器位置执行受约束的操作的方法。然而,在许多情况下,将这些“不安全”方法暴露给软件开发者造成存储器位置可能以虚拟机不能预期的方式被操纵的风险。这可能导致虚拟机的设计者努力减轻或阻止的运行时错误和/或崩溃,诸如分段错误。在实施例中,VarHandle提供了一种在方便和易于使用的接口中向开发者暴露这些低级的受约束的操作的安全方式。此外,在一些实施例中,虚拟机被配置为优化访问和安全检查的方式,以实现与暴露到“不安全”方法的直接接口几乎相同的运行时性能,但是没有直接利用“不安全”方法的缺点。

因此,VarHandle是将大大有益于软件社区的、以安全、方便和高效的方式向开发者暴露受约束的操作的机制。例如,“无阻塞算法”的领域是涉及使用受约束的访问作为基本并发原语的高性能并发数据结构的研究领域。因此,提供其中适当熟练的开发者可以容易且安全地实现这样的算法的环境将对该领域有显著益处。

2.0示例操作架构

图1示出了本文描述的技术可以在其中实践的、包括运行时环境112的示例计算架构100。本文描述的技术常常使用来自Java编程语言、Java虚拟机(“JVM”)和Java运行时环境的术语和定义。然而,设想的是,所描述的技术可以与任何编程语言、虚拟机架构或运行时环境结合使用。因此,例如,诸如“方法”之类的以Java术语描述的术语可以与其它术语(诸如“函数”)互换。此外,术语“方法”还与术语“类方法”或“对象方法”同义。方法是通过名称指代并且可以在使得方法的代码被执行的程序中的各个点处被调用(启用)的代码集合。

如图1中所示,运行时环境112包括虚拟机104。虚拟机104包括各种组件,诸如存储器管理器105(其可以包括垃圾收集器)、用于检查虚拟机104代码的有效性的类文件验证器106、用于定位和构建类的存储器中(in-memory)表示的类加载器107、以及用于执行虚拟机104代码的解释器108(其可以包括即时“JIT”编译器)。在一些实施例中,解释器108实现解释器和JIT编译器二者的方面。

在实施例中,计算架构100包括源代码文件101,源代码文件101包含以特定编程语言(诸如Java、C、C++、C#、Ruby、Perl等)编写的代码。因此,源代码文件101遵守用于相关联语言的语法和/或语义规则的特定集合。例如,用Java编写的代码遵守Java语言规范。然而,由于规范随时间被更新和修订,因此源代码文件101可以与指示源代码文件101所遵守的规范的修订的版本号相关联。用来编写源代码文件101的确切的编程语言一般不是关键的。

在各种实施例中,编译器102将根据依照程序员的方便的规范编写的源代码转换为可由特定机器环境直接执行的机器代码或目标代码,或者可由能够在各种特定机器环境之上运行的虚拟机104执行的中间表示,诸如字节码。字节码可由虚拟机104以比源代码更直接和高效的方式执行。将源代码转换为字节码包括将来自语言的源代码功能映射到利用诸如数据结构之类的底层资源的虚拟机功能。通常,由程序员经由源代码简单地呈现的功能被转换为更直接地映射为在由虚拟机104执行期间将引起的机器操作的较复杂的步骤。

在一些实施例中,解释器108实现解释器和JIT编译器二者的方面。例如,Oracle的HotSpot解释来自“字节码”的代码,而且定位被频繁执行(“热”)的字节码的部分并且将那些部分编译成适合底层计算机硬件的处理器的高效机器代码。运行时环境120可以在诸如操作系统110之类的较低级软件之上运行,在一些实施例中,该较低级软件通过一个或多个应用编程接口(API)而被访问。虚拟机104、API 109和操作系统110的组合被称为执行平台111。

为了提供清楚的示例,源代码文件101被示为要由执行平台111执行的程序的“最高级”表示。然而,虽然计算架构100将源代码文件101描绘为“最高级”程序表示,但是在其它实施例中,源代码文件101可以是经由将不同语言的代码文件处理成源代码文件101的语言的“较高级”编译器接收的中间表示。为了说明清楚的示例,以下公开假设源代码文件101遵守基于类的面向对象的编程语言。然而,这不是利用本文所描述的特征的要求。

在实施例中,编译器102接收源代码文件101作为输入,并且将源代码文件101转换为具有虚拟机104所期望的格式的类文件103。例如,在JVM的背景下,Java虚拟机规范的第4章定义了期望类文件103遵守的特定类文件格式。在一些实施例中,类文件103包含已经从源代码文件101转换而来的字节码。然而,在其它实施例中,类文件103还可以包含其它结构,诸如识别与各种结构(类、字段、方法等)相关的常量值和/或元数据的表。

以下讨论将假设类文件103中的每个类文件表示在源代码文件101中定义的(或由编译器102/虚拟机104动态生成的)相应的“类”。然而,上面提到的假设不是严格的要求并且将取决于虚拟机104的实现。因此,不管类文件103的确切格式如何,本文描述的技术仍然可以被执行。在一些实施例中,类文件103被划分为一个或多个“库”或“包”,该一个或多个“库”或“包”中的每个包括提供相关功能的类的集合。例如,库可以包含实现输入/输出(I/O)操作、数学工具、密码技术、图形实用程序等的一个或多个类文件。此外,一些类(或那些类内的字段/方法)可以包括访问限制,该访问限制将它们的使用限制在特定类/库/包内或者限制为具有适当许可(permission)的类。

2.1示例类文件结构

图2示出了根据实施例的、以框图形式的用于类文件200的示例结构。为了提供清楚的示例,本公开的剩余部分假设计算架构100的类文件103遵守本节中描述的示例类文件200的结构。然而,在实际环境中,类文件200的结构将依赖于虚拟机104的实现。此外,本文讨论的一个或多个特征可以修改类文件200的结构,以便例如添加附加的结构类型。因此,类文件200的确切结构对于本文所描述的技术不是关键的。为了节2.1的目的,“类”或“该类”是指由类文件200表示的类。

在图2中,类文件200包括常量表201、字段结构208、类元数据204和方法结构209。

在实施例中,常量表201是除其他功能之外还充当类的符号表的数据结构。例如,常量表201可以存储与在源代码文件101中使用的各种标识符(诸如类型、范围、内容和/或位置)相关的数据。常量表201具有用于由编译器102从源代码文件101导出的值结构202(表示类型整型、长整型、双精度型、浮点型、字节型(byte)、字符串型(string)等的常量值)、类信息结构203、名称和类型信息结构205、字段引用结构206以及方法引用结构207的条目。在实施例中,常量表201被实现为将索引i映射到结构j的数组。然而,常量表201的确切实现不是关键的。

在一些实施例中,常量表201的条目包括索引其它常量表201条目的结构。例如,用于表示字符串的值结构202中的一个值结构的条目可以保持将其“类型”识别为字符串的标签,以及对存储表示该字符串的ASCII字符的字符型(char)、字节型或整型值的常量表201的一个或多个其它值结构202的索引。

在实施例中,常量表201的字段引用结构206保持对常量表201中的类信息结构203中表示定义字段的类的一个类信息结构的索引以及对常量表201中的名称和类型信息结构205中提供字段的名称和描述符的一个名称和类型信息结构的索引。常量表201的方法引用结构207保持对常量表201中的类信息结构203中表示定义方法的类的一个类信息结构的索引以及对常量表201中的名称和类型信息结构205中提供用于方法的名称和描述符的一个名称和类型信息结构的索引。类信息结构203保持对常量表201中的值结构202中保持相关联的类的名称的一个值结构的索引。名称和类型信息结构205保持对常量表201中的值结构202中存储字段/方法的名称的一个值结构的索引以及对常量表201中的值结构202中存储描述符的一个值结构的索引。

在实施例中,类元数据204包括用于类的元数据,诸如(一个或多个)版本号、常量池中的条目数、字段数、方法数、访问标志(类是否是公有的、私有的、最终的、抽象的,等等)、对常量表201的类信息结构203中识别该类的一个类信息结构的索引、对常量表201的类信息结构203中识别超类(如果有的话)的一个类信息结构的索引,等等。

在实施例中,字段结构208表示识别类的各个字段的一组结构。字段结构208为类的每个字段存储用于该字段的访问器标志(字段是否是静态的、公有的、私有的、最终的,等等)、对常量表201中的值结构202中保持字段的名称的一个值结构的索引、以及对常量表201中的值结构202中保持字段的描述符的一个值结构的索引。

在实施例中,方法结构209表示识别类的各种方法的一组结构。方法结构209为类的每个方法存储用于该方法的访问器标志(例如,该方法是否是静态的、公共的、私有的、同步的,等等)、对常量表201中的值结构202中保持方法的名称的一个值结构的索引、对常量表201中的值结构202中保持方法的描述符的一个值结构的索引、以及与如源代码文件101中定义的方法的主体对应的虚拟机104指令。

在实施例中,描述符表示字段或方法的类型。例如,描述符可以被实现为遵守特定语法的字符串。虽然确切的语法并不关键,但是下文描述几个示例。

在描述符表示字段的类型的示例中,描述符识别由该字段保持的数据的类型。在实施例中,字段可以保持基本类型、对象或数组。当字段保持基本类型时,描述符是识别该基本类型的字符串(例如,“B”=byte(字节型)、“C”=char(字符型)、“D”=double(双精度型)、“F”=float(浮点型)、“I”=int(整型)、“J”=long int(长整型)等)。当字段保持对象时,描述符是识别该对象的类名称的字符串(例如,“L ClassName”)。在这种情况下,“L”指示引用,因此“L ClassName”表示对类ClassName的对象的引用。当字段是数组时,描述符识别由该数组保持的类型。例如,“[B”指示字节型的数组,其中“[”指示数组,而“B”指示该数组保持字节型的基本类型。然而,由于数组可以嵌套,因此数组的描述符还可以指示嵌套。例如,“[[L ClassName”指示数组,在该数组中每个索引保持保持类ClassName的对象的数组。在一些实施例中,ClassName是完全限定的并且包括类的简单名称以及类的路径名称。例如,ClassName可以指示文件存储在托管类文件200的包、库或文件系统中的何处。

在方法的情况下,描述符识别方法的参数和方法的返回类型。例如,方法描述符可以遵循一般形式“({ParameterDescriptor})ReturnDescriptor”,其中{ParameterDescriptor}是表示参数的字段描述符的列表,而ReturnDescriptor是识别返回类型的字段描述符。例如,字符串“V”可以被用于表示void(空)返回类型。因此,在源代码文件101中被定义为“Object m(int I,double d,Thread t){…}”的方法匹配描述符“(I D L Thread)L Object”。

在实施例中,方法结构209中保持的虚拟机104指令包括引用常量表201的条目的操作。

使用Java作为示例,考虑以下类

在上面的示例中,Java方法add12and13在类A中定义、不带参数并且返回整数。方法add12and13的主体调用采用常量整数值12和13作为参数的类B的静态方法addTwo,并且返回结果。因此,在常量表201中,编译器102除其它条目之外还包括对应于对方法B.addTwo的调用的方法引用结构。在Java中,对方法的调用向下编译为JVM的字节码中的invoke命令(在该情况中为invokestatic,因为addTwo是类B的静态方法)。向invoke命令提供对应于识别定义addTwo的类“B”、addTwo的名称“addTwo”以及addTwo的描述符“(I I)I”的方法引用结构的对常量表201的索引。例如,假设上面提到的方法引用被存储在索引4处,则字节码指令可以看起来是“invokestatic#4”。

由于常量表201用携带识别信息的结构以符号形式指代类、方法和字段,而不是用对存储器位置的直接引用来指代类、方法和字段,因此常量表201的条目被称为“符号引用”。符号引用被用于类文件103的一个原因是因为在一些实施例中编译器102不知道类一旦被加载到运行时环境112中将怎样被存储以及将被存储在何处。如将在节2.3中描述的,在所引用的类(和相关联的结构)已经被加载到运行时环境中并且已经被分配了具体的存储器位置之后,最终,符号引用的运行时表示由虚拟机104解析为实际的存储器地址。

2.2示例虚拟机架构

图3示出了根据实施例的以框图形式的示例虚拟机存储器布局300。为了提供清楚的示例,剩余的讨论将假设虚拟机104遵守图3中所描绘的虚拟机存储器布局300。此外,虽然虚拟机存储器布局300的组件可以被称为存储器“区域”,但是不要求存储器区域是相邻的。

在图3示出的示例中,虚拟机存储器布局300被划分为共享区域301和线程区域307。

共享区域301表示存储器中存储在虚拟机104上执行的各种线程之间共享的结构的区域。共享区域301包括堆302和按类的(per-class)区域303。在实施例中,堆302表示从其分配用于类实例和数组的存储器的运行时数据区域。在实施例中,按类的区域303表示存储与单独的类有关的数据的存储器区域。在实施例中,对于每个加载的类,按类的区域303包括表示来自类的常量表201的数据的运行时常量池304、字段和方法数据306(例如,为了保持类的静态字段)以及表示用于类的方法的虚拟机104指令的方法代码305。

线程区域307表示其中存储特定于单独的线程的结构的存储器区域。在图3中,线程区域307包括表示由不同线程利用的每线程结构的线程结构308和线程结构311。为了提供清楚的示例,图3中所描绘的线程区域307假设两个线程正在虚拟机104上执行。然而,在实际环境中,虚拟机104可以执行任何任意数量的线程,其中相应地缩放线程结构的数量。

在实施例中,线程结构308包括程序计数器309和虚拟机栈310。类似地,线程结构311包括程序计数器312和虚拟机栈313。在实施例中,程序计数器309和程序计数器312存储由它们各自的线程执行的虚拟机指令的当前地址。因此,当线程逐句通过(step through)指令时,程序计数器被更新以维护对当前指令的索引。在实施例中,虚拟机栈310和虚拟机栈313各自存储保持局部变量和部分结果的、用于它们各自的线程的帧,并且还被用于方法启用和返回。

在实施例中,帧是被用来存储数据和部分结果、返回用于方法的值以及执行动态链接的数据结构。每次启用方法时都创建新的帧。当使得帧被生成的方法完成时,帧被销毁。因此,当线程执行方法启用(invocation)时,虚拟机104生成新帧并将该帧推送到与该线程相关联的虚拟机栈上。当方法启用完成时,虚拟机104将方法启用的结果传递回前一帧,并且使当前帧出栈。在实施例中,对于给定的线程,在任何时间点处有一个帧是活动的。这个活动帧被称为当前帧,使得当前帧被生成的方法被称为当前方法,并且当前方法所属的类被称为当前类。

图4示出了根据实施例的以框图形式的示例帧400。为了提供清楚的示例,剩余的讨论将假设虚拟机栈310和虚拟机栈313的帧遵守帧400的结构。

在实施例中,帧400包括局部变量401、操作数栈402和运行时常量池引用表403。

在实施例中,局部变量401被表示为各自保持诸如布尔型(Boolean)、字节型、字符型、短整型、整型、浮点型、引用型等之类的值的变量的数组。此外,诸如长整型或双精度型之类的一些值类型可以由数组中的多于一个条目表示。局部变量401被用来在方法启用时传递参数以及存储部分结果。例如,当响应于启用方法而生成帧400时,参数可以被存储在局部变量401内预定义的位置中,诸如对应于启用中的第一个至第N个参数的索引1-N。

在实施例中,当帧400由虚拟机104创建时,操作数栈402默认是空的。然后,虚拟机104提供来自当前方法的方法代码305的指令,以将来自局部变量501的常量或值加载到操作数栈502上。其它指令从操作数栈402取出操作数、对它们进行操作并且将结果推回到操作数栈402上。此外,操作数栈402被用来准备要被传递到方法的参数以及接收方法结果。例如,在发出对方法的启用之前,被启用的方法的参数可以被推送到操作数栈402上。然后,虚拟机104生成用于方法启用的新帧,其中前一帧的操作数栈402上的操作数出栈并且被加载到新帧的局部变量401中。当被启用的方法终止时,新帧从虚拟机栈出栈并且返回值被推送到前一帧的操作数栈402上。

在实施例中,运行时常量池引用表403包含对当前类的运行时常量池304的引用。运行时常量池引用表403被用来支持解析(resolution)。解析是这样的过程:通过该过程,常量池304中的符号引用被翻译成具体的存储器地址,从而按照需要加载类以解析尚未定义的符号并且将变量访问翻译成与这些变量的运行时位置相关联的存储结构中的适当偏移。

2.3加载、链接和初始化

在实施例中,虚拟机104动态地加载、链接和初始化类。加载是寻找具有特定名称的类并且在运行时环境112的存储器内创建来自该类的相关联的类文件200的表示的过程。例如,在虚拟机存储器布局300的按类的区域303内创建用于类的运行时常量池304、方法代码305以及字段和方法数据306。链接是取出类的存储器中表示并且将其与虚拟机104的运行时状态组合以使得类的方法可以被执行的过程。初始化是执行类构造函数(constructor)以设置类的字段和方法数据306的起始状态和/或为被初始化的类在堆302上创建类实例的过程。

以下是可以由虚拟机104实现的加载、链接和初始化技术的示例。然而,在许多实施例中,步骤可以是交错,以使得初始类被加载,然后在链接期间第二个类被加载以解析在第一个类中发现的符号引用,这又导致第三个类被加载,等等。因此,通过加载、链接和初始化的阶段的进程可以根据类而不同。此外,一些实施例可以延迟(“懒惰地”执行)加载、链接和初始化过程的一个或多个功能,直到类被实际需要。例如,方法引用的解析可以被延迟直到启用被引用的方法的虚拟机104指令被执行。因此,对于每个类何时执行步骤的确切时机在实施方式之间可以差别很大。

为了开始加载过程,虚拟机104通过启用加载初始类的类加载器107来启动。用于指定初始类的技术将根据实施例而不同。例如,一种技术可以使虚拟机104在启动时接受指定初始类的命令行变元(argument)。

为了加载类,类加载器107解析与该类对应的类文件200,并且确定类文件200是否为形式良好的(满足虚拟机104的语法期待)。如果不是,则类加载器107生成错误。例如,在Java中,可能以异常的形式生成错误,该异常被抛给异常处理器以供处理。否则,类加载器107通过在按类的区域303内分配用于类的运行时常量池304、方法代码305以及字段和方法数据306来生成类的存储器中表示。

在一些实施例中,当类加载器107加载类时,类加载器107还递归地加载被加载类的超类。例如,虚拟机104可以确保在继续进行对特定类的加载、链接和初始化过程之前该特定类的超类被加载、链接和/或初始化。

在链接期间,虚拟机104验证类、准备类并且执行在类的运行时常量池304中定义的符号引用的解析。

为了验证类,虚拟机104检查类的存储器中表示在结构上是否是正确的。例如,虚拟机104可以检查除了泛型(generic)类对象(Object)之外的每个类具有超类、检查最终(final)类没有子类以及最终方法没有被重写(override)、检查常量池条目是否彼此一致,检查当前类是否具有对常量池304中引用的类/字段/结构的正确访问许可、检查方法的虚拟机104代码不会引起非预期行为(例如,确保跳转指令不会使虚拟机104超出方法的末尾),等等。在验证期间执行的确切检查取决于虚拟机104的实现。在一些情况下,验证可以导致附加的类被加载,但是在继续进行之前不一定要求那些类也被链接。例如,假设类A包含对类B的静态字段的引用。在验证期间,虚拟机104可以检查类B,以确保所引用的静态字段实际存在,这可能导致类B的加载,但不一定导致类B的链接或初始化。然而,在一些实施例中,某些验证检查可以被延迟直到较晚的阶段,诸如在符号引用的解析期间被检查。例如,一些实施例可以推迟检查对于符号引用的访问许可直到这些引用被解析。

为了准备类,虚拟机104将位于该类的字段和方法数据306内的静态字段初始化为默认值。在一些情况下,将静态字段设置为默认值可以与运行类的构造函数不同。例如,验证过程可以在初始化期间将静态字段清零或将静态字段设置为构造函数期望这些字段具有的值。

在解析期间,虚拟机104从包括在类的运行时常量池304中的符号引用动态地确定具体的存储器地址。为了解析符号引用,虚拟机104利用类加载器107加载符号引用中识别出的类(如果还未加载的话)。一旦被加载,则虚拟机104知道所引用的类和它的字段/方法的按类的区域303内的存储器位置。然后,虚拟机104用对所引用的类、字段或方法的具体存储器位置的引用来替换符号引用。在实施例中,虚拟机104高速缓存解析,以便在当虚拟机104处理另一个类时遇到相同的类/名称/描述符的情况下重用。例如,在一些情况下,类A和类B可以启用类C的同一方法。因此,当对类A执行解析时,该结果可以被高速缓存并且在类B中的相同符号引用的解析期间被重用,以减少开销。

在一些实施例中,在链接期间解析符号引用的步骤是可选的。例如,实施例可以以“懒惰的”方式执行符号解析,从而延迟解析的步骤直到需要所引用的类/方法/字段的虚拟机104指令被执行。

在初始化期间,虚拟机104执行类的构造函数以设置该类的起始状态。例如,初始化可以初始化该类的字段和方法数据306,以及在由构造函数创建的堆302上生成/初始化任何类实例。例如,用于类的类文件200可以指定特定方法是用于设置起始状态的构造函数。因此,在初始化期间,虚拟机104执行该构造函数的指令。

在一些实施例中,虚拟机104通过最初检查字段/方法在所引用的类中是否被定义来执行对字段和方法引用的解析。否则,虚拟机104针对所引用的方法/字段递归搜索所引用的类的超类直到字段/方法被定位或到达最高级的超类,在到达最高级的超类的情况下生成错误。

3.0VARHANDLE

在实施例中,VarHandle是对存储器的可执行引用,诸如对由(一个或多个)(受管理或不受管理的)存储器位置定义的存储器、(一个或多个)类的(一个或多个)对象或(一个或多个)实例化、(一个或多个)对象的(一个或多个)字段、(一个或多个)类的(一个或多个)静态字段或者(一个或多个)数组的(一个或多个)元素的可执行引用。在一些实施例中,VarHandle被实现为可以被实例化以创建VarHandle实例/对象的类。在实施例中,VarHandle经由保持变量的接收者(诸如保持字段的对象实例或保持元素的数组)来访问变量。在低级别,经由对与底层计算机硬件架构通信并且访问任意存储器位置的低级内部函数(intrinsic)的调用来执行受约束的操作。例如,在Java的背景下,可以通过对“sun.misc.Unsafe”的调用来执行受约束的操作,该“sun.misc.Unsafe”实现用于对表示各种变量(诸如对象、整型、浮点型、长整型、双精度型等)的任意存储器位置执行受约束的操作的方法。然而,在许多情况下,向软件开发者暴露这些“不安全”方法造成存储器位置可能以虚拟机104不能预期的方式被操纵的风险。这可能导致虚拟机104的设计者努力减轻或阻止的运行时错误和/或崩溃,诸如分段错误。

一种先前的解决方案是将对“不安全”方法的访问限制为已知具有以安全和负责的方式使用“不安全”库的专门知识的受信任的开发者。然而,将对受约束的操作的访问限制为开发者的小子集阻碍了其他开发者创建健壮和高效的软件产品的能力。在实施例中,VarHandle提供了一种在方便和易于使用的接口中向开发者暴露这些低级受约束的操作的安全方式。此外,在一些实施例中,虚拟机104被配置为优化访问和安全检查的方式,以实现与暴露到“不安全”方法的直接接口几乎相同的运行时性能,但是没有允许对“不安全”方法的直接访问的固有缺点。

3.1示例受约束的操作

本文描述的技术适用于几乎任何类型的受约束的操作。然而,为了提供清楚的示例,参考VarHandle描述的操作将包括放宽的获得(relaxed-get)和放宽的设置(relaxed-set)、易失性获得和易失性设置,获取获得(acquire-get)和释放设置(release-set)(懒惰的获得/设置),以及比较和设置(compare-and-set)和获得和设置(get-and-set)。然而,不同的实施例可以实现受约束操作的不同集合。例如,其它实施例还可以支持原子的加、减或其它原子算术运算。

在实施例中,放宽的获得和放宽的设置执行放宽的受防护操作。在放宽的受防护操作中,仅在特定线程的执行内保证排序。因此,如果一个线程执行存储,则不能保证另一个线程将以相同的顺序看到存储。例如,如果线程A执行对变量a的存储1,然后执行对变量b的存储2,则有可能对变量b的存储在对变量a的存储之前对另一线程B变得可见。

在实施例中,易失性获得和易失性设置执行易失性受防护操作。在易失性受防护操作中,在易失性存储和易失性加载之间实施顺序排序。因此,易失性受防护操作用直接对主存储器执行的读取和写入来绕过本地线程高速缓存。

在实施例中,获取获得和释放设置执行懒惰的受防护操作。在懒惰的受防护操作中,在释放设置和获取获得之间执行同步,以确保在执行期间的特定点处发生顺序(happens-before)关系在线程之间可以继续保留。例如,如果线程A使用释放设置来执行对变量a的存储1,然后使用释放设置来执行对变量b的存储2,则变量a和变量b的存储之间的发生顺序关系相对于后续的获取获得被维持。因此,如果线程B在对b的存储变得可见之后使用获取获得来读取变量b,则将保证对a的存储会在线程B的上下文中预先发生。因此,一旦对b的存储作为获取获得的结果而可见,则对a的存储也对线程B可见。

在实施例中,比较和设置是将存储器位置的内容与给定值进行比较并且仅当比较为真(true)时将内容修改为新值的原子操作。在实施例中,获得和设置是将值写入存储器位置并且返回存储器位置的旧值的原子操作。

在一些实施例中,VarHandle还支持对变量的“正常”或“无约束”访问,该“正常”或“无约束”访问不提供关于对存储器中的变量的存储/加载的原子性或重新排序的保证。因此,如本文所描述的实施例适用于宽范围的排序和原子性约束,包括与“正常”存储器访问相关联的“空(null)”约束。因此,在一些实施例中,“正常”或“无约束”访问模式是使用VarHandle可用的若干选项中的一个选项,其中每个选项对存储器访问施加不同级别的约束。

3.2示例接口

如上面所提到的,VarHandle引用保持变量的“接收者”,诸如保持字段的对象实例或保持元素的数组。“接收者”的类型和由“接收者”保持的“变量”的类型是特定于实现的。本文所描述的技术适用于几乎任何类型的“接收者”和“变量”。然而,以下表示可以经由VarHandle访问的“接收者”和“变量”的示例。

在实施例中,VarHandle保持对以下的一个或多个的引用:(1)静态字段,其中接收者是保存静态字段的类,(2)实例字段,其中接收者是保持字段的类的实例,或(3)数组元素,其中接收者是在数组中的定义的索引处保持该元素的数组。因此,每个接收者与特定类型的变量访问相关联,诸如(1)参考静态访问、(2)参考实例访问,及(3)参考数组访问。

然而,在一些实施例中,VarHandle表示参考虚拟机104外部的堆外(off-heap)或不受管理的存储器位置的接收者。例如,可以替代地使用基于库的接收者类型,而不是基于语言的接收者类型,诸如保持字段的类/保持元素的数组。在这种情况下,接收者类型可以在具有作为接收者接受该类的实例的对应VarHandle实现的库中被定义。例如,一个这样的表示可以是保持到堆外/直接/不受管理的存储器的基地址(base address)以及界限的接收者类型,其中存储器访问被约束为[基地址,基地址+界限]。然后,VarHandle将存储与该接收者类型相关的数据并且相应地使用基地址/界限来访问存储器。

在实施例中,由“接收者”保持的变量包括以下的一个或多个:“Object”引用、静态类型的“Object”引用(“Object”的子类型)、“整型”或“长整型”。然而,其它实施例可以具有可以保持许多其它类型的原语(primitive)变量的“接收者”,诸如浮点型、双精度型、字符型等等。另外,其它实施例可以提供对于保持“类似原语”变量(诸如字符串)的“接收者”的支持。此外,在其它实施例中,由“接收者”保持的变量可以表示在堆302外部或者甚至在运行时环境112外部的存储器区域。例如,VarHandle可以表示对不同虚拟机/运行时环境的存储器区域中的结构的引用。为了清晰,斜体的Object指的是充当所有其它类的超类的泛型对象结构,正如在Java编程语言中那样。其它对象类型被称为“静态类型的对象”并且是Object的子类。

在一些实施例中,VarHandle对保持值类型的接收者执行受约束的操作,如在2014年5月13日提交的美国临时申请61/992,753中所描述的那样,在此出于所有目的通过引用并入该申请,如同本文完全阐述了该申请一样。

在示例实施例中,假设存在三种类型的接收者和四种类型的变量,对于每个受约束的操作,实现的数量为12(3×4)。例如,处理对Object的静态字段、静态类型对象的数组、整型的实例字段以及各种组合等的受约束访问的实现。接收者类型和变量类型的组合将被称为“访问类型”。此外,在示例实施例中存在9(3×3)个不同的接口形状,因为Object引用和静态类型对象引用可以共享公共的接口形状(可以使用泛型Object类型来表示二者),但是具有不同的实现。例如,后者可以执行显式的类型转换,而对于前者来说类型转换将是多余的。然而,实现和/或接口形状的数量取决于由给定实施例支持的不同访问类型的数量。此外,假设存在八种不同类型的支持的受约束操作,则示例实施例将拥有96(8×12)种不同的受约束操作实现。例如,其中接收者是静态字段并且变量是静态类型对象的放宽的获得的实现、其中接收者是实例字段并且变量是整型的易失性设置的实现,以及各种组合等。

在一些实施例中,使用用于每个接口形状的分开的抽象类来实现VarHandle,从而造成九个不同抽象类的集合。然后这些抽象类可以由实现用于给定访问类型的受约束操作方法(包括用于其的代码)的类来进行子类化(sub-class)。例如,一个子类可以实现接收者是数组并且变量是泛型Object的情况,而另一个子类可以实现接收者是数组并且变量是静态类型对象的情况。二者共享相同的接口形状并且因此可以派生自同一抽象类,但是提供不同的实现。然而,为了简化对VarHandle的访问,一些实施例为VarHandle定义包括用于每个受约束操作的多态方法签名的单个抽象类。例如,在Java代码中,抽象类可以部分地看起来是:

public abstract class VarHandle{

...

//放宽的访问器

public final native

@MethodHandle.PolymorphicSignature

Object get(Object...args);

public final native

@MethodHandle.PolymorphicSignature

Object set(Object...args);

//易失性访问器

public final native

@MethodHandle.PolymorphicSignature

Object getVolatile(Object...args);

public final native

@MethodHandle.PolymorphicSignature

Object setVolatile(Object...args);

//懒惰的访问器

public final native

@MethodHandle.PolymorphicSignature

Object getAcquire(Object...args);

public final native

@MethodHandle.PolymorphicSignature

Object setRelease(Object...args);

//比较和设置访问器

public final native

@MethodHandle.PolymorphicSignature

Object compareAndSet(Object...args);

public final native

@MethodHandle.PolymorphicSignature

Object getAndSet(Object...args);

...}

以下示例引用VarHandle以便于说明清楚的示例,但是本文所描述的技术不限于如在VarHandle示例中呈现的类的确切格式。

在实施例中,多态签名是指示方法可以接受任何任意数量的变元和变元类型以及可以具有任何返回类型的特殊类型的签名。因此,实现对应于多态签名的方法的VarHandle的子类可以潜在地使用任何接口形状并且仍然通过验证。因此,一个抽象类足以覆盖不同的访问类型,并且开发者可以以相对统一的方式利用VarHandle的任何子类的受约束的操作方法。

在实施例中,VarHandle子类保持与受约束操作的四个可能方法描述符对应的四个方法类型描述符以及保持“成员名(membername)”的“varform”。例如,用于“-get(获得)”操作的描述符可以是相同的,类似地用于“-set(设置)”操作的描述符也可以是相同的,因为这些方法的变元和返回类型匹配。由VarHandle子类保持的方法类型描述符决定所实现的受约束操作方法的方法签名。因此,为了正确地使用受约束操作方法,调用点使用由对应描述符指定的变量和返回类型来启用该方法。“varform”表示不同受约束操作的行为。因此,在示例实施例中,varform保持八个“成员名”,每个成员名表征不同的受约束操作方法的行为。例如,对于每个受约束操作,对应的“成员名”可以表示用于执行用于所实现的访问类型的受约束操作的代码或指令。

3.3VarHandle过程流程字段例

图5示出了根据实施例的以框图形式的用于使用VarHandle执行受约束操作的示例过程。为了本节的目的,利用VarHandle的类将被称为“作用类(acting class)”,并且将假设使用示例VarHandle抽象类来定义VarHandle。

在下面的示例中,将参考访问类的实例字段的VarHandle来描述图5。针对数组访问使用图5的过程的附加示例将稍后在节3.4中描述。

在方框500处,虚拟机104接收VarHandle实例的声明。在一些实施例中,虚拟机104经由已从用于作用类的源代码文件101编译而来的类文件103接收VarHandle实例的声明。在实施例中,可以使用以下示例源代码来声明用于由接收者“Receiver”保持的类型“Value”的字段“val”的VarHandle实例:

VarHandle varHandleOfValueOnReceiver=VarHandles.lookup()

.findFieldHandle(Receiver.class,"val",Value.class);

在实施例中,VarHandles是如下生成器类:其包括用于返回实现用于特定访问类型的受约束操作的各种VarHandle子类的实例的方法。lookup()是类VarHandle的静态方法,它返回被用于返回(经由方法findFieldHandle)连结到(tiedto)指定字段的VarHandle子类实例的查找类。例如,由查找类生成的VarHandle子类实例可以存储与VarHandle子类实例所绑定到的接收者和变量类对应的用于每个方法的描述符。在上面的示例声明中,findFieldHandle创建访问具有Receiver类型的静态类型对象的名为“val”的字段的VarHandle实例,其中该字段保持具有Value类型的静态类型对象。因此,例如,对于指派给varhandleOfValueOnReceiver的VarHandle来说,“-set”操作可以与描述符“(L Receiver L Value)V”相关联,而“-get”操作可以与描述符“(L Receiver)L Value”相关联。

作为另一个示例,在用于类的静态字段的VarHandle的情况下,查找类方法可以采用类的名称(或者表示该类的Class对象的名称)、在该类内的静态字段的名称以及静态字段的类。在一些实施例中,查找类执行访问控制检查,以查明作用类是否具有访问指定的接收者和变量的适当特权。例如,如果变量被声明为私有的,则在定义该变量的包外部的类可能没有足够的特权来访问该变量,这将导致查找类生成错误。在一些实施例中,在创建VarHandle时而不是在启用VarHandle的方法时执行访问检查。在一些情况下,在查找时执行访问控制检查通过允许VarHandle在启用期间省略这些访问检查来减少开销。因此,在这样的实施例中,有可能将VarHandle传递到否则将不具有对所引用的接收者/变量的访问许可的类。然而,在一些实施例中,在启用期间可以再次执行访问检查,以防止具有不足的许可的类启用VarHandle,其代价是低效的运行时性能。

在方框501处,虚拟机104接收使用VarHandle执行受约束操作的指令。例如,对varHandleOfValueOnReceiver的易失性设置操作可以看起来如下:

Receiver r=...

Value v=...

varHandleOfValueOnReceiver.setVolatile(r,v)

其中r是类Receiver的实例,v是类Value的实例,并且使用r和v为参数对varHandleOfValueOnReceiver启用方法setVolatile。在实施例中,当编译器102构建用于作用类的类文件200时,常量表201的方法引用结构207包括对类VarHandle、方法的名称setVolatile以及表示采用类型为Receiver的对象和类型为Value的对象作为参数并且具有返回类型void的方法的符号方法描述符“(L Receiver L Value)V”的引用。此外,在虚拟机104代码中,调用被转换为指定对于上面提到的对setVolatile的方法引用的到常量表201的索引的启用指令。

作为另一个示例,在用于类的静态字段的VarHandle的情况下,setVolatile可以被隐式地绑定到类,从而消除了显式指定接收者的需要。例如,调用可以看起来是“varHandleOfValueOnReceiver.setVolatile(v)”。在声明期间,VarHandle被绑定到接收者的类/类型,并且因为这是静态访问,所以仅存在一个可能的接收者(该类自身)。因此,与实例字段情况不同,VarHandle已经知道要对其作用的确切接收者。然而,在实例字段情况下,VarHandle被绑定到接收者的类型/类,但是不绑定到任何特定的接收者实例。因此,调用特定受约束函数/方法的指令通过隐式绑定到接收者(诸如在静态情况下)来指示接收者或者指定接收者(诸如在实例字段的情况下)。因此,执行实例访问的VarHandle能够对被传递到调用中的任何接收者实例执行操作,只要该接收者匹配在声明期间VarHandle被绑定到的类型。

作为另一个示例,对varHandleOfValueOnReceiver的易失性获得操作可以看起来如下:

Receiver r=...

Value v=(Value)varHandleOfValueOnReceiver.getVolatile(r);其中r是Receiver的实例,v是值的实例,(Value)是对返回的类型转换,并且使用r作为参数对varHandleOfValueOnReceiver启用getVolatile。在实施例中,当编译器102构造用于作用类的类文件200时,常量表201的方法引用结构207包括对类VarHandle、方法的名称getVolatile以及表示采用类型为Receiver的对象作为参数并且返回类型为Value的对象的方法的符号方法描述符“(L Receiver)L Value”的引用。此外,在虚拟机104代码中,调用被转换为指定对于上面提到的对getVolatile的方法引用的到常量表201的索引的启用指令。在一些实施例中,getVolatile将静态类型的对象向下擦除(erase down)为最一般的形式(类型Object),因此返回时的类型转换确保编译器102可以确定用于符号方法描述符“(L Receiver)L Value”的返回类型。然而,在其它实施例中,如果编译器102可以从表达式左侧的局部变量类型推断出返回类型(例如,编译器实现“目标类型化”),则可以避免显式的类型转换。

在方框502处,虚拟机104对受约束操作执行类型安全检查。在实施例中,当引用多态签名方法的启用指令由虚拟机104执行时,用被称为“内部链接”的过程来替换上文在节2.3中描述的链接步骤。在正常情况下,使用易失性设置操作作为示例,当虚拟机104试图解析符号引用却发现没有与名称setVolatile和描述符“(L Receiver L Value)V”相匹配的方法在类VarHandle中被定义时,将发生链接错误。然而,响应于确定被启用的方法是签名多态的,虚拟机104在执行各种类型安全检查的调用点处执行与启用的访问类型相关联的预定义方法,并且执行与被启用方法对应的成员名。

例如,多态签名方法可以与标志或者属性的特定组合相关联,诸如在VarHandle类的类文件200中将方法设置为最终的(final)和本机的(native)二者。当启用方法引用时,虚拟机104检查标志,以确定该方法是否是签名多态的。如果是,则虚拟机104调用被设计为处理由方法引用的描述符指定的特定接口形状的预定义方法。例如,预定义方法可以将类向下擦除为泛型Object,以便通过允许向下擦除为同一接口形状的不同启用使用同一预定义方法来减少预定义方法的数量。否则,方法不是签名多态的,并且虚拟机104继续进行如上文在节2.3中所描述的链接。

在一些实施例中,虚拟机104操纵操作数栈402,以向预定义方法提供附加信息。例如,由于在内部链接期间绕过了平常的链接程序,因此不能保证提供给被启用的多态签名方法的变元匹配在VarHandle声明期间指定的类型。这可能导致非预期的行为,因为被启用以执行受约束操作的VarHandle实例的成员名可能依赖于在VarHandle声明期间指定的接收者和变量类的大小和/或访问类型。在启用setVolatile方法时,操作数栈402将具有被推送到其上的r、v和varHandleofValueonReceiver。然而,虚拟机104可以将诸如对由启用指令索引的方法引用结构的描述符的引用之类的附加数据推送到栈上。因此,在预定义的方法中,虚拟机104可以将由VarHandle实例存储的描述符与调用点处的方法启用的描述符进行比较,以确保变元和返回类型与VarHandle实例的成员名所期望的变元和返回类型匹配。如果匹配,则预定义方法启用成员名以执行受约束的操作。否则,生成错误。

因此,在一些实施例中,内部链接分两个部分执行:(1)虚拟机104基于方法引用的被擦除的描述符来自动启用预定义方法,以执行类型安全检查,以及(2)虚拟机104启用VarHandle实例的成员名,以执行受约束的操作。在一些实施例中,在步骤(2)期间,执行附加的安全检查,诸如确定接收者是否为null或者(在数组访问的情况下)确定指定的索引是否在界限内。

当句柄被常量折叠(constant folded)时,易失性设置操作的示例内联踪迹(inlining trace)如下:

另外,用于处理“(L Object,L Object)V”的被擦除的签名的用于setVolatile的示例预定义方法可以看起来如下:

在示例方法setVolatile_LL_V中,第一参数是作为类VarHandle的实例的handle,第二参数是接收者,第三参数是值,而最后一个参数是符号方法描述符。示例方法setVolatile_LL_V首先执行方法类型描述符检查,以确定在调用点处的符号描述符(在作用类处的方法调用)是否与用于由handle存储的用于“-set(设置)”操作的调用描述符匹配。如果描述符不匹配,则生成错误,诸如抛出将由异常处理程序捕获的异常。否则,将利用除了在内部链接期间被添加的symbolicMethodType之外的所有参数来启用MethodHandle.linkToStatic方法以辅助预定义方法。然而,附加参数handle.vform.setVolatile被添加到变元,它表示实现受约束的操作的成员名。try/catch块存在,以捕获可能由MethodHandle.linkToStatic启用抛出的异常。然而,在其它实施例中,可以通过利用避免这样的声明的内部链接方法来避免try/catch块。在实施例中,对内部链接方法使用@ForceInline,它通知解释器108的JIT编译器不管最大内联限制和方法大小如何都内联该方法。

在方框503处,虚拟机104链接到实现受约束操作的方法代码。在实施例中,方法MethodHandle.linkToStatic链接由与易失性设置相关联的“成员名”表征的方法(在上面的示例中为handle.vform.setVolatile)。因此,MethodHandle.linkToStatic使得虚拟机104执行解析,从而将作用类中对setVolatile的方法引用解析为处理所声明的varHandle的访问类型的子类的setVolatile方法代码。在这种情况下,示例声明使用指派给VarHandle子类型FieldInstanceRefHandle的lookup()方法并且因此linkToStatic将引用链接到实现受约束操作setVolatile的FieldInstanceRefHandle的方法代码。对于字段实例访问实现setVolatile的链接代码的示例如下:

第一个参数是“FieldInstanceRefHandle”实例而后续参数是传递给“VarHandle.setVolatile”方法的参数。“FieldInstanceRefHandle”实例保持要与“Unsafe.putObjectVolatile”的启用一起使用的字段偏移。在该启用之前:执行安全检查,以确保接收者实例不是“null”(以避免潜在的分段错误);并且作为附加的安全检查来执行值的转换检查。

在方框504处,虚拟机104执行所链接的方法代码,以执行受约束操作。在上面的示例中,所链接的方法代码经由对Unsafe.putObjectVolatile的调用来执行易失性设置操作,其中Unsafe.putObjectVolatile以指定的受约束方式将value存储在对应的存储器位置处。例如,虚拟机104可以经由对操作系统110的一个或多个系统调用或呈现给底层计算机系统的处理器的机器指令来执行受约束操作。例如,为了实现存储器围栏操作,除了对变量的存储器位置的加载和/或存储之外,虚拟机104还可以发出由底层硬件本机支持的一条或多条存储器屏障指令。在一些实施例中,底层计算机硬件的处理器可以不支持执行受约束操作的本机操作。然而,在这样的实施例中,虚拟机104可以将综合了受约束操作将产生的相同效果的多个系统调用或机器指令拼凑(cobble)在一起。

3.4VARHANDLE过程流数组例

以下是被应用于接收者是数组并且变量是静态类型对象的情况的图5的过程流的示例。为了本节的目的,利用VarHandle的类将被称为“作用类”,并且将假设使用示例VarHandle抽象类来定义VarHandle。

在方框500处,虚拟机104接收VarHandle的声明。在实施例中,使用以下示例源代码来声明由数组类型“Value[]”的接收者保持的组件类型“Value”的数组元素的VarHandle实例:

VarHandle varHandleOfValueArray=VarHandles.

arrayHandle(Value[].class);

其中VarHandles.arrayHandle返回被实现以处理数组访问的VarHandle子类的实例。

在方框501处,虚拟机104接收经由VarHandle执行受约束操作的指令。在实施例中,经由以下示例源代码来启用对数组类型Value[]的实例r的索引i处的数组元素Valuev的易失性设置操作:

Value[]r=...

int i=...

Value v=...

varHandleOfValueArray.setVolatile(r,i,v)

在实施例中,对setVolatile(r,i,v)的方法引用包括方法描述符“(LReceiverILValue)V”,其表示该方法采用类型为Receiver的对象、整数和类型为Value的对象,并且返回类型为void。

在实施例中,通过以下示例源代码来启用对来自数组类型Value[]的实例r的索引i处的数组元素的Valuev的易失性获得操作:

Value[]r=...

int i=...

Value v=(Value)varHandleOfValueArray.getVolatile(r,i);

对应的符号方法描述符是“(LReceiverI)LValue”,其匹配易失性获得操作的方法类型描述符。

在方框502处,虚拟机104对该操作执行类型安全检查。在实施例中,对“VarHandle.setVolatile”的启用内部链接到“VarHandle.setVolatile_LIL_V”:

在VarHandle.setVolatile_LIL_V中,类型安全检查由checkExactType执行,其确保handle的方法描述符匹配在调用点处使用的参数/返回值的symbolicMethodType。

在实施例中,“MethodHandle.linkToStatic”的启用链接到由与易失性设置相关联的“成员名”表征的方法。在这种情况下,handle经由Varhandles.arrayHandle生成,Varhandles.arrayHandle返回VarHandle的ArrayRefHandle子类型并且因此成员名链接到setVolatile方法的数组实现。在实施例中,所链接的方法的示例实现如下:

“ArrayRefHandle”实例保持数组基地址偏移和数组移位,以便从索引计算要与“Unsafe.putObjectVolatile”的启用一起使用的偏移。在该启用之前:执行安全检查,以确保数组不为“null”并且索引在数组界限内。

3.5优化

在实施例中,虚拟机104执行用于VarHandle的优化,以减轻由于内部链接、类型检查和安全检查而施加的负担。

作为一个示例,可以由虚拟机104执行的优化是“常量折叠(constant folding)”。常量折叠是在编译时而不是在执行期间评估涉及常量的表达式的优化技术。因此,例如,解释器108/JIT编译器可以用存储结果的指令替换评估常量的指令。例如,“i=100+105+109”一般会把值推送到栈上并且执行相加指令的启用。然而,虚拟机104可以替代地提前评估表达式并且用将“314”加载到局部变量i中的单个指令来替换这些指令。因此,当虚拟机104指令被转换为机器代码以供执行时,由于执行更少和更简单的操作,所以运行时速度增加。

作为另一个示例,可以由虚拟机104执行的优化是“内联”或“内联展开”。在对虚拟机104或其组件(诸如解释器108/JIT编译器)进行内联期间,用被调用方法的主体来替换在调用点处的方法启用。因此,虚拟机104绕过了处理该启用以及使执行从一个方法的主体的跳转到另一个方法的主体的开销。在实施例中,当虚拟机104将调用内部链接到对VarHandle的受约束操作时,虚拟机104可以通过内联在内部链接期间执行的代码中的一些或全部来优化。

作为另一个示例,可以由虚拟机104执行的优化是高速缓存所解析的VarHandle启用并且对于将来的启用重用该存储器位置。假设识别绑定的接收者和变量类型VarHandle的字段是静态的,则不需要再次执行在查找期间的访问许可检查以及由预定义方法执行的类型安全检查。因此,当下次启用同一VarHandle方法时,可以跳过这些检查,从而得到几乎与直接暴露“不安全”方法持平的运行时性能。

作为另一个示例,实施例可以将对VarHandle的引用声明为一旦设置就不能改变的常量值。例如,在Java中,一些实现使用“static final(静态最终)”属性来定义这样的字段。因此,使用Java示例,对VarHandle实例“V”的static final引用被认为是常量,因此关于可以经由引用依赖关系链被追溯到V的类的任何final实例字段也被认为是常量。因此,当虚拟机104内联对V的方法调用时,虚拟机104可以将与类型检查相关联的指令常量折叠起来(fold away),link-to-static(链接到静态)调用可以被内联(因为已知最后一个变元是常量),并且显式的转换检查也可以折叠起来。

3.6放宽关于多态签名方法的返回类型

在一些实施例中,可以放宽多态签名方法(诸如在先前的VarHandle示例中所使用的多态签名方法)的返回类型,以使得如果类型不是泛型Object,则该类型是被编码成用于该方法的符号类型描述符的返回类型。例如,在说明性实施例中,VarHandle类可以被修改以声明放宽的多态签名方法,以使得所有基于设置的方法都返回“void”,而比较和设置方法返回“布尔型”。因此,在一些情况下,因为返回类型可以从方法签名推断出,所以可以避免上面的示例中的类型转换。表示用于VarHandle的放宽的多态方法签名的示例源代码部分如下:

abstract class VarHandle{

...

public final native

@MethodHandle.PolymorphicSignature

voidset(Object...args);

...

public final native

@MethodHandle.PolymorphicSignature

boolean compareAndSet(Object...args);

...

}

3.7通用多态签名方法

在实施例中,多态签名方法的类型的放宽被扩展,以换取每个接收者类型声明一个抽象类。因此,这样的抽象类将能够支持原语类型和引用值类型二者。

在实施例中,作为一个VarHandle抽象类的代替,定义对应于每种类型的接收者的三个类:

-用于静态字段访问的“StaticFieldHandle<R,V>”

-用于实例字段访问的“FieldHandle<R,V>”;和

-用于数组元素访问的“ArrayHandle<R,I,V>”。

为接收者R和值V声明类型变量。除了数组访问之外,还存在用于到数组的索引I的类型变量。在一些实施例中,类型变量I确保针对大于由虚拟机104定义的最大整数值的索引的对大数组中的元素的访问可以被支持。此外,与VarHandle一样,用于数组的接收者可以是数组类,或者可以是用于类似数组的结构的某种其它类型,诸如对堆外存储器的访问。

在静态字段访问可能很少的一些实施例中,这样的访问可以被折叠成“FieldHandle<R,V>”,其中受约束的访问方法接受空值或忽略接收者的值。因此,用于接收者的空值可以充当指示是否对所声明的VarHandle使用静态字段访问或实例字段访问的标志。因此,可以以用于确定是否正在执行静态访问或字段访问的附加处理为代价来减少类的数量。

在一些实施例中,上文描述的类可以从VarHandle扩展,以保留较低的级别但是更通用的机制,因为关于子类的方法将实质上重写超类的那些方法。

在实施例中,方法对于受约束操作通过其被内部链接的机制保持与先前在节3.3中描述的相同,并且因此性能将保持与VarHandle示例相对持平。然而,在一些情况下,可用性可以被增强,因为在编译时而不是运行时可能捕获更多的错误,这是由于在一些实现中,签名多态方法的启用可以由编译器102编译,而不管用于该启用的描述符是否产生有效的实现方法。

考虑用于对访问类型为Receiver的实例上的int字段的FieldHandle的启用的以下示例代码:

FieldHandle<Receiver,Integer>fh=...

int a=1;

int b=2;

fh.compareAndSet(this,1,2);

Integer ba=a;

Integer bb=b;

fh.compareAndSet(this,ba,bb);

在该示例中,原语类型被表示为装箱的(boxed)类型。在表示原始值的对象中封装(package)原始值被称为“装箱”。同样,将装箱的值解包回原始形式被称为“拆箱”。在一些实施例中,诸如整型、字节型、字符型、长整型等之类的原语类型不能被擦除为较通用的形式。然而,装箱的形式可以被擦除为泛型Object。因此,通过将原始值装箱,原始值可以被一般化。

在一些实施例中,第一个“compareAndSet”操作编译为以下示例虚拟机104代码:

10:invokevirtual#2//Method java/lang/invoke/FieldHandle.compareAndSet:(LVarHandleT est;II)Z

并且第二个编译为:

32:invokevirtual#4//Method java/lang/invoke/FieldHandle.compareAndSet:(LVarHandleT est;Ljava/lang/Integer;Ljava/lang

其中第一个数字(“10”/“32”)表示虚拟机104代码的索引,invoke virtual是启用采用指示常量表201中的方法引用的索引的变元的实例方法的命令,并且注释包括包含该方法的类的完全限定的路径名称和正在被启用的方法的描述符。

由于原语类型“int”与它的装箱的类型“Integer”兼容,因此编译器102将成功地编译第一启用,并且因此符号方法描述符将等于比较和设置操作的方法描述符(例如,在方法是签名多态的情况下,编译器103将验证“int”可转换为“Integer”,但是将不生成装箱指令)。对于第二次启用,符号方法描述符将不同,并且因此可能导致运行时异常。然而,invoke-exact语义可以被放宽,以替代地对变元执行变换,以便使得在调用点处的描述符与用于比较和设置操作的描述符相匹配。例如,值可以在执行基本启用之前和/或之后被自动地装箱或拆箱。

在一些实施例中,变换被扩展到接收者类型和/或非原始值类型,诸如从字符数组转换而来的字符串。另外,在一些实施例中,编译器102将类型参数的类(如果已知的话)编码为符号方法描述符,而不是可能的子类型。

在实施例中,VarHandle实例的方法中的一个或多个方法被定义为具有通用签名,并且虚拟机104为例如编译后的Java字节码中的VarHandle实例的方法中的一些或所有方法在调用点处具体化(reify)通用签名。

例如,考虑以下Java代码:

上面的类中的fh.compareAndSet的启用可以被编译为以下指令:

32:invokevirtual#6//方法

Test$FieldHandle.compareAndSet:(Ljava/lang/Object;

Ljava/lang/Object;Ljava/lang/Object;)Z

在上面的示例中,与类型变量“R”和“V”相关联的参数类型(在这种情况下是用于启用的Receiver和Value的实例)被擦除为泛型Object。在具体化通用签名被实现的实施例中,Receiver和Value可以在调用点处被捕获,而不是擦除它们,并且如果知道被传递给该方法的参数的类型是正确的,则在进一步继续之前,可以执行调用点处的方法签名(符号类型描述符)与期望的方法类型描述符的高效的指针相等性检查(就像在多态签名的情况下那样)。

3.8代码VARHANDLE和方法句柄的合并

在一些实施例中,除了VarHandle之外,虚拟机还实现充当对特定方法的句柄的、被称为MethodHandle的另一访问结构。MethodHandle实现的示例可以在公开的Java文档中找到。(http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodH andle.html)

与VarHandle一样,MethodHandle的一些实施例利用多态签名和内部链接。例如,启用MethodHandle被绑定到的方法可以通过对被声明为使用lookup类的特定方法的MethodHandle实例使用.invoke([Parameters])来执行。启用内部链接到MethodHandle的LamdaForm,该MethodHandle的LamdaForm本质上充当实现方法的包装器,并且在启用实现方法之前执行各种动作和/或检查。

在一些实施例中,作为VarHandle保持Varform的代替,VarHandle保持对应于不同的受约束操作方法的LambdaForms的集合。因此,“VarForm”实例可以被高速缓存并且在VarHandle和MethodHandle实例之间共享。此外,常见的“VarForm”实例可以静态定义并且因此降低启动成本。LambdaForm本质上充当其引用可以被更新的成员名的箱子。当LamdaForm从被解释切换到(经由编译器102)被编译时,可能发生更新。具体而言对于静态字段访问,成员名指向如下实现:其检查类是否需要被初始化(并且如果需要的话就将类初始化)、访问静态字段,然后将成员名更新为不执行静态访问并且仅访问该字段的实现。此外,一些实施例可以实现用于返回对于特定受约束操作的MethodHandle的VarHandle的方法。

在一些实施例中,作为可替代物,可以生成参考VarHandle实例上的方法的基于字段的直接方法句柄,以便以新形式的类自旋为代价减少自旋(spinning)类的数量。

3.9存储器围栏

存储器围栏是使得诸如CPU之类的处理器或编译器对在屏障指令之前和之后发出的存储器操作实施排序约束的一种类型的屏障指令。因此,在该屏障之前发出的操作被保证在该指令之后发出的操作之前执行。采用存储器围栏的一个原因是由于CPU和编译器常常采用可能导致无序执行的性能优化的事实。对于单个执行线程来说,优化通常被设计为确保程序的语义不受干扰,诸如省略对于从未由程序实际使用的数据的加载指令或者从缓冲区或高速缓存而不是从主存储器检索值。然而,对于多线程程序来说,除非仔细控制,否则存储器操作的重新排序可能导致不可预测的行为。存储器围栏不一定将数据存储或加载到存储器位置,而是控制何时可以从缓冲区/高速缓存读取值以及何时缓冲区/高速缓存应当被冲刷到主存储器。

几乎所有处理器至少支持保证在围栏之前发起的所有加载和存储将被严格排序在围栏之后发起的任何加载或存储之前的通用屏障指令。然而,许多处理器还支持提供较少排序保证但是常常产生较少开销的更细粒度的屏障。

在实施例中,虚拟机104支持可以访问底层硬件架构(或虚拟化的软件构造(construct),诸如虚拟机104)的各种防护操作的VarHandle。例如,VarHandle可以被扩展为包括附加的方法,该方法作为调用执行原子操作的“不安全”方法的代替内部链接到发出屏障指令的成员名。由VarHandle支持的确切的屏障指令可以由硬件的底层架构决定。例如,底层处理器或多处理器可以支持等同于所期望的存储器围栏的机器级指令。然而,在其它实施例中,底层硬件可能不支持所期望的存储器围栏。因此,在一些实施例中,虚拟机104通过在软件中实现围栏来合成存储器围栏操作,从而基本上使用其它机器级指令的组合来实现相同的效果。

以下是可以经由VarHandle访问的存储器围栏的几个示例。

(1)LoadLoad(加载加载)屏障–序列(Load1;LoadLoad;Load2),确保Load1的数据在Load2访问的数据和所有后续加载指令被加载之前被加载。在一些实施例中,LoadLoad屏障被用于执行推测加载和/或其中等待加载指令可以绕过等待存储的无序处理的处理器。

(2)StoreStore(存储存储)屏障–序列(Store1;StoreStore;Store2),确保Store1的数据在与Store2和所有后续存储指令相关联的数据之前对其它处理器可见。例如,常常对否则不保证从写缓冲区和/或高速缓存到其它处理器或主存储器的冲刷的严格排序的处理器使用StoreStore屏障。

(3)LoadStore(加载存储)屏障–序列(Load1;LoadStore;Store2),确保Load1的数据在与Store2和所有后续存储指令相关联的所有数据之前被加载。例如,常常对否则不保证从写缓冲区和/或高速缓存到其它处理器或主存储器的冲刷的严格排序的处理器使用StoreStore屏障。

(4)StoreLoad(存储加载)屏障–序列(Store1;StoreLoad;Load2),确保Store1的数据在由Load2和所有后续加载指令访问的数据被加载之前对其它处理器可见。StoreLoad屏障防止后续的加载不正确地使用Store1的数据值而不是使用来自由不同处理器执行的对同一位置的更近存储的数据值。

在一些实施例中,虚拟机104在底层硬件本身不支持围栏的情况下利用支持的屏障指令来合成围栏。例如,假设底层硬件不提供对于释放设置和获取获得懒惰操作的本机支持,而是支持上文列出的示例屏障指令。可以通过将加载-加载屏障指令与加载-存储屏障指令组合来合成获取围栏,并且可以通过将存储-加载屏障指令与存储-存储屏障指令组合来合成释放围栏。在一些实施例中,上面提到的获取/释放围栏然后可以向硬件发出和/或被用来实现释放设置/获取获得懒惰的受防护的操作。

3.10VARHANDLE功能的扩展

虽然VarHandle已经被描述为提供对原子操作和存储器屏障指令的高效和安全访问,但是VarHandle不限于这些功能。在其它实施例中,VarHandle可以被扩展,以提供对几乎任何类型的功能的访问,诸如对没有落入原子操作和存储器屏障指令的类别内的硬件特征的访问。

4.0硬件概述

根据一个实施例,本文所描述的技术由一个或多个专用计算设备实现。专用计算设备可以被硬连线以执行技术,或者可以包括诸如被永久性地编程以执行技术的一个或多个专用集成电路(ASIC)或现场可编程门阵列(FPGA)的数字电子设备,或者可以包括被编程为按照固件、存储器、其它存储装置或者其组合中的程序指令来执行技术的一个或多个通用硬件处理器。这样的专用计算设备还可以将定制的硬连线逻辑、ASIC或FPGA与定制的编程组合来实现技术。专用计算设备可以是桌上型计算机系统、便携式计算机系统、手持式设备、联网设备或者结合硬连线和/或程序逻辑以实现技术的任何其它设备。

例如,图6是示出可以在其上实现本发明的实施例的计算机系统600的框图。计算机系统600包括总线602或者用于传送信息的其它通信机制,以及与总线602耦接以用于处理信息的硬件处理器604。硬件处理器604可以是例如通用微处理器。

计算机系统600还包括被耦接到总线602以用于存储信息和要由处理器604执行的指令的主存储器606,诸如随机存取存储器(RAM)或其它动态存储设备。主存储器606还可以被用于在要由处理器604执行的指令的执行期间存储临时变量或其它中间信息。当被存储在处理器604可访问的非暂态存储介质中时,这样的指令使计算机系统600成为被定制以执行指令中指定的操作的专用机器。

计算机系统600还包括耦接到总线602以用于为处理器604存储静态信息和指令的只读存储器(ROM)608或者其它静态存储设备。诸如磁盘、光盘或固态驱动器之类的存储设备610被提供并且耦接到总线602,以用于存储信息和指令。

计算机系统600可以经由总线602耦接到诸如发光二极管(LED)显示器之类的显示器612,以用于向计算机用户显示信息。包括字母数字和其它键的输入设备614被耦接到总线602,以用于向处理器604传送信息和命令选择。另一种类型的用户输入设备是诸如鼠标、轨迹球或者光标方向键之类的光标控制器616,以用于向处理器604传送方向信息和命令选择以及用于控制显示器612上的光标移动。这种输入设备通常具有允许设备指定平面中的位置的在两个轴(第一个轴(例如,x)和第二个轴(例如,y))上的两个自由度。

计算机系统600可以使用与计算机系统结合使得计算机系统600成为专用机器或者把计算机系统600编程为专用机器的定制的硬连线逻辑、一个或多个ASIC或FPGA、固件和/或程序逻辑来实现本文所描述的技术。根据一个实施例,本文的技术由计算机系统600响应于处理器604执行包含在主存储器606中的一条或多条指令的一个或多个序列而执行。这样的指令可以从诸如存储设备610之类的另一存储介质被读取到主存储器606中。包含在主存储器606中的指令序列的执行使处理器604执行本文所描述的过程步骤。在可替代的实施例中,硬连线的电路系统可以代替软件指令或者与软件指令结合使用。

如本文所使用的,术语“存储介质”指存储使机器以特定方式操作的数据和/或指令的任何非暂态介质。这样的存储介质可以包括非易失性介质和/或易失性介质。非易失性介质包括例如光盘、磁盘或固态驱动器,诸如存储设备610。易失性介质包括动态存储器,诸如主存储器606。存储介质的常见形式包括例如软盘、柔性盘、硬盘、固态驱动器、磁带,或者任何其它磁性数据存储介质,CD-ROM,任何其它光学数据存储介质、具有孔的图案的任何物理介质、RAM、PROM和EPROM、FLASH-EPROM、NVRAM、任何其它存储器芯片或盒式磁带。

存储介质与传输介质不同但是可以与其结合使用。传输介质参与在存储介质之间传送信息。例如,传输介质包括同轴线缆、铜线和光纤,这些同轴线缆、铜线和光纤包括包含总线602的配线。传输介质还可以采取声波或光波的形式,诸如在无线电波数据通信和红外数据通信中产生的那些声波或光波。

将一条或多条指令的一个或多个序列承载到处理器604以供执行可以涉及各种形式的介质。例如,指令最初可以被承载在远程计算机的磁盘或固态驱动器上。远程计算机可以把指令加载到其动态存储器中并且使用调制解调器经电话线发送指令。计算机系统600本地的调制解调器可以接收电话线上的数据并且使用红外发射器把数据转换为红外信号。红外检测器可以接收红外信号中承载的数据,而适当的电路系统可以把数据放在总线602上。总线602把数据承载到主存储器606,处理器604从主存储器606检索指令并且执行指令。由主存储器606接收的指令可以可选地在由处理器604执行之前或之后被存储在存储设备610上。

计算机系统600还包括耦接到总线602的通信接口618。通信接口618提供耦接到网络链路620的双向数据通信,其中网络链路620连接到局域网622。例如,通信接口618可以是提供到对应类型电话线的数据通信连接的综合业务数字网络(ISDN)卡、线缆调制解调器、卫星调制解调器或者调制解调器。作为另一个示例,通信接口618可以是提供到兼容的局域网(LAN)的数据通信连接的LAN卡。无线链路也可以被实现。在任何这样的实现中,通信接口618发送和接收承载表示各种类型的信息的数字数据流的电信号、电磁信号或光信号。

网络链路620通常通过一个或多个网络提供到其它数据设备的数据通信。例如,网络链路620可以通过局域网622提供到主机计算机624或者到由互联网服务提供商(ISP)626运营的数据设备的连接。ISP 626又通过现在通常称为“因特网”628的全球分组数据通信网络提供数据通信服务。局域网622和因特网628二者都使用承载数字数据流的电信号、电磁信号或光信号。通过各种网络的信号以及在网络链路620上并且通过通信接口618的信号是传输介质的示例形式,其中这些信号把数字数据承载到计算机系统600或者承载来自计算机系统600的数字数据。

计算机系统600可以通过(一个或多个)网络、网络链路620和通信接口618发送消息和接收包括程序代码的数据。在因特网示例中,服务器630可以通过因特网628、ISP 626、局域网622和通信接口618发送对应用的请求代码。

接收到的代码可以由处理器604在它被接收到时执行、和/或被存储在存储设备610或其它非易失性存储装置中以供以后执行。

如本文所使用的,术语“第一”、“第二”、“某些”和“特定的”被用作命名约定,以将查询、计划、表示、步骤、对象、设备或其它项彼此区分,使得这些项可以在它们被引入之后被引用。除非本文另有指定,否则这些术语的使用并不暗示所引用的项的顺序、时机或任何其它特点。

5.0扩展和可替代物

在前面的说明书中,参考依实施方式不同的许多具体细节描述了本发明的实施例。因此,什么是本发明以及申请人旨在将什么作为本发明的唯一且排他的指示物是以权利要求发布的具体形式从本申请发布的一组权利要求,包括任何后续补正。用于这些权利要求中包含的术语的本文明确阐述的任何定义将决定权利要求中使用的这些术语的含义。因此,没有在权利要求中明确陈述的限制、元素、性质、特征、优点或属性不应当以任何方式限制这些权利要求的范围。因而,说明书和附图应当在说明性的意义上而不是限制性的意义上被考虑。

6.0附加公开

本文所描述的主题的方面在以下编号的条款中阐述:

1、一种方法,包括:识别创建变量句柄实例的第一指令,该第一指令包括识别接收者的类型以及由接收者保持的该变量句柄实例被配置为提供对其的访问的变量的声明信息;响应于第一指令,并且基于声明信息,执行一个或多个检查,以确定对变量的访问是否是许可的;响应于确定对变量的访问是许可的,创建变量句柄实例,该变量句柄实例包括被配置为对变量的存储器位置执行受约束操作的一个或多个约束函数;识别指定对该变量句柄实例的一个或多个受约束函数中的特定受约束函数的调用的第二指令,其中第二指令指示接收者;以及识别存储变量的特定存储器位置并且使该特定受约束函数相对于该特定存储器位置被执行。

2、如条款1所述的方法,其中接收者是类并且变量是由该类保持的静态字段、接收者是类实例并且变量是由该类实例保持的字段、接收者是数组并且变量是该数组的元素、或者接收者是对表示变量的堆外或直接存储器位置的引用。

3、如条款1-2中的任何一个条款所述的方法,其中第一指令调用查找函数,该查找函数使变量句柄实例存储变量在接收者内的偏移,并且将变量句柄实例的一个或多个受约束函数绑定到识别接收者的类型和变量的类型的一个或多个描述符。

4、如条款1-3中的任何一个条款所述的方法,其中该方法由管理第一存储器区域的虚拟机执行,并且变量的实例被存储在由虚拟机管理的第一存储器区域外部的第二存储器区域中。

5、如条款1-4中的任何一个条款所述的方法,其中第一指令从特定类导出,并且确定对由接收者保持的变量的访问是否是许可的是基于该特定类是否具有访问由接收者保持的变量的许可。

6、如条款1-5中的任何一个条款所述的方法,还包括响应于确定对由接收者保持的变量的访问是不许可的来生成访问错误。

7、如条款1-6中的任何一个条款所述的方法,其中该方法由虚拟机执行,并且虚拟机通过生成由虚拟机正在其上执行的硬件支持的机器代码指令来执行受约束的操作。

8、如条款7所述的方法,其中受约束操作包括不由硬件本身支持的至少一个受约束操作,并且还包括使用由硬件支持的机器代码指令的集合来合成受约束操作。

9、如条款1-8中的任何一个条款所述的方法,其中受约束操作执行以下操作中的一个或多个:放宽的操作、易失性操作、懒惰的操作、比较和设置操作或者获得和设置操作。

10、如条款1-9中的任何一个条款所述的方法,其中第二指令包括用于特定受约束函数的一个或多个参数。

11、如条款10所述的方法,其中该一个或多个参数包括以下各项中的一个或多个:变量的实例所位于的到接收者的实例的数组索引或者要存储到变量的实例的特定存储器位置的值。

12、如条款1-11中的任何一个条款所述的方法,还包括:执行检查,以确保由第二指令指定的接收者的实例与由声明信息识别的接收者的类型匹配。

13、如条款1-12中的任何一个条款所述的方法,其中使特定受约束函数相对于特定存储器位置被执行包括经由一条或多条硬件指令调用与任意存储器位置交互的函数。

14、如条款1-13中的任何一个条款所述的方法,其中与任意存储器位置交互的函数不执行类型检查。

15、如条款1-14中的任何一个条款所述的方法,其中变量句柄实例的一个或多个受约束函数包括对应于一个或多个原子算术运算的函数。

16、如条款1-15中的任何一个条款所述的方法,其中变量句柄实例从扩展抽象变量句柄类的变量句柄子类派生,该抽象变量句柄类为一个或多个受约束函数提供一个或多个接口,并且变量句柄子类对特定访问类型实现一个或多个受约束函数。

17、如条款1-16中的任何一个条款所述的方法,还包括:响应于确定由第二指令调用的特定受约束函数是签名多态的,基于表示对特定受约束函数的调用的引用的描述符来自动启用预定义函数,其中该预定义函数执行类型检查并且启用执行该特定受约束函数的变量句柄的函数。

18、如条款17所述的方法,其中确定特定受约束函数是签名多态的通过检查抽象变量句柄类的运行时表示中的与该特定受约束函数相关联的一个或多个标志来执行。

19、如条款17-18中的任何一个条款所述的方法,其中作为基于声明信息创建变量句柄实例的结果,类型检查通过将一个或多个参数和对特定受约束函数的调用的返回类型与和用于变量句柄实例的特定受约束函数相关联的描述符进行比较来执行。

20、如条款17-19中的任何一个条款所述的方法,其中启用变量句柄实例的特定受约束函数至少包括将对常量池中的特定受约束函数的符号引用解析为表示运行时存储器中的该特定受约束函数的位置的具体存储器引用。

21、如条款20所述的方法,还包括在运行时存储器中高速缓存特定受约束函数的位置,以便在对该变量句柄实例的该特定受约束函数的未来调用期间重用。

22、如条款17-20中的任何一个条款所述的方法,其中变量句柄实例的特定受约束函数至少执行一个或多个安全检查,该一个或多个安全检查如果成功,则使得访问该特定存储器位置并且执行与该特定受约束函数对应的受约束操作的不安全函数被启用。

23、一种方法,包括:生成被配置为通过一个或多个存储器防护操作提供对存储器的安全访问的对象;通过该对象,接收指示存储器位置并且指定该一个或多个存储器防护操作中的要相对于该存储器位置执行的特定存储器防护操作的调用;使得该特定存储器防护操作相对于该存储器位置被执行;以及其中该方法由一个或多个处理器执行。

24、如条款23所述的方法,其中对象与定义一个或多个存储器防护操作的类相关,但是不包括用于多个不同变量种类中的任何特定变量种类的一个或多个存储器防护操作的实现。

25、如条款24所述的方法,其中所述多个不同变量种类包括以下的一个或多个:实例字段变量、静态字段变量、数组元素变量或堆外变量。

26、如条款24-25中的任何一个条款所述的方法,其中对象是从该类扩展并且相对于多个不同变量种类中的特定变量种类实现一个或多个存储器防护操作的子类的实例。

27、如条款24-25中的任何一个条款所述的方法,还包括:生成作为该类的实例的第二对象;通过该第二对象,接收指示第二存储器位置并且指定一个或多个存储器防护操作中的要相对于第二存储器位置执行的第二特定存储器防护操作的第二调用,其中存储器中的存储器位置表示第一变量类型并且第二存储器位置表示第二变量类型,其中第一变量类型不同于第二变量类型;以及使得特定存储器防护操作相对于第二存储器位置被执行。

28、如条款27所述的方法,其中第一变量类型和第二变量类型是以下的一个或多个:整型、长整型、浮点型、双精度型或对象引用。

29、如条款23-28中的任何一个条款所述的方法,其中生成对象将该对象绑定到第一变量类型,其中调用指定访问存储器位置以基于第二变量类型执行特定存储器防护操作,并且该方法还包括:确定第一变量类型是否与第二变量类型匹配,其中响应于确定第一变量类型与第二变量类型匹配,使得该特定存储器防护操作相对于该存储器位置被执行;响应于确定第一变量类型不匹配第二变量类型,执行以下操作中的一个或多个:生成错误,或者使第一变量类型符合第二变量类型,然后使得该特定存储器防护操作相对于该存储器位置被执行。

30、如条款23-29中的任何一个条款所述的方法,其中生成对象包括执行检查以确定对位置的访问是否是许可的,并且该方法还包括通过该对象接收多个调用以在不用重复检查的情况下执行存储器防护操作。

31、如条款23-30中的任何一个条款所述的方法,其中特定存储器防护操作通过除了向存储器位置发出一条或多条加载或存储指令之外还向存储器位置至少发出由一个或多个处理器本机支持的一条或多条存储器屏障指令来执行。

32、如条款31所述的方法,其中特定存储器防护操作通过组合由一个或多个处理器本机支持的一条或多条存储器屏障指令中的两条或更多条存储器屏障指令来执行。

33、如条款23-32中的任何一个条款所述的方法,还包括:通过该对象接收第二调用,该第二调用指定与要相对于该存储器位置执行的、不同于该特定存储器防护操作的第二存储器防护操作;以及使得第二存储器防护操作相对于该存储器位置被执行。

34、如条款23-33中的任何一个条款所述的方法,其中使得特定存储器防护操作被执行涉及启用与执行一个或多个安全检查的对象相关联的代码,其中该一个或多个安全检查包括以下的一个或多个:确定传递到调用中的一个或多个参数匹配对象预期的一组参数、确定对传递到调用中的存储器位置的引用是否为空、或者确定传递到调用中的数组索引是否有效。

35、如条款23-34中的任何一个条款所述的方法,其中存储器防护操作包括用于一个或多个访问模式的操作,其中该一个或多个访问模式包括以下的一个或多个:放宽的读取、放宽的写入、易失性读取、易失性写入、原子读取、原子写入或原子更新。

36、一个或多个非暂态计算机可读介质,该一个或多个非暂态计算机可读介质存储指令,当该指令由一个或多个计算设备执行时,使得如条款1-35中的任何一个条款所述的方法被执行。

37、一种系统,该系统包括一个或多个计算设备,该一个或多个计算设备包括至少部分地由计算硬件实现的、被配置为实现如条款1-35中的任何一个条款所述的方法的步骤的组件。

7.0第二附加公开

下文公开本发明的附加实施例。此外,本节包括从针对Java虚拟机(JVM)编写的各种规范拉取的附加描述,在一些实施例中,JVM被用来实现本文所讨论的技术。然而,不要求实施例使用本节中讨论的特征中的任何特征或所有特征。

本文描述了用于通过使用支持各种不同存储器访问模式的“句柄”来提供对存储器位置的安全和高效的外来(exotic)访问的技术。句柄提供了关于对这些存储器位置的操作的若干级别的抽象。这些抽象中的一个抽象涉及存储器位置本身的性质,因为存储器位置可以存储各种类型的字段或其它数据结构中的任何类型的字段或其它数据结构。存储器位置可以是由语言运行环境(language runtime)(诸如虚拟机或解释器)管理的位置和/或“不受管理”的位置,因为它们由语言运行环境之外的存储器管理器(例如,由操作系统分配给不在语言运行环境内运行的应用的存储器)管理。这些抽象中的另一个抽象涉及句柄支持的存储器访问模式,因为句柄不将存储器位置锁定到具体的存储器访问模式(如通常在变量的情况下发生的),而是可以提供由解释器允许并且由底层硬件支持的任何存储器访问模式。

根据实施例,用于利用这些句柄的方法包括生成被配置为通过不同的存储器防护操作提供对存储器的安全访问的对象结构。例如,对象结构可以是(如本文所描述的)VarHandle类或者被抽象地定义以提供对任意类型的数据结构的任意访问模式的任何其它合适的类的实例。随后描述存储器防护操作的示例。虽然为了方便起见在本文中句柄被描述为对象结构,但是将理解的是,取决于编程语言,句柄可以是任何合适的构造,并且不一定需要被严格限制为对象或甚至面向对象的构造。

方法还包括通过对象结构接收指示存储器中的位置并且指定要相对于所指示的存储器中的位置执行存储器防护操作中的哪一个存储器防护操作的调用(诸如对由它的类定义的方法的调用)。调用可以使用各种约定来指示位置,诸如通过引用由实现该方法的语言运行环境(例如,虚拟机或解释器)管理的位置、通过指定另一对象的先前声明的和/或实例化的字段、通过引用静态字段、通过引用数组元素或者通过引用不由语言运行环境管理的位置,诸如尚未分配给语言运行环境的位置或者由不同于语言运行环境或独立于语言运行环境的过程分配和/或管理的位置(例如,“堆外”位置)。

方法还包括使得指定的存储器防护操作相对于所指示的位置被执行。例如,由对象结构的类定义和/或以其它方式与对象结构的类相关联的逻辑可以被执行,以执行存储器防护操作,和/或向执行硬件发出使得该硬件执行存储器防护操作的低级指令。

如本文所使用的,存储器防护操作(也被称为存储器屏障、存储器围栏或围栏指令)是使得中央处理单元(CPU)或编译器对在屏障指令之前和之后发出的存储器操作实施排序约束的一种类型的屏障指令。存储器屏障可以是必要的,因为许多现代CPU采用可能导致无序执行的性能优化。存储器操作(加载和存储)的这种重新排序通常在单个执行线程内不被注意,但是除非被仔细控制,否则可以造成并发程序和设备驱动程序中的不可预测的行为。顺序约束的确切本质是依赖硬件的并且由架构的存储器排序模型定义。一些架构提供了用于实施不同排序约束的多个屏障。

在实施例中,生成对象结构包括执行检查,以确定对位置和/或与该位置相关联的数据类型的访问在当前执行上下文内是否是许可的。方法还包括通过对象结构接收多个调用,以在不重复检查的情况下执行存储器防护操作。

在实施例中,方法由基于例如较低级代码(诸如Java字节码或从人类可读源代码编译的任何其它代码)中的指令的语言运行环境执行。方法还包括基于确定由在其上实现语言运行环境的硬件本机支持的机器存储器架构和/或指令集来确定如何执行指定的存储器防护操作。方法还包括选择指令集中执行指定的存储器防护操作的具体存储器防护指令。

在实施例中,方法还包括生成作为类的实例的多个对象结构。方法还包括通过这多个对象结构接收调用以相对于不同类型的数据结构(例如,不同的原语类型、不同的值类型等)执行存储器防护操作。调用指定数据结构和/或数据结构的类型。方法还包括基于类中的逻辑或与类相关联的逻辑来使得指定的存储器防护操作被执行。从而,单个类提供对各种类型的数据结构的抽象句柄。

在实施例中,方法还包括通过对象结构接收指定要相对于同一存储器位置执行的不同存储器防护操作的多个调用,不同的存储器防护操作包括具有不同存储器访问模式(例如,易失性访问、懒惰的访问、放宽的访问、正常访问等的任何组合)的至少两个操作。从而,单个类为同一存储器位置提供多个存储器访问模式。

根据实施例,用于利用所描述的句柄的另一方法(在一些情况下,但不一定在所有情况下,其构成先前描述的方法的具体实例)包括识别创建变量句柄对象的第一指令。这样的指令的一个示例可以是变量声明,该变量声明包括对“查找”方法(诸如类似于本文所描述的VarHandles.Lookup之类的方法)的调用或者对例如Java字节码中的这样的方法的等价启用的调用。第一指令包括至少指示变量句柄对象被配置为提供对其的访问的结构类型的配置信息。例如,配置信息可以指定以下各项中的一个或多个的任何合适的组合:对象类或数据结构类型、与为对象类或数据结构类型定义的字段对应的字段名称,和/或字段类或类型。作为另一个示例,配置信息可以引用不受管理的存储器位置,该不受管理的存储器位置包括尚未分配给正在执行第一指令的语言运行环境(诸如虚拟机)的存储器位置。

方法还包括,响应于第一指令并且基于配置信息来执行一个或多个检查,以确定对结构类型的访问是否是许可的。例如,可以执行访问检查,以确定与结构类型相关联的各种规则或声明(诸如“私有”或“公有”声明)是否许可从当前执行上下文(诸如在其中创建变量句柄对象的函数或方法)内访问结构类型的实例。可以被执行的另一示例检查是确定结构类型是否实际存在。可选地,方法还包括如果检查中的一个或多个检查失败则抛出错误。例如,可以抛出指示不能找到被引用的结构类型的错误,或者根据与结构类型相关联的安全规则对该结构类型的访问将不被许可,诸如如果结构类型是私有的话。

方法还包括:如果对结构类型的访问是许可的,则创建变量句柄对象。该变量句柄对象包括被配置为对结构类型的存储器位置执行原子操作的方法。这样的原子操作的示例可以包括例如存储器防护操作或直接与可以由其上实现语言运行环境的硬件支持的低级硬件指令对应的操作。可选地,操作可以包括由语言运行环境被配置为对结构类型使用的默认存储器防护模式不支持的至少一个存储器防护操作。例如,存储器访问可以是由字段如何被声明而暗示的模式。即,如果字段类型被声明为“volatile(易失性)”,则在操作的正常过程期间语言运行环境将仅能够使用易失性存储器访问模式来执行对该类型的字段进行读取或写入的指令。相反,变量句柄对象将包括能够访问抽象地存储该字段的存储器位置的方法,而不限于任何暗示的存储器访问模式。随后将更详细地描述存储器防护操作和存储器访问模式。

方法还包括识别调用变量句柄对象的方法中的一个方法的第二指令。本文描述这样的第二指令的示例,诸如“get(获得)”、“setVolatile(易失性设置)”等等。第二指令引用数据结构类型的实例或包括数据结构类型的对象的实例。例如,第二指令可以是包含对象、数组索引或字段本身的任何合适的组合。第二结构可选地包括用于要被执行的对应原子操作的一个或多个参数,诸如用于要读取/写入的存储器位置的数组索引、写入存储器位置的值等等。在实施例中,第一指令和第二指令可以是同一指令的一部分,或者可以是分开的指令。

方法还包括识别由所引用的实例指示的存储器位置。例如,可以提供各种查找机制以在存储器中定位被引用的字段,或者定位包括所指示的结构类型的字段的所引用对象实例的开始位置,以及确定该字段离该开始位置的偏移。可选地,可以执行检查,以确保该字段具有与创建变量句柄对象时所指示的相同的结构类型。

方法还包括使得对应的原子操作相对于存储器位置被执行。例如,语言运行环境可以通过确定实现原子操作的具体的低级硬件指令(如果可用的话)并且向硬件发送该指令来使得对应的原子操作被执行。作为另一个示例,在Java编程语言中,语言运行环境可以执行“不安全”方法,以直接访问存储器位置。

在实施例中,变量句柄对象的方法包括与以下各项中的一个或多个对应的方法:放宽的设置、放宽的获得、易失性获得、易失性设置、获取获得、释放设置、比较和设置、获得和设置、原子加法和/或原子减法。

在实施例中,存储器位置是在分配给正在执行第一指令和第二指令的语言运行环境的存储器的外部的不受管理的存储器位置。在实施例中,存储器位置是受管理的存储器位置,其可以以各种形式是静态字段、实例字段、数组、数组元素或其它种类的基于存储器的变量。

在实施例中,可以提供定义用于访问方法的接口和用于实现方法的逻辑的类类型。可以为所有变量句柄对象提供单个类类型,而不管它们对其提供访问的数据结构的类型。或者,可以为静态文件和/或实例文件、数组、数组元素、数字原语(primitive)和/或其它类型的变量提供不同的类类型。

在实施例中,变量句柄对象的方法中的一个或多个方法被定义为具有多态签名,如在其它节中所描述的。在实施例中,变量句柄对象的方法中的一个或多个方法被定义为具有通用签名,并且该方法还包括对于例如编译后的Java字节码中的变量句柄对象的方法中的一些或所有方法在调用点处具体化通用签名。

在实施例中,方法还包括识别参考潜在不同的对象或数据结构实例调用变量句柄对象的不同方法的多条指令,以及响应于该多条指令使得取决于所引用的实例不同的原子操作相对于同一存储器位置和/或相对于不同的存储器位置被执行。

在实施例中,方法还包括部分地基于该一个或多个检查来确定由变量句柄对象支持的原子操作。例如,如果数据结构类型已被声明为“final(最终的)”,则将不支持设置操作,并且可能(甚至潜在地可能)更新存储器的任何操作将抛出异常。

在其它方面,公开了用于实现本文所描述的技术的计算设备的系统和计算机可读介质。

invokedynamic指令

invokedynamic指令简化并且潜在地改进用于JVM上的动态语言的编译器和运行时系统的实现。invokedynamic指令通过允许语言实现者定义定制的链接行为来做到这一点。这与其中特定于Java类和接口的链接行为由JVM硬连线的其它JVM指令(诸如invokevirtual)形成对比。

invokedynamic指令的每个实例被称为动态调用点(dynamic call site)。动态调用点最初处于未链接状态,这意味着不存在要启用的为调用点指定的方法。如之前提到的,动态调用点通过引导(bootstrap)方法被链接到方法。动态调用点的引导方法是编译器为动态类型语言指定的由JVM调用一次以链接站点的方法。从引导方法返回的对象永久地确定调用点的行为。

invokedynamic指令包含(以与用于其它启用指令的格式相同的格式的)常量池索引。该常量池索引引用CONSTANT_InvokeDynamic条目。该条目指定引导方法(CONSTANT_MethodHandle条目)、动态链接方法的名称,以及对动态链接方法的调用的变元类型和返回类型。

以下是invokedynamic指令的示例。在这个示例中,运行时系统通过使用引导方法Example.mybsm来将由该invokedynamic指令指定的动态调用点(其为+,加法运算符)链接到IntegerOps.adder方法。方法adder和mybsm在上面定义:

注意:这些节中的字节码示例使用ASM Java字节码操纵和分析框架的语法。

用invokedynamic指令启用动态链接的方法包括以下步骤:

1.定义引导方法

在运行时,当JVM首次遇到invokedynamic指令时,它调用引导方法。该方法将由invokedynamic指令指定的名称与应当被执行的代码(目标方法)链接,该代码由方法句柄引用。如果JVM再次执行相同的invokedynamic指令,则它不调用引导方法;它自动调用链接的方法句柄。

引导方法的返回类型可以是java.lang.invoke.CallSite。CallSite对象表示invokedynamic指令和它链接到的方法句柄的链接状态。

引导方法采用三个或更多个参数:MethodHandles.Lookup对象,它是用于在invokedynamic指令的上下文中创建方法句柄的工厂;String对象,在动态调用点中提到的方法名;以及MethodType对象,动态调用点的解析的类型签名。

可选地,对invokedynamic指令的一个或多个附加静态变元。从常量池汲取(draw)的这些变元旨在帮助语言实现者安全、紧凑地将对引导方法有用的附加元数据进行编码。原则上,名称和确切的变元是冗余的,因为每个调用点可以被给予其自己的唯一的引导方法。然而,这样的做法可能产生大的类文件和常量池。对于引导方法的示例,请参见上文。

2.指定常量池条目

如之前所提到的,invokedynamic指令包含对常量池中具有标签CONSTANT_InvokeDynamic的条目的引用。该条目包含对常量池中其它条目的引用和对属性的引用。本节简要描述由invokedynamic指令使用的常量池条目。对于更多信息,请参见java.lang.invoke包文档和Java虚拟机规范(The Java Virtual Machine Specification)。

示例常量池

以下是来自用于类Example的常量池的摘录(excerpt),其包含将方法+与Java方法adder链接的引导方法Example.mybsm:

在这个示例中用于invokedynamic指令的常量池条目包含三个值:

CONSTANT_InvokeDynamic tag

Unsigned short of value 0

Constant pool index#234.

值0是指存储在BootstrapMethods属性中的指定符数组中的第一引导方法指定符。引导方法指定符不在常量池表中;它们包含在这个分开的指定符数组中。每个引导方法指定符包含对作为引导方法本身的CONSTANT_MethodHandle常量池条目的索引。

以下是来自同一常量池的摘录,其示出了BootstrapMethods属性,该属性包含引导方法指定符的数组:

用于引导方法mybsm方法句柄的常量池条目包含三个值:

CONSTANT_MethodHandle tag

Unsigned byte of value 6

Constant pool index#232.

值6是子标记REF_invokeStatic。

3.使用invokedynamic指令

以下字节码使用invokedynamic指令来调用引导方法mybsm,该引导方法mybsm将动态调用点(+,加法运算符)链接到方法adder。这个示例使用+方法将数字40和2相加(为了清晰,已插入换行符):

前四条指令将整数40和2放在栈上,并且以java.lang.Integer包装器类型将它们装箱。第五条指令启用动态方法。这条指令参考具有CONSTANT_InvokeDynamic标签的常量池条目:

这个条目中的CONSTANT_InvokeDynamic标签之后跟着四个字节:

前两个字节形成对引用引导方法指定符的CONSTANT_MethodHandle条目的引用:

如之前所提到的,对引导方法指定符的该引用不在常量池表中;它包含在由类文件属性namedBootstrapMethods定义的分开的数组中。引导方法指定符包含对作为引导方法本身的CONSTANT_MethodHandle常量池条目的索引。

在该CONSTANT_MethodHandle常量池条目之后有三个字节:第一个字节是子标签REF_invokeStatic。这意味着该引导方法将为静态方法创建方法句柄;注意到该引导方法正在将动态调用点与静态Java方法adder链接。接下来的两个字节形成表示要为其创建方法句柄的方法的CONSTANT_Methodref条目:

在这个示例中,引导方法的完全限定名称是Example.mybsm;变元类型为MethodHandles.Lookup、String和MethodType;而返回类型是CallSite。

接下来的两个字节形成对CONSTANT_NameAndType条目的引用:

该常量池条目指定方法名(+)、变元类型(两个Integer实例)以及动态调用点的返回类型(Integer)。

在这个示例中,给出具有箱化的整数值的动态调用点,其与最终目标(adder方法)的类型完全匹配。实际上,变元和返回类型不需要确切地匹配。例如,invokedynamic指令可以在JVM栈上传递其操作数中的任一个或两个作为原始整型值。任一个或两个操作数还可以是未类型化(untyped)的对象值。invokedynamic指令还可以接收其结果作为原始整型值或未类型化的对象值。在任何情况下,mybsm的dynMethodType变元都将准确地描述invokedynamic指令所需的方法类型。

独立地,adder方法还可以被赋予原始的或未类型化的变元或返回值。引导方法负责弥补theDynMethodType和adder方法的类型之间的任何差异。如代码中所示,这容易用对目标方法的asType调用来完成。

解构MethodHandle

本节使用设置非静态字段的值的具体示例来解构(deconstruct)MethodHandle的确切启用如何被编译、链接和执行。通过编译和执行基于MethodHandle的jmh微基准来获得字节码和内联踪迹的示例。

尽管名称如此,但是Methodhandle可以引用类的底层静态字段或实例字段。在OpenJDK实现中,在执行了适当的安全检查之后,对这样的句柄的启用产生对sun.misc.Unsafe上的方法的对应调用。例如,如果字段是被标记为易失性的引用类型(非原语类型),则Unsafe.putObjectVolatile方法将被启用。

如果对MethodHandle的这样的引用被保持在静态最终字段中,则运行环境应当能够对该引用的启用和当内联发生时它所保持的内容进行常量折叠。在这样的情况下,可能令人惊讶的是,与对Unsafe或get/putfield字节码指令直接启用方法相比,生成的机器代码可以具有竞争力。

通过分别对Unsafe、putObject、putOrderedObject和compareAndSwapObject启用适当的方法,扩展MethodHandle实现以支持用于放宽的、懒惰的以及比较和设置原子操作的句柄是直接的。

给出对类(例如,Receiver)上的非静态字段(例如,具有类型Value的)“vfield”的写访问的MethodHandle可以如下获得:

MethodHandles.Lookup lookup=...

MethodHandler setterOfValueOnReceiver=

lookup.findSetter(Receiver.class,"vfield",Value.class);

然后,该MethodHandle的确切启用将设置Receiver的实例上的字段“vfield”的值:

Receiver r=...

Value v=...

setterOfValueOnReceiver.invokeExact(r,v);

注意到接收者实例作为第一参数被传递给invokeExact方法。接收者提供从其访问它保持的字段“vfield”的值的基本位置(因为Java中直接的l值以及通过字段或数组元素的引用的传递没有被支持,所以一对接收者和值是必要的)。

MethodHandle.invokeExact方法被声明为多态签名方法:

public final native@PolymorphicSignature Object invokeExact(Object...args)throws Throwable;

当javac将启用编译成签名多态方法时,它使用符号类型描述符作为方法签名,该符号类型描述符从调用者的实际参数和返回类型而不是从方法声明的方法签名导出。

invokevirtual指令的示例如下所示:

13:invokevirtual#12//Methodjava/lang/invoke/MethodHandle.invokeExact:(Lvarmh/VolatileSet AndGetTest$Receiver;Lvarmh/VolatileSetAndGetTest$Value;)V

注意到方法签名接受两个变元,即类Receiver和类Value的实例,并且返回void,该签名被称为符号类型描述符。

当句柄被常量折叠时,这样的启用的内联踪迹如下:

启用包括两个阶段。第一阶段执行高效的运行时安全检查,以确定在调用点处编码的符号类型描述符是否与MethodHandle的方法类型描述符完全匹配。第二阶段执行写入访问,在这种情况下是对易失性字段的写入访问。

在动态生成的类上,MethodHandler.invokeExact启用被内部链接(参见节“invokeExact启用的链接”)到静态方法invokeExact_MT,该静态方法invokeExact_MT的字节码是:

用于invocationExact_MT的参数如下:

MethodHandle实例、setterOfValueOnReceiver;传递给invokeExact方法的参数(r,v);最后是当调用点链接时附加的调用点的符号类型描述符。

注意到在该时间点处,引用参数类型被擦除为Object,因此声明该静态方法的类可以被共享,以用于利用擦除为相同签名的不同方法类型描述符的启用。

该方法首先执行方法类型描述符检查,并且如果该方法类型描述符检查失败则抛出异常,否则继续是安全的,因为已知调用点的变元类型和返回类型是正确的并且完全匹配。接下来,利用传递给invokeExact方法的相同参数来启用MethodHandle实例上的invokeBasic。

invokeBasic的启用被内部链接到对应于MethodHandle的编译的lambda形式的动态生成的类上的静态方法putObjectVolatileFieldCast。MethodHandle的LambdaForm的vmentry字段是表征方法putObjectVolatileFieldCast的MemberName,方法putObjectVolatileFieldCast的字节码是:

第一个参数是MethodHandle实例并且后续参数是传递给invokeExact方法的那些参数(r,v)。MethodHandle实例是保持要在这个方法结束时与Unsafe.putObjectVolatile的启用一起使用的字段偏移的直接句柄。在该启用之前:执行安全检查,以确保接收者实例不为空;并且执行值实例到值(字段)类型的实例的转换检查,以确保运行时编译器具有足够的信息来执行类型剖析。注意到这不是类型安全所必需的,因为这样的安全检查已经由invokeExact_MT方法执行;观察接收者实例的类型不需要转换到接收者类型的实例。

invokeExact启用的链接

MethodHandler.invokeExact的启用经由对返回表征要被链接到的Java方法的MemberName的Java方法的来自VM的向上调用被内部链接。这种对于VM来说静态已知的被向上调用的Java方法是MethodHandleNatives.linkMethod:

static MemberName linkMethod(Class<?>callerClass,int refKind,

Class<?>defc,String name,Object type,Object[]appendixResult)

“type”参数是对应于符号类型描述符的MethodType或String的实例。“appendixResult”被用来返回可选的额外参数,该参数必须与由MemberName表征的方法的最后一个参数匹配,并且将被永久地附加在被链接的调用点处。

在启用invokeExact时,VM栈已经包含三个值,并且VM将向调用栈添加一个附加参数,以使得参数如下:MethodHandle实例,setterOfValueOnReceiver;参数(r,v);最后是作为“appendixResult”的第一个元素的附加参数,它是调用点的符号类型描述符。

MethodHandle Class

public abstract class MethodHandle

extends Object

方法句柄是具有变元或返回值的可选变换的、对底层方法、构造函数、字段或类似低级操作的类型化的、可直接执行的引用。这些变换是相当通用的,并且包括诸如转换、插入、删除和替换之类的模式。

方法句柄内容

方法句柄根据它们的参数和返回类型被动态地类型化和强类型化。它们不通过其底层方法的名称或定义类来区分。必须使用匹配方法句柄自己的类型描述符的符号类型描述符来启用方法句柄。

每个方法句柄经由type访问器报告其类型描述符。该类型描述符是MethodType对象,它的结构是一系列类,该一系列类中的一个类是方法的返回类型(如果没有,则是void.class)。

方法句柄的类型控制它接受的启用的类型,以及应用于它的变换的种类。方法句柄包含一对名为invokeExact和invoke的特殊启用者(invoker)方法。两个启用者方法都提供对如通过变元和返回值的变换所修改的、方法句柄的底层方法、构造函数、字段或其它操作的直接访问。两个启用者都接受与方法句柄自己的类型完全匹配的调用。普通的、不确切的启用者还接受一系列其它调用类型。

方法句柄是不可变的并且不具有可见状态。当然,它们可以被绑定到展现状态的底层方法或数据。对于Java存储器模型,任何方法句柄将表现得好像它的(内部)字段中的所有字段都是最终变量。这意味着对应用可见的任何方法句柄将总是被完全形成。即使方法句柄通过数据竞争中的共享变量发布,也是如此。

方法句柄不能由用户子类化。实现可以(或者可能不)创建可以经由Object.getClass操作可见的MethodHandle的内部子类。程序员不应当从方法句柄的具体类得出关于该方法句柄的结论,因为方法句柄类层次结构(如果有的话)可以随时间或者跨来自不同供应商的实现而改变。

方法句柄编译

名为invokeExact或invoke的Java方法调用表达式可以启用来自Java源代码的方法句柄。从源代码的角度来看,这些方法可以采用任何变元,并且它们的结果可以被转换为任何返回类型。形式上,这是通过给予启用者方法Object返回类型和可变元数(variable arity)Object变元来完成的,但是它们具有将这种启用的自由度直接连接到JVM执行栈的被称为签名多态性的附加特性。

与虚方法通常一样,对invokeExact和invoke的源级调用编译为invokevirtual指令。较不同寻常的是,编译器必须记录实际的变元类型,并且可以不对变元执行方法启用转换。相反,编译器必须根据变元自己的未转换类型把它们推送到栈上。方法句柄对象本身在变元之前被推送到栈上。然后编译器用描述变元和返回类型的符号类型描述符来调用方法句柄。

为了发出完整的符号类型描述符,编译器还必须确定返回类型。如果存在一个方法启用表达式的话,则这是基于对方法启用表达式的转换,否则如果启用是表达式则返回类型为Object,否则如果启用是语句则返回类型为void。转换可以是转换到原语类型(但不是void)。

作为极端情况,未被转换的null变元被给予java.lang.Void的符号类型描述符。与Void类型的歧义是无害的,因为除了空引用,不存在类型为Void的引用。

方法句柄启用

第一次执行invokevirtual指令时,它通过符号解析指令中的名称并且验证方法调用是静态合法的而被链接。对invokeExact和invoke的调用也是如此。在这种情况下,为了正确语法而检查由编译器发出的符号类型描述符并且解析它所包含的名称。因此,只要符号类型描述符在语法上良好形成并且类型存在,则启用方法句柄的invokevirtual指令将总是链接。

当在链接之后执行invokevirtual时,首先由JVM检查接收方法句柄的类型,以确保其匹配符号类型描述符。如果类型匹配失败,则意味着调用者启用的方法在被启用的单独的方法句柄上不存在。

在invokeExact的情况下,启用的类型描述符(在解析符号类型名称之后)必须与接收方法句柄的方法类型完全匹配。在普通的、不确切的invoke的情况下,解析后的类型描述符必须是接收者的asType方法的有效变元。因此,普通的invoke比invokeExact是更被许可的。在类型匹配之后,对invokeExact的调用直接并且立即启用方法句柄的底层方法(或其它行为,视情况而定)。

如果由调用者指定的符号类型描述符与方法句柄自己的类型完全匹配,则对普通的invoke的调用与对invokeExact的调用的工作方式相同。如果存在类型不匹配,则invoke尝试调整接收方法句柄的类型(就像通过对asType的调用一样)以获得完全可调用的方法句柄M2。这允许在调用者和被调用者之间的更强大的方法类型协商。(注意:调整后的方法句柄M2不是可直接观察的,因此不需要实现来使其具体化)。

启用检查

在典型的程序中,方法句柄类型匹配通常将成功。但是如果匹配失败,则JVM将直接地(在invokeExact的情况下)或者就像通过对asType的失败的调用一样间接地(在invoke的情况下)抛出WrongMethodTypeException。

因此,在静态类型化的程序中可能显示为链接错误的方法类型不匹配可以在使用方法句柄的程序中显示为动态的WrongMethodTypeException。

因为方法类型包含“活的”Class对象,所以方法类型匹配考虑类型名称和类加载器二者。因此,即使方法句柄M在一个类加载器L1中创建并且在另一个L2中使用,方法句柄调用也是类型安全的,因为如在L2中解析的调用者的符号类型描述符与如在L1中解析的原始被调用者方法的符号类型描述符匹配。L1中的解析在M被创建并且其类型被指派时发生,而L2中的解析在invokevirtual指令被链接时发生。

除了类型描述符的检查之外,方法句柄调用其底层方法的能力也是不受限制的。如果方法句柄由可访问该方法的类对非公有方法形成,则得到的句柄可以在任何地方由接收到对它的引用的任何调用者使用。

与每次启用反射方法时检查访问的核心反射(Core Reflection)API不同,当创建方法句柄时执行方法句柄访问检查。在ldc(参见下文)的情况下,访问检查作为链接在常量方法句柄底层的常量池条目的一部分来执行。

因此,用于非公有方法的句柄或者用于非公有类中的方法的句柄一般应当保密。它们不应当被传递给不受信任的代码,除非从不受信任的代码使用它们是无害的。

方法句柄创建

Java代码可以创建直接访问该代码可访问的任何方法、构造函数或字段的方法句柄。这是经由反射的、基于能力的API(被称为MethodHandles.Lookup)来完成的。例如,静态方法句柄可以从Lookup.findStatic获得。还存在来自核心反射API对象的转换方法,诸如Lookup.unreflect。

类似于类和字符串,对应于可访问字段、方法和构造函数的方法句柄还可以在类文件的常量池中被直接表示为要由ldc字节码加载的常量。新类型的常量池条目CONSTANT_MethodHandle直接指向关联的CONSTANT_Methodref、CONSTANT_InterfaceMethodref或CONSTANT_Fieldref常量池条目。(关于方法句柄常量的详情,请参见Java虚拟机规范的节4.4.8和节5.4.3.5)。

通过来自具有可变元数修饰符位(0x0080)的方法或构造函数的查找或常量加载而产生的方法句柄具有对应的可变元数,就像它们是在asVarargsCollector的帮助下定义的一样。

方法引用可以参考静态或非静态方法。在非静态的情况下,方法句柄类型包括置于任何其它变元之前的显式接收者变元。在方法句柄的类型中,根据最初在其下请求该方法的类来类型化初始接收者变元。(例如,如果非静态方法句柄是经由ldc获得的,则接收者的类型是在常量池条目中命名的类)。

方法句柄常量经受与它们对应的字节码指令相同的链接时间访问检查,并且ldc指令将抛出对应的链接错误,如果字节码行为将抛出这样的错误的话。

作为其结果,对受保护成员的访问仅被限制于访问类或它的子类中的一个子类的接收者,并且访问类又必须是该受保护成员的定义类的子类(或包兄弟)。如果方法引用参考受保护的非静态方法或者当前包之外的类的字段,则接收者变元将被缩小为访问类的类型。

当启用虚方法的方法句柄时,总是在接收者中查找该方法(即,第一个变元)。

还可以创建具体虚方法实现的非虚(non-virtual)方法句柄。这些不基于接收者类型执行虚拟查找。这样的方法句柄模拟同一方法的invokespecial指令的效果。

使用示例

这是使用的一些示例:

Object x,y;String s;int i;

MethodType mt;MethodHandle mh;

MethodHandles.Lookup lookup=MethodHandles.lookup();

//mt is(char,char)String

mt=MethodType.methodType(String.class,char.class,char.class);

mh=lookup.findVirtual(String.class,"replace",mt);

s=(String)mh.invokeExact("daddy",'d','n');

//invokeExact(Ljava/lang/String;CC)Ljava/lang/String;

assertEquals(s,"nanny");

//weakly typed invocation(using MHs.invoke)

s=(String)mh.invokeWithArguments("sappy",'p','v');

assertEquals(s,"savvy");

//mt is(Object[])List

mt=MethodType.methodType(java.util.List.class,Object[].class);

mh=lookup.findStatic(java.util.Arrays.class,"asList",mt);

assert(mh.isVarargsCollector());

x=mh.invoke("one","two");

//invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;

assertEquals(x,java.util.Arrays.asList("one","two"));

//mt is(Object,Object,Object)Object

mt=MethodType.genericMethodType(3);

mh=mh.asType(mt);

x=mh.invokeExact((Object)1,(Object)2,(Object)3);

//invokeExact(Ljava/lang/Object;Ljava/lang/Object;

Ljava/lang/Object;)Ljava/lang/Object;

assertEquals(x,java.util.Arrays.asList(1,2,3));

//mt is()int

mt=MethodType.methodType(int.class);

mh=lookup.findVirtual(java.util.List.class,"size",mt);

i=(int)mh.invokeExact(java.util.Arrays.asList(1,2,3));

//invokeExact(Ljava/util/List;)I

assert(i==3);

mt=MethodType.methodType(void.class,String.class);

mh=lookup.findVirtual(java.io.PrintStream.class,"println",mt);

mh.invokeExact(System.out,"Hello,world.");

//invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V

对invokeExact或普通的invoke的上面的调用中的每个调用生成具有在以下注释中指示的符号类型描述符的单个invokevirtual指令。在这些示例中,辅助器方法assertEquals被假设是对它的变元调用Objects.equals并且声称结果为真的方法。

异常

方法invokeExact和invoke被声明为抛出Throwable(可抛出),这就是说,不存在对于方法句柄可以抛出什么的静态限制。由于JVM不区分已检查和未检查的异常(当然,除了通过它们的类区分之外),因此将已检查的异常归因于方法句柄启用对字节码形状没有特别的影响。但是在Java源代码中,执行方法句柄调用的方法必须显式抛出Throwable,否则必须本地捕获所有的Throwable,从而仅重新抛出在上下文中合法的Throwable,并且包装非法的Throwable。

签名多态性

invokeExact和普通的invoke的不同寻常的编译和链接行为由术语签名多态性引用。如在Java语言规范中所定义的,签名多态方法是可以与大范围的调用签名和返回类型中的任何调用签名和返回类型一起操作的方法。

在源代码中,不管所请求的符号类型描述符如何,对签名多态方法的调用将编译。通常,Java编译器针对命名的方法发出具有给定符号类型描述符的invokevirtual指令。不同寻常的部分是符号类型描述符从实际的变元和返回类型派生,而不是从方法声明派生。

当JVM处理包含签名多态调用的字节码时,它将成功链接任何这样的调用,而不管其符号类型描述符如何。(为了保持类型安全,JVM将利用合适的动态类型检查来保卫这样的调用,如其他地方所描述的)。

要求包括编译器后端的字节码生成器发出用于这些方法的未变换的符号类型描述符。要求确定符号链接的工具接受这样的未变换的描述符,而无需报告链接错误。

方法句柄和核心反射API之间的互操作

通过在Lookup API中使用工厂方法,由核心反射API对象表示的任何类成员可以被转换为行为上等价的方法句柄。例如,可以使用Lookup.unreflect将反射方法转换为方法句柄。得到的方法句柄一般提供对底层类成员的更直接和高效的访问。

作为特殊情况,当核心反射API被用来查看该类中的签名多态方法invokeExact或普通的invoke时,它们看起来就像平常的非多态方法。如由Class.getDeclaredMethod看到的,它们的反射外观不受该API中的它们的具体状态影响。例如,Method.getModifiers将确切地报告对于任何类似声明的方法所需的那些修饰符位,在这种情况下包括native和varargs位。

与任何反射方法一样,这些方法(当被反射时)可以经由java.lang.reflect.Method.invoke被启用。然而,这样的反射调用不会导致方法句柄启用。这样的调用如果被传递了所需的变元(类型Object[]的单个变元),则将忽略该变元并且将抛出UnsupportedOperationException。

由于invokevirtual指令可以在任何符号类型描述符下本机启用方法句柄,因此这种反射视图与这些方法经由字节码的正常呈现冲突。因此,这两个本机方法在通过Class.getDeclaredMethod被反射查看时可以仅被视为占位符。

为了获得用于特定类型描述符的启用者方法,使用MethodHandles.exactInvoker或MethodHandles.invoker。Lookup.findVirtual API还能够为任何指定的类型描述符返回用于调用invokeExact或普通的invoke的方法句柄。

方法句柄和Java泛型之间的互操作

可以对利用Java泛型类型声明的方法、构造函数或字段获得方法句柄。与核心反射API一样,方法句柄的类型将从源级类型的擦除构建。当方法句柄被启用时,它的变量的类型或返回值转换类型可以是泛型类型或类型实例。如果这种情况发生,则编译器将在它构造用于invokevirtual指令的符号类型描述符时用它们的擦除来替换那些类型。

因为类似函数(function-like)的类型和参数化的Java类型之间存在三方面不匹配,所以方法句柄不用Java参数化的(泛型)类型来表示它们的类似函数的类型。

方法类型涉及(range over)从无变元到多达允许的变元的最大数目的所有可能的元数。泛型不是可变变元的(variadic),因此不能表示这一点。

方法类型可以指定Java泛型类型不能涉及的原语类型的变元。

方法句柄之上的更高阶函数(组合器)常常是跨大范围的函数类型(包括多个元数的函数类型)通用的。用Java类型参数来表示这种通用性是不可能的。

Lookup工厂方法

对Lookup对象的工厂方法与用于方法、构造函数和字段的所有主要用例对应。由工厂方法创建的每个方法句柄是特定字节码行为的功能等价物。(字节码行为在Java虚拟机规范的节5.4.3.5中描述。)这是这些工厂方法与得到的方法句柄的行为之间的对应关系的总结:

这里,类型C是正在被搜索成员的类或接口,其在查找方法中被记录为名为refc的参数。方法类型MT由返回类型T以及变元类型A*的序列组成。构造函数也具有变元类型A*的序列并且被认为返回类型C的新创建的对象。MT和字段类型FT二者都被记录为名为type的参数。形式参数this表示类型C的自引用;如果它存在,则它总是方法句柄启用的首要变元。(在一些protected(受保护)成员的情况下,this可以在类型方面被限制为查找类;参见下文。)名称arg表示所有其它方法句柄变元。在用于核心反射API的代码示例中,如果所访问的方法或字段是静态的,则名称thisOrNull表示空引用,否则名称thisOrNull表示this。名称aMethod、aField和aConstructor表示对应于给定成员的反射对象。

在给定成员具有可变元数的情况下(即,给定成员是方法或构造函数),返回的方法句柄也将具有可变元数。在所有其它情况下,返回的方法句柄将具有固定元数。

讨论:查找方法句柄与底层类成员和字节码行为之间的等价性可以以若干方式被破坏。如果C不是从查找类的加载器可符号访问的,则查找仍然可以成功,即使没有等价的Java表达式或字节码常量。同样,如果T或MT不是从查找类的加载器可符号可访问的,则查找仍然可以成功。例如,不管所请求的类型如何,对MethodHandle.invokeExact和MethodHandle.invoke的查找将总是成功。如果安装了安全管理器,则它可以出于各种原因禁止查找(参见下文)。相比之下,对CONSTANT_MethodHandle常量的ldc指令不经受安全管理器检查。如果查找方法具有非常大的元数,则由于方法句柄类型具有太多参数,因此方法句柄创建可能失败。

访问检查

当创建方法句柄时,在Lookup的工厂方法中应用访问检查。这是与核心反射API的主要区别,因为java.lang.reflect.Method.invoke在每次调用时对每个调用者执行访问检查。

所有访问检查从Lookup对象开始,Lookup对象将其记录的查找类与所有请求进行比较以创建方法句柄。单个Lookup对象可被用来创建任何数量的访问检查过的方法句柄,所有方法句柄都针对单个查找类进行检查。

Lookup对象可以与其它受信任代码(诸如元对象协议)共享。共享的Lookup对象委托对查找类的私有成员创建方法句柄的能力。即使有特权的代码使用Lookup对象,访问检查也局限为原始查找类的特权。

查找可能失败,因为包含类对于查找类来说不可访问,或者因为期望的类成员丢失,或者因为期望的类成员对于查找类来说不可访问,或者因为查找对象不被足够信任以访问成员。在这些情况中的任何情况下,将从尝试的查找中抛出ReflectiveOperationException。确切的类将是以下之一:NoSuchMethodException–如果方法被请求但是不存在;NoSuchFieldException–如果字段被请求但是不存在;IllegalAccessException–如果成员存在但是访问检查失败。

一般而言,可以为方法M查找方法句柄的条件不比查找类可以编译、验证和解析对M的调用的条件更严格。在JVM将引发像NoSuchMethodError一样的异常的情况下,方法句柄查找一般将引发对应的检查异常,诸如NoSuchMethodException。而且启用由查找产生的方法句柄的效果完全等价于执行对M的编译后、验证后和解析后的调用。对于字段和构造函数也是如此。

讨论:访问检查仅适用于命名和反射方法、构造函数和字段。诸如MethodHandle.asType之类的其它方法句柄创建方法不要求任何访问检查,并且独立于任何Lookup对象被使用。

如果期望的成员是protected(受保护的),则包括查找类必须在与期望成员相同的包中或者必须继承该成员的要求的通常的JVM规则适用。(请参见Java虚拟机规范,节4.9.2、5.4.3.5和6.4。)另外,如果期望的成员是不同包中的非静态字段或方法,则得到的方法句柄仅适用于查找类或它的子类中的一个子类的对象。这个需求通过将首要的this参数的类型从C(这将必然是查找类的超类)缩小到查找类本身来实施。

JVM对invokespecial指令施加类似的要求,即,接收者变元必须匹配解析的方法和当前类二者。同样,这个要求通过将首要参数的类型缩小为得到的方法句柄来实施。(请参见Java虚拟机规范,节4.10.1.9。)

JVM将构造函数和静态初始化器块表示为具有特殊名称的内部方法(“<init>”和“<clinit>”)。启用指令的内部语法允许它们参考这样的内部方法,就好像它们是正常的方法一样,但是JVM字节码验证器拒绝它们。这样的内部方法的查找将产生NoSuchMethodException。

在一些情况下,嵌套类之间的访问由Java编译器通过创建包装器方法以访问同一最高级声明中另一个类的私有方法来获得。例如,嵌套类C.D可以访问其它相关类(诸如,C、C.D.E或C.B)内的私有成员,但是Java编译器可能需要在那些相关类中生成包装器方法。在这样的情况下,C.E上的Lookup对象将不能访问那些私有成员。对这种限制的解决方法是Lookup.in方法,它可以将对C.E的查找变换为对那些其它类中的任何类的查找,而无需特殊的特权提升。

对给定查找对象的允许的访问可以根据其lookupMode集合被限制为查找类正常可访问的成员子集。例如,publicLookup方法产生仅允许访问公有类中的公有成员的查找对象。调用者敏感的方法lookup产生具有相对于其调用者类的全部能力的查找对象,以模拟所有支持的字节码行为。另外,Lookup.in方法可以产生具有比原始查找对象更少的访问模式的查找对象。

私有访问的讨论:如果查找的查找模式包括访问private(私有)成员的可能性,则我们称查找具有私有访问。如在其它地方的相关方法中所记录的,只有具有私有访问的查找才拥有以下能力:

访问查找类的私有字段、方法和构造函数

创建启用调用者敏感的方法(诸如Class.forName)的方法句柄

创建模拟invokespecial指令的方法句柄

避免对于查找类可访问的类的包访问检查

创建具有对同一包成员内的其它类的私有访问的委托的查找对象

这些许可中的每个许可是具有私有访问的查找对象可以被安全地追溯到起源类的事实的结果,该起源类的字节码行为和Java语言访问许可可以由方法句柄可靠地确定和模拟。

安全管理器交互

虽然字节码指令仅可以参考相关类加载器中的类,但是这个API可以搜索任何类中的方法,只要对它的Class对象的引用是可用的。这样的交叉加载器引用对于核心反射API也是可能的,并且对诸如invokestatic或getfield之类的字节码指令是不可能的。存在安全管理器API以允许应用检查这样的交叉加载器引用。这些检查适用于(如在Class上找到的)MethodHandles.Lookup API和Core Reflection API二者。

如果存在安全管理器,则成员查找经受附加的检查。对安全管理器进行一至三次调用。这些调用中的任何调用可以通过抛出SecurityException来拒绝访问。将smgr定义为安全管理器,将lookc定义为当前查找对象的查找类,将refc定义为在其中查找成员的包含类,以及将defc定义为成员在其中被实际定义的类。如果当前查找对象不具有私有访问,则值lookc被定义为不存在。调用是根据以下规则进行的:

步骤1:如果lookc不存在,或者如果它的类加载器与refc的类加载器不相同或者不是refc的类加载器的祖先,则调用smgr.checkPackageAccess(refcPkg),其中refcPkg是refc的包。

步骤2:如果检索出的成员不是公有的并且lookc不存在,则调用具有RuntimePermission(“accessDeclaredMembers”)的smgr.checkPermission。

步骤3:如果检索出的成员不是公有的,并且如果lookc不存在,并且如果defc和refc不同,则调用smgr.checkPackageAccess(defcPkg),其中defcPkg是defc的包。

安全检查在其它访问检查已通过之后被执行。因此,上面的规则预先假定成员是公有的,否则是正从具有访问该成员的权限的查找类访问的成员。

调用者敏感的方法

少数Java方法具有被称为调用者敏感性的特殊性质。调用者敏感的方法可以取决于其直接调用者的身份而表现不同。

如果请求对于调用者敏感的方法的方法句柄,则用于字节码行为的通用规则适用,但是它们以特殊方式考虑查找类。得到的方法句柄表现得好像它是从包含在查找类中的指令被调用的一样,以便调用者敏感的方法检测该查找类。(相比之下,方法句柄的启用者被忽略。)因此,在调用者敏感的方法的情况下,不同的查找类可能产生表现不同的方法句柄。

在查找对象是publicLookup()或者不具有私有访问的某些其它查找对象的情况下,查找类被忽略。在这样的情况下,不可以创建调用者敏感的方法句柄,访问被禁止,并且查找失败并且抛出IllegalAccessException。

讨论:例如,取决于调用调用者敏感的方法Class.forName(x)的类的类加载器,调用者敏感的方法Class.forName(x)可以返回不同的类或抛出不同的异常。Class.forName的公有查找将失败,因为没有合理的方式来确定它的字节码行为。

如果应用高速缓存方法句柄以用于广泛共享,则它应当使用publicLookup()来创建它们。如果存在Class.forName的查找,则它将失败,并且在该情况下应用必须采取适当的动作。也许在引导方法的启用期间的稍后的查找可以结合调用者的特殊身份,从而使得该方法可访问。

函数MethodHandles.lookup是调用者敏感的,以便于可以存在用于查找的安全基础。JSR 292 API中的几乎所有其它方法依赖于查找对象来检查访问请求。

方法句柄和invokedynamic

抽象地来说,方法句柄仅是类型和符合该类型的某些行为。由于适合面向对象的系统,因此行为可以包括数据。具体来说,方法句柄可以参考任何JVM方法、字段或构造函数,否则它可以是任何先前指定的方法句柄的变换。变换包括部分应用(绑定)、过滤和各种形式的变元混排(shuffling)。

方法句柄的类型被表示为零个或更多个参数类型的序列,以及可选的返回类型(或者无类型void)。具体来说,这是MethodType引用,并且可以使用MethodHandle.type从任何方法句柄提取。

该行为是当方法句柄被启用时使用方法MethodHandle.invokeExact发生的行为。方法句柄的特殊能力在于invokeExact接受任何数量的任何类型的变元,并且可以返回任何类型或void。常规的invokevirtual指令执行该操作。(它被秘密地重写为invokehandle(如下文所讨论的),但是除了HotSpot实现器之外,这可以被忽略。)

对于方法句柄特有地,invokevirtual指令可以指定任何结构上有效的类型签名,并且调用点将链接。在技术上,我们称invokeExact是签名多态的。实际而言,当链接这样的调用点时,JVM可以准备好处理任何类型的签名,这意味着它将必须生成各种各样的适配器。从用户的角度来看,方法句柄可以包装和/或启用任何类型的任何方法。

具体来说,方法句柄的行为取决于被称为LambdaForm的对象,该对象是逐步操作的低级描述。方法句柄的lambda形式被存储在它的形式字段中,就好像它的类型被存储在它的类型字段中一样。

方法句柄的lambda形式可以完全忽略方法句柄,并且进行与上下文无关的某些操作,比如抛出异常或返回零。更一般地,它可以查询方法句柄以获得信息。例如,它可以检查方法句柄的返回类型,并且在返回某个值之前将它转换为该类型。

更有趣的是,如果方法句柄的类是包含附加数据字段的子类,则lambda形式可以在它执行时参考那些字段。

由于方法句柄表达行为多于表达状态,因此它们的字段通常是不可变的。然而,方法句柄可以容易地被绑定到任意Java对象,从而产生闭包(closure)。

“基本类型”系统

为了更简单地实现签名多态性,方法句柄在内部依据基本类型操作。基本类型是其中许多不方便的区别已被“擦除”的JVM类型,以便于剩余的区别(诸如引用vs.原语和整型vs.长整型)可以被注意。

对于初学者,在基本类型系统中,除了浮点型之外的所有32位类型都被擦除成简单的整型。如果在某个地方需要字节值,则它可以从完整的整型被遮蔽。因此,仅有四种原语类型需要担心。

依据基本类型化规则,所有引用类型由java.lang.Object表示。因此,总共有五种基本类型,这五种基本类型由它们的JVM签名字符表示为:L、I、J、F、D。我们将用于无类型void的V添加到这些基本类型.

在Java代码的大部分中,完整类型系统是生效的。为了命名引用类型,可以查询和兑现(honor)类加载器和类型约束的系统。从JSR 292运行环境的角度来看,该类型系统是名称和范围的复杂组合。在运行环境内部,除了Object和引导(boot)类路径上的其它类型,使用基本类型没有名称需要担心。

如果在某个地方需要较窄类型的引用,则可以在使用该引用之前发出显式的检查转换(checkcast)。事实上,检查转换一般是对Class.cast的调用,其中专用类型是常量Class引用而不是符号引用名称。

通常,所有额外的转换(诸如整型到字节型以及Object到命名的引用类型)在优化器中不出现,优化器对来自上下文的完整类型信息保持跟踪。

lambda形式基础

简而言之,lambda形式是具有零个或更多个形式参数、加上零个或更多个主体表达式的典型lambda表达式。参数和表达式值的类型是从基本类型系统汲取的。每个表达式仅是方法句柄到零个或更多个变元的应用。每个变元或者是常量值或者是先前指定的参数或表达式值。

当lambda形式被用作方法句柄行为时,第一个参数(a0)总是方法句柄本身。(但是对于lambda形式来说还存在其它用法。)当方法句柄被启用时,在任何初始的类型检查之后,JVM执行方法句柄的lambda形式以完成方法句柄启用。这导致一些引导挑战,因为lambda形式通过评估附加的方法句柄启用来执行。

lambda形式优化

在lambda形式执行中还存在允许系统优化自身的一个间接寻址(indirection):lambda形式具有被称为vmentry的、(最后)为JVM提供要跳转到的Method*指针的字段,以便评估lambda形式。(注意:由于Java不能直接表示JVM元数据指针,因此该vmentry实际上具有类型MemberName,MemberName是用于Method*的低级包装器。因此毕竟还存在一个间接寻址来隐藏元数据。)

当首次创建lambda形式时,该vmentry指针被初始化为被称为lambda形式解释器的方法,该方法可以执行任何lambda形式。(实际上它具有专用于变元的元数和基本类型的瘦包装器。)lambda形式解释器非常简单并且慢。在它执行几十次给定的lambda形式之后,解释器取来(fetch)或生成用于lambda形式的字节码,该字节码(至少部分地)被定制为lambda形式主体。在稳定状态下,所有“热”方法句柄及其“热”lambda形式使字节码被生成,并且使字节码最终被JIT编译。

因此,在稳定状态下,在没有lambda形式解释器的情况下,热方法句柄被执行。低级JVM步骤如下:1.取来MethodHandle.form;2.取来LambdaForm.vmentry;3.取来隐藏的Method*指针MemberName.vmtarget;4.取来Method::from_compiled_entry;5.跳转到优化的代码。如在别处所指出的,如果方法句柄(或者如果lambda形式或成员名)是编译时常量,则可以完成所有通常的内联。

Invokedynamic

如在JVM中所定义的,invokedynamic包括名称、方法类型签名和引导指定符。调用者可见的指令行为仅由类型签名来定义,该类型签名确切地确定哪些类型的变元和返回值通过栈被混排。当指令首次被执行时,确定指令的实际行为。与其它启用指令一样,LinkResolver模块处理在第一次执行时被执行的设置操作。

对于invokedynamic,引导指定符被解析为方法句柄以及零个或更多个额外的常量变元。(这些都是从常量池汲取的。)名称和签名连同额外的变元和MethodHandles.Lookup参数一起被推送到栈上,以具体化请求类,并且引导方法句柄被启用。(这种对用户指定的方法的诉求可能看起来令人吃惊,但是对JVM来说,它不比为了定位新类的字节码而可以执行的ClassLoader(类加载器)操作复杂多少。)

当引导方法返回时,它向JVM运行环境给出CallSite对象。这个调用点包含方法句柄,它(最终)确定链接的invokedynamic指令的确切行为。由于方法句柄差不多可以做任何事情,因此invokedynamic指令在链接之后是完全一般的虚拟机指令。

(细心的读者将想知道为什么引导方法不仅仅返回方法句柄。答案是一些调用点潜在地可以随时间被绑定到一系列不同的方法句柄。这给予Java程序员类似于由JVM用来管理单态虚拟调用点和多态虚拟调用点的低级代码打补丁技术。)

invokedynamic实现

因为每个invokedynamic指令(一般)链接到不同的调用点,所以常量池高速缓存可以包含用于每个invokedynamic指令的分开的条目。(如果其他启用指令使用常量池中的同一符号引用,则它们可以共享CP高速缓存条目。)CP高速缓存条目(“CPCE”)在被解析时具有元数据和/或偏移信息的一个词或两个词。

对于invokedynamic,解析后的CPCE包含到提供调用的确切行为的具体adapter(适配器)方法的Method*指针。还存在与被称为appendix(附录)的调用点相关联的引用参数,该引用参数被存储在用于CPCE的resolved_references数组中。

该方法被称为适配器,因为(一般来说)它混排变元、从调用点提取目标方法句柄,并且启用该方法句柄。额外的引用参数被称为附录,因为它在invokedynamic指令被执行时附加到变元列表。通常,附录是由引导方法产生的CallSite引用,但是JVM对此不关心。只要CPCE中的适配器方法知道如何处理与CPCE一起存储的附录,就一切都好。

作为极端情况,如果附录值为空,则它根本不被推送,并且适配器方法可能不期望额外的变元。在这种情况下,适配器方法可以是对具有与invokedynamic指令一致的签名的静态方法的永久链接的引用。这将实际上将invokedynamic变成简单的invokestatic。许多其它这样的强度降低(strength reduction)优化是可能的。

链接握手

用于invokedynamic CPCE的适配器Method*指针不是由JVM选择,而是由受信任的Java代码选择。对于附录引用也是如此。事实上,JVM不直接启用引导方法。相反,JVM利用解析后的引导指定符信息来调用特定于HotSpot的方法MethodHandleNatives.linkCallSite。(其它JVM实现不一定使用这种握手。)linkCallSite方法执行JSR 292引导规则所要求的步骤,并且返回两个协调值、适配器方法及其附录。

由于Java不能表示原始Method*指针,因此该方法被包装在被称为MemberName的私有Java类型中,这类似于用于Klass*的Java镜像。附录是简单的Object引用(或null)。在经过些许解包之后,这些被插入到CPCE中。

用于invokedynamic的适配器方法

一般而言,适配器方法是由JSR 292运行环境运行中(on the fly)创建的特殊生成的方法。它从计算当前调用点目标并且启用该目标的lambda形式生成。lambda形式采用与为invokedynamic指令入栈的变元对应的首要参数,即,由该指令的方法签名所需的参数。lambda形式还采用尾随的附录变元(如果相关的话)。然后它执行引导方法及其调用点所需的任何操作。

这是取自实际应用的适配器方法的示例:

这里,invokedynamic指令采用两个变元,double a0和引用a1,并且返回引用t4。附录跟随在结尾处,在a2中。

Lambda形式的主体使用子例程Invokers.getCallSiteTarget从附录提取方法句柄目标。方法句柄被绑定到t3,然后立即对两个首要参数a0和a1启用。

如通过检查Java代码可以看到的,getCallSiteTarget期望获得非空的CallSite变元。如果这将失败,则它将意味着受信任的Java代码中有错误(bug),因为受信任代码负责向JVM返回一对一致的适配器和附录。

特殊的非公有例程MethodHandle.invokeBasic是MethodHandle.invokeExact的未检查版本。它在两个方面与invokeExact不同。第一,它不检查其被调用者具有与调用点处的类型(完全)匹配的类型。(不管怎样,它可以不抛出WrongMethodTypeException。)

第二,它允许根据在JSR 292运行环境中使用的基本类型方案来放宽其变元和返回值的类型化。(参见上文。)

invokedynamic的示例执行序列

invokedynamic指令的调用点的目标可以是任何方法句柄。在最简单的情况下,它可以是将包含invokedynamic指令的方法连接到某些其它Java语言方法的直接方法句柄。

这是将进行从方法IndyUser.m1到目标方法LibraryCls.m2的这样的连接的事件和栈帧的序列的示例:

存在两个内部栈帧,一个用于绑定到invokedynamic调用点的适配器,一个处理用于目标方法句柄的启用。

在[关于直接方法句柄的页面]上解释具体的方法internalMemberName和linkToStatic。

方法句柄启用

在(rewriter.cpp中的)HotSpot的内部,方法句柄启用被重写,以使用被称为invokehandle的特殊指令。这个指令在许多方面与invokedynamic相似。它解析为适配器方法指针和附录。附录(如果不为空)在invoke或invokeExact的显式变元之后被推送。

解析经由对受信任的Java代码,被称为MethodHandleNatives.linkMethod的方法的调用来完成。与linkCallSite一样,JVM向linkMethod传递所有解析后的常量池引用,并接收回一对协调值MemberName和Object。在解包之后,这些作为适配器和附录被插入到CPCE中。

相同的自由度适用于invokehandle CPCE条目,就像适用于invokedynamic CPCE条目一样,并且类似的优化机会适用。与invokedynamic有一个主要区别:如果许多invokehandle指令都具有同一的签名和方法名称(“invokeExact”vs.“invoke”),则它们可以共享单个CPCE条目。

用于invokeExact的适配器方法

MethodHandle.invokeExact的invokevirtual的标准语义是简单的。用于调用点的签名(其可以包含任何引用和原语类型的任何混合)被解析(在链接时,被解析一次)为MethodType。每次方法句柄被启用时,将对照被启用的方法句柄的类型来检查方法类型。(由于方法句柄是计算出的值,所以它当然可以每次不同,并且类型不一定匹配。)如果两种类型在任何方面不同,则抛出WrongMethodTypeException。否则,将对给定的类型启用方法句柄,并返回由调用者期望的类型的值。

用于invokeExact的适配器方法对应地是简单的。它仅仅执行方法类型检查,然后调用invokeBasic。附录是对进行类型检查所需的resolvedMethodType的引用。

这是示例:

首要变元a0是方法句柄。结尾变元a2是在调用点被解析时计算的方法类型附录。中间变元a1是方法句柄的唯一变元。

首先,对方法句柄和附录调用子例程Invokers.checkExactType,从而提取来自方法句柄的类型并将其与静态调用点类型进行比较。如果没有抛出异常,则控制返回到适配器方法。不返回任何值,并且名称t3具有伪类型void。(由于附录表示静态调用点类型,并且方法句柄的类型是动态可接受的类型,因此这可以被视为简单的动态类型检查。)

接下来,invokeBasic被用来跳转到方法句柄(现在已知它对于该调用是完全安全的)。简称为t4的结果返回,并且返回到调用者。

用于泛型启用的适配器方法

方法句柄还支持较复杂的启用模式,该较复杂的启用模式可以对单独的变元和返回值执行类型转换,并且甚至将变元分组成varargs数组。与invokeExact一样,这样的调用点的解析通过对MethodHandleNatives.linkMethod的调用来实现。在这种情况下,受信任的Java代码可以返回更灵活和复杂的适配器方法。

这是示例:

如前所述,a0是方法句柄,结尾的a3是方法类型。这里有两个常规变元:引用和整型。如前所述,第一个任务是检查类型,并且这由Invokers.checkGenericType完成。与较简单的检查不同,该例程返回值。(如果必要,则该例程还可以抛出WrongMethodTypeException。)从checkGenericType返回的值实际上是方法句柄t4。该方法句柄立即对原始变元、加上原始方法句柄a0、加上期望的调用点类型a3(不按该顺序)启用。取决于a0的类型与调用点类型a3之间的匹配或不匹配,t4的实际行为可以非常简单(基本上是另一个invokeBasic)或非常复杂(具有类型转换的元数改变)。这取决于运行环境。

用于invokeExact的示例执行序列

invokeExact调用的接收者可以是任何种类的方法句柄。在最简单的情况下,它可以是将包含方法句柄指令的启用者的方法连接到某些其它Java语言方法的直接方法句柄。

这是将进行从方法MHUser.m1到目标方法LibraryCls.m2的这样的连接的事件和栈帧的序列的示例:

与上面的invokedynamic示例的情况一样,存在两个内部栈帧,一个用于绑定到invokeExact调用点的适配器,一个处理用于目标方法句柄的启用。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1