处理值类型的制作方法

文档序号:11160868阅读:320来源:国知局
处理值类型的制造方法与工艺

实施例一般涉及用于在编程语言内支持和/或利用值类型结构体的技术。



背景技术:

计算机语言提供许多不同的方法来创建聚合类型(aggregate type)。例如,诸如Java之类的语言提供具有标识(identity)的异构聚合(例如,“类”)和具有标识的同构聚合(例如,“数组”)。标识由诸如唯一地识别对象的标识号(例如,存储器地址)之类的每个实例化对象所包括的头部信息来实现。然而,对象标识具有占用空间(footprint)和性能成本,这是Java具有存储在适当的位置、在方法调用期间在不使用引用的情况下通过值传递、并且不包括头部的原生类型(primitive type)的主要原因。这与引用类型相反,引用类型由指针访问、使用在方法调用期间通过值传递的引用来表示、并且包括头部。对于不具有许多字段来分摊支持标识所需的额外成本的小对象来说,支持标识的成本是最难以负担的。

例如,考虑具有类型为整型(int)的x和y字段的Point类。在实施方式中,每个Point对象可以包括额外的对象头部(8到16字节)和引用(4到8字节)的Point对象数组意味着这些8字节的数据(对于x和y整数)占用20到32字节的堆空间。此外,遍历该数组意味着对于所访问的每个Point对象的指针解引用。这破坏了数组的内在局部性(inherent locality)并且限制了程序性能。

程序员经常采取比如将Point数组表示为两个整型数组(一个用于x并且一个用于y)的技巧来避免这些成本,但是该方法牺牲了封装性(和可维护性)只是为了补回标识的性能损失。一种方法是显著扩大语言支持的原生的数量。然而,(如果有可能的话)可能难以预期高效地解决放在程序员面前的每个问题将需要的原生类型。因此,落在原生类型和对象的领域之间的结构体将对程序员有很大益处。

附图说明

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

图1示出了可以实践本文所描述的技术的示例性计算架构。

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

图3示出了根据实施例的以框图形式的示例性虚拟机存储器布局。

图4示出了根据实施例的以框图形式的示例性帧。

图5示出了根据实施例的用于在容器中存储值类型的过程。

图6示出了根据实施例的用于访问值类型的过程流。

图7是示出了可以实现本发明的实施例的计算机系统的框图。

具体实施方式

在下面的描述中,出于解释的目的,阐述了许多具体细节以便提供对本发明的深入理解。然而,显而易见,可以在没有这些具体细节的情况下实践本发明。在其他实例中,公知的结构和设备以框图形式示出,以避免不必要地混淆本发明。

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

1.0总体概述

2.0示例性操作环境

2.1示例性类文件结构

2.2示例性虚拟机架构

2.3加载、链接和初始化

3.0值类型概述

3.1用例

3.2示例性值类型特性

3.3示例性限制

3.4指针管理

3.5示例性定义

3.6示例性用法

3.7示例性实现细节

3.8示例性字节码和类型描述符

3.9装箱和对象互操作性

3.10示例性代码

3.11附加的示例性选项

3.12类型到值类型的迁移

3.13更新字段的值

4.0修改后的加载进程

5.0存储考虑

5.1存储过程流

6.0原子操作

7.0硬件概述

8.0扩展和替代

9.0第一附加公开

10.0第二附加公开

1.0总体概述

本文所描述的技术经常使用来自Java编程语言、Java虚拟机(“JVM”)和Java运行环境的术语和定义。然而,构想的是所描述的技术可以结合任何编程语言、虚拟机架构或运行环境来使用。因此,例如诸如“方法”之类的按Java术语描述的用辞可与诸如“函数”之类的其他用辞互换。此外,术语“方法”还与术语“类方法”或“对象方法”同义。方法是由名称指代并且可以在使得方法的代码被执行的程序中的各种点处调用(援用)的一组代码或代码块。

在实施例中,值类型是遵循“像类一样编程,像整型一样工作”原则的一种类别的聚合体。因此,像类一样,值类型可以由程序员按照被命名的、类型成分(字段)、行为(方法)、隐私(访问控制)、编程初始化(构造函数(constructor))等来定义。然而,像原生类型(诸如整型)一样,值类型不包含头部信息并且被有效地当作用户定义的原生。因此,当可行时,值类型被存储为扁平化的表示并且在方法之间通过值传递。因此,与对象相比,通过不要求指针被解引用以访问值类型实现了速度改进,并且通过不要求值类型存储头部信息实现了存储改进。然而,实施例自由地以许多方式实现值类型的方面,这些方式中的一些方式可以违背“像类一样编程,像整型一样工作”原则。因此,本文所描述的值类型的特征可以被分开实现、作为整体实现或者无限制地以任何组合实现。

然而,为了实现值类型,存在若干考虑。一个考虑是如何在源语言和/或虚拟机的指令集(当适用时)中显现(surface)值类型。例如,由于值类型不包含具有标识信息的头部,因此依赖于标识的许多特征要么对于值类型必须被禁用,要么在值类型的背景下必须被分配新含义。作为另一示例,附加的指令和/或关键字可以被添加到源语言/虚拟机指令集以定义和利用值类型。节3.0“值类型概述”及其相关小节一般涉及如何在源语言和/或虚拟机的指令集中显现值类型的问题。

另一个考虑是如何在诸如虚拟机的运行环境之类的计算机系统的工作存储器内处理值类型的存储。如上文提到的,旨在以扁平化的表示来存储值类型。因此,例如如果对象具有存储值类型的字段,则旨在将该值类型在没有指针间接寻址(pointer indirection)的情况下存储在适当的位置。作为另一示例,当值类型被加载到栈帧(stack frame)中的局部变量中时,该值类型被扁平地存储在表示局部变量的容器中。然而,因为值类型可以具有可变的尺寸,所以存在容器可能需要被调整尺寸以便容纳特定的值类型的情况。这造成了性能问题,因为为容器重新分配内存可能带来大量开销,尤其是当重新分配经常被执行的时候。

因此,在一些实施例中,实现优化以对存储值类型的成本设定界限。例如,虚拟机的验证器可以定义若干指派规则,当接收到在容器中放置值类型的指令时检查这些指派规则。例如,验证器可以阻止将被分配为针对一种值类型的容器用于存储具有不同尺寸的值类型。因此,阻止了虚拟机不断地重新分配容器以适配不同的值类型。作为另一示例,如果值类型超出了尺寸阈值并且被确定为是不可变的,则虚拟机可以选择在所管理的堆存储器上存储值类型并且替代地在容器中存储对堆的引用。因此,阻止了虚拟机引起多次复制过大的值类型的开销。例如,如果局部变量被放置在操作数栈上,则该局部变量可以通过引用而不是通过值被复制。然而,由于值类型是不可变的,因此对终端用户而言,指令的行为(从语义的角度)无法与其中值类型被存储并通过值传递的情况分辨开来。这可能导致访问的时间增加,但是对于大的值类型来说,复制花费的时间量可能大大超过去除指针遍历的益处。节5.0“存储考虑”及其相关小节一般涉及存储优化和性能的问题。

在一些实施例中,值类型是不可变的,意味着值类型(至少从终端用户的视角)不能以分段的方式更新。这类似于诸如Java之类的一些语言怎样允许由变量保持的整数被替换,但是不向用户提供允许整数的单个位改变值的指令。因此,在值类型的背景下,不可变性意味着值类型仅可以作为整体被替换。然而,即使当值类型为不可变时,值类型也仍然可以被存储在可变的变量或容器中。在变量、字段或容器的背景下,不可变性暗示着由该变量或容器保持的值不能被替换(例如,被冻结)。

另一考虑是如何阻止值类型在被多个线程访问时的撕裂。假设值类型被存储在诸如许多虚拟机实现方式的堆之类的共享存储器中,由于多个线程同时访问值类型,因此存在竞争状况(race condition)的风险。由于值类型旨在充当诸如原生整型之类的整个值,因此当第一线程试图更新存储值类型的容器并且第二线程在第一线程完全结束更新之前读取该容器时,结构撕裂发生。结果,第二线程在容器中遇到部分是旧值并且部分是新值的值类型。为了阻止结构撕裂,技术被应用以使得存储和加载操作是原子的。然而,由于值类型是用户定义的,因此执行原子访问的合适方法可能基于值类型的尺寸和/或用途以及底层计算机系统的硬件能力而变化。例如,一些中央处理单元(CPU)支持针对各种尺寸(例如,64位、128位、256位等)的原子更新和/或事务,但是这些硬件指令不能考虑可能被用于值类型的每个尺寸。因此,因为值类型可以大于底层硬件支持的最大原子存储器转移,所以结构撕裂变得有可能,这是因为(例如)对4成分值的写入可以被拆成两个64位写入,而该值的读取可以被拆成两个64位读取。因此,如果读取和写入这二者同时执行,则有可能遇到作为旧值和新值的混合的值类型。

在实施例中,使用若干技术来优化针对值类型的原子操作。例如,如果硬件指令可用于执行原子操作,则该指令是优选的,因为硬件实现的技术与软件实现的技术相比一般具有更好的性能。此外,在某些情形中,原子操作可以被绕过,诸如当被访问的字段为不可变时。在一些实施例中,当值类型的字段被频繁访问时,值类型被修改以将该字段放置在由指针引用访问的单独的容器中。通过创建更新后的值的拷贝并且切换指针,可以相对快地执行对通过指针访问的字段的原子更新。因此,通过修改值类型的结构,当执行原子访问时可以获得提高的性能。节6.0“原子操作”一般针对涉及原子操作的问题。

2.0示例性操作架构

图1示出了可以实践本文所描述的技术的示例计算架构100。

如图1中所示,计算架构100包括源代码文件101,源代码文件101由编译器102编译为表示要被执行的程序的类文件103。类文件103然后由执行平台112加载并且执行,执行平台112包括运行环境113、操作系统111以及使得能够实现运行环境113和操作系统111之间的通信的一个或多个应用编程接口(API)110。运行环境113包括虚拟机104,虚拟机104包括各种组件,诸如存储器管理器105(其可以包括垃圾收集器)、用于检查类文件103和方法指令的合法性的验证器106、用于定位和建立类的存储器中(in-memory)表示的类加载器107、用于执行虚拟机104代码的解释器108以及用于产生优化的机器级别代码的即时(JIT)编译器109。

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

在各种实施例中,编译器102把根据依照程序员的方便的规范编写的源代码转换成可由特定机器环境直接执行的机器代码或对象代码,或者转换成可由能够在多种特定机器环境上运行的虚拟机104执行的中间表示(“虚拟机代码/指令”),诸如字节码。虚拟机指令可由虚拟机104以比源代码更直接、更高效的方式执行。将源代码转换为虚拟机指令包括将来自语言的源代码功能映射到利用诸如数据结构之类的底层资源的虚拟机功能。时常地,由程序员经由源代码以简单术语呈现的功能被转换成更复杂的步骤,该更复杂的步骤更直接地映射到由虚拟机104驻留在其上的底层硬件支持的指令集。

一般来说,程序要么作为编译后的程序执行,要么作为解释后的程序执行。当程序被编译时,在执行之前代码从第一语言被全局地转变为第二语言。由于转变代码的工作是提前执行的,因此编译后的代码往往具有杰出的运行时性能。另外,由于在执行之前所述转变全局地发生,因此可以使用诸如常量折叠、死代码消除、内联等之类的技术来分析和优化代码。然而,取决于正被执行的程序,启动时间可能是显著的。另外,插入新代码将会要求程序离线、重新编译和重新执行。对于被设计为允许在程序的执行期间插入代码的许多动态语言(诸如Java)来说,纯编译的方法一般是不合适的。当程序被解释时,程序的代码在程序执行的同时被逐行读取并且转换为机器级别的指令。结果,程序具有短的启动时间(可以几乎立刻开始执行),但是运行中执行转换减弱了运行时性能。此外,由于每个指令被单独分析,因此依赖于对程序的更全局的分析的许多优化不能执行。

在一些实施例中,虚拟机104包括解释器108和JIT编译器109(或者实现这二者的方面的组件),并且使用解释技术和编译技术的组合来执行程序。例如,虚拟机104最初可以开始于经由解释器108解释表示程序的虚拟机指令并同时追踪关于程序行为的统计数据,诸如虚拟机104执行不同的代码部分或代码块的频率。一旦代码块超过某一阈值(它是“热的”),则虚拟机104调用JIT编译器109来执行对块的分析并且生成替换“热”代码块的经优化的机器级别的指令以供将来执行。由于程序往往花费其大部分时间来执行它们的整体代码的小部分,因此仅编译程序代码的“热”部分可以提供与完全编译的代码相似的性能,但是没有启动的损失。此外,尽管优化分析局限于被替换的“热”块,但是仍然存在与独个地转换每个指令相比大得多的优化潜力。

为了提供清楚的示例,源代码文件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)操作、数学工具、密码技术、图形实用程序等的一个或多个类文件。此外,一些类(或者这些类内的字段/方法)可以包括访问限制,该访问限制将其使用限制在特定的类/库/包内或者限制在具有适当许可的类内。

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(表示整型、长整型、双精度型(double)、浮点型、字节型、字符串型等类型的常量值)、类信息结构203、名称和类型信息结构205、字段引用结构206以及方法引用结构207的条目。在实施例中,常量表201被实现为将索引i映射到结构j的数组。然而,常量表201的确切实现方式不是关键的。

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

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

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

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

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

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

在描述符表示字段的类型的示例中,描述符标识由字段保持的数据的类型。在实施例中,字段可以保持基本类型、对象或数组。当字段保持基本类型时,描述符是标识该基本类型的字符串(例如,“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是完全限定的(fully qualified)并且包括类的简单名称以及类的路径名称。例如,ClassName可以指示文件存储在托管类文件200的包、库或文件系统中的何处。

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

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

使用Java作为示例,考虑下面的类:

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

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

2.2示例性虚拟机架构

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

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

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

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

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

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

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

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

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

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

尽管使用诸如“数组”和/或“栈”之类的数据结构来提及局部变量401和操作数栈402,但是对用于实现这些元素的数据结构的类型没有限制。另外,关于局部变量401和操作数栈402在此提及的数据结构涉及数据结构的高级别表示。实施例可以使用各种较低级别的存储机制来实现这些数据结构,诸如将局部变量401和/或操作数栈402的一个或多个值存储在执行虚拟机104的机器硬件的中央处理单元(CPU)的一个或多个寄存器中。较低级别的指令可以引用对于较高级别的指令来说透明的一个或多个较低级别的存储机制。尽管可以使用各种较低级别的存储机制(这些较低级别的存储机制都不是由较高级别的指令指定的)来实现较高级别的指令,但是较低级别的指令引用用于实现较高级别的指令的一个或多个特定存储机制。

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

2.3加载、链接和初始化

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

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

为了开始加载过程,虚拟机104通过调用加载初始类的类加载器107而启动。用于指定初始类的技术将随着实施例不同而变化。例如,一种技术可以使虚拟机104在启动时接受指定初始类的命令行参数。

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

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

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

为了验证类,虚拟机104检查类的存储器中表示在结构上是否是正确的。例如,虚拟机104可以检查除了泛型类对象之外的每个类具有超类、检查最终类不具有子类以及最终方法没有被重载、检查当前类是否具有对于常量池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执行类的构造函数以设置该类的起始状态。例如,初始化可以初始化类的字段和方法数据306以及在由构造函数创建的堆302上生成/初始化任何类实例。例如,用于类的类文件200可以指定特定方法是用于设立起始状态的构造函数。因此,在初始化期间,虚拟机104执行该构造函数的指令。

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

3.0值类型概述

在一些实施例中,虚拟机104提供许多不同的方式来创建聚合类型。例如,虚拟机104可以提供具有标识的异构聚合(例如,经由“类”)和具有标识的同构聚合(例如,经由“数组”)。值类型表示异构的并且没有标识的另一类聚合。因此,类似于原生类型(诸如字节型、短整型、整型、长整型、浮点型、双精度型、字符型和布尔型),如果两个存储器区域包含相同的值类型,则这些存储器区域中的数据不能被区分。这与在一些实施例中各自包含具有诸如对象的存储器地址之类的唯一标识号的头部信息的类对象相反。对象标识具有占用空间和性能成本,这是与许多其他面向对象的语言不同,Java具有原生类型的主要原因。这些成本对于不具有许多字段来分摊额外成本的小对象来说是非常难以负担的。

在一些实施例中,在占用空间方面,具有标识的对象被分配在堆302上、具有一个或多个字的对象头部并且(除非死亡,否则)具有指向它们的一个或多个指针。在性能方面,每个相异的实例可以被分配在堆302上并且即使对象仅包含单个字段(例如,java.lang.Integer),每个访问也包括相关的加载(指针遍历)以到达“载荷”。另外,在Java中,对象头部支持包括Object.getClass、Object.wait和System.identityHashCode的许多基于标识的操作。

对象标识用来支持可变性,其中对象的状态可以是可变的,但是维持相同的内部对象。例如,对象的字段可以被修改,但是尽管字段的值发生改变,头部信息仍然将对象识别为“相同的”。另外,在一些实施例中,对象标识还用来支持多态性。然而,许多编程惯用语法(idiom)不要求标识,并且由于不付出存储器占用空间、局部性和标识的优化惩罚而受益。尽管有重大的尝试,但是许多虚拟机在解决标识对于程序是否有意义方面仍然较差,并且因此可能悲观地对于不需要标识的许多对象支持标识。

在一些实施方式中,甚至名义上无状态的(具有所有最终字段的)对象可以即使在高度优化的代码中使其标识随时被追踪,以免对象被用作同步的手段或者由依赖于标识的任何其他机制使用。该内在的“有状态性”妨碍许多优化。在一些情况中,逃逸分析(escape analysis)技术有时可以减轻这些成本,但是这些技术在代码复杂性面前是脆弱的并且在分开编译的情况下几乎完全失效。

运行示例:Point(点)

考虑Point类:

即使Point是不可变的,虚拟机104也不一定知道Point对象的标识将从来不被使用(例如,出于同步的目的将对象用作内部锁)。结果,Point被表示为关于x和y的“箱”对象。在一种实现方式中,Point对象的数组包括额外的对象头部(8到16字节)和引用(4到8字节),从而意味着这些8字节的数据(用于x和y整型)占用20到32字节的堆空间,而迭代该数组意味着对于所访问的每个Point对象的指针解引用。这破坏了数组的内在局部性并且限制了程序性能(更不用说分配带来了垃圾收集器的工作)。程序员通常借助于如将点数组表示为两个整型数组的技巧来避免这些成本,但是这牺牲了封装性(和可维护性)只是为了补回标识的性能损失。另一方面,如果Point可以用与诸如整型之类的原生类型相同的方式表示,则虚拟机104可以在寄存器中存储Point、将它们推到栈上、遍历具有局部性的数组以及使用少得多的存储器,而不损失封装性。

在实施例中,值类型表示没有标识的由用户定义的聚合类型,其可以以源代码文件101和虚拟机104的指令集的语言中显现,从而在不牺牲封装性的情况下支持存储器高效的和局部性高效的编程惯用语法。在实施例中,值类型是可以包含原生类型、引用类型或者甚至其他值类型的异构聚合。

在一些实施例中,例如在Java中,用于类的许多定义和封装机构可以被用于容易且安全地建立基于新的值类型结构体的数据结构。例如,值类型可以被当作特殊标记的并且受限的类定义的形式。然而,同时,值类型对于虚拟机104的用户来说(在语义的意义上)仅仅充当新型的原生。

如将在下面的节中探索的,尽管值类型有用地类似于类,但是(像原生那样)在值类型和引用类型之间做出清楚且系统的区分是有用的。因此,在一些实施例中,新的指令被添加到虚拟机104的指令集以创建、发布和/或修改值类型。然而,在其他实施例中,当前指令被重载,以便根据指令是否对引用、原生类型和/或值类型操作而执行不同的功能。

3.1用例

在实施例中,值类型可以被引入到虚拟机104以支持许多特征,这些特征在使用基于标识的聚合的情况下可能不存在或者是以次优方式实现的。然而,使用值类型可以高效地实现以下示例。一些示例包括:

数值(numeric)。诸如JVM之类的一些虚拟机仅提供有限数量的高效数值类型;如果使用这些数值类型不能高效地解决问题,则必须替代地使用具有标识的聚合(以及相关联的开销)。比如复数、扩展精度的整数或无符号整数以及十进制(decimal)类型之类的数值类型是广泛有用的,但是仅可以由原生和/或对象类来近似(有损于类型安全性、封装性和/或性能)。

本地类型(native type)。现代处理器支持各种各样的本地数据类型,并且(像数值那样)它们中的仅仅一些被直接映射为虚拟机所支持的原生。这使得编写直接编译为诸如向量指令之类的包含本地类型的指令的代码是困难的或者不可能的。

代数数据类型。使用对象箱可以实现像Optional<T>或Choice<T,U>那样的数据类型。然而,就小的无标识的聚合而言,许多这样的类型(尤其是像元组那样的产品类型)具有自然的表示。单元类型(空类似物(void-like)和度量二者)对于表示代数数据类型有时也是有用的,但前提是它们的占用空间开销可以被显著降低或被驱使为零。

元组。值的元素可以自身被当作值并且不一定需要对象箱。

游标(cursor)。迭代器或其他游标在复制数据结构中并且不一定需要对象箱。此外,(所管理的和/或本地的)数据结构的客户机可以在保持完整封装性和类型安全性的情况下绕过数据结构中的迭代器和其他“智能指针”。

3.2示例性值类型特性

在一些实施例中,在虚拟机104级别处的值类型设施提供以下有益特性中的一个或多个:

标量化。在一些实施例中,值类型的实例可以被定期分解为它们的成分并且存储在寄存器或者栈(诸如虚拟机栈310)中,而不是在堆302上。当作为包含对象的部分被存储在堆302中时,虚拟机104可自由地使用值类型的扁平化的、无指针的表示。

包装(wrapping)。在一些实施例中,值类型具有拆箱表示和装箱表示这二者。拆箱表示在可行的情况下被使用以降低开销;装箱表示被用于与需要引用类型的API的互操作性。与例如Java中现有的原生包装器不同,装箱表示可以从值类型的描述自动生成。例如,每个值类型可以使用类文件来定义,并且虚拟机104使用该类文件来自动生成表示用于值类型的箱对象的新的类文件。作为另一示例,源代码文件101中定义的值类型由编译器102编译为用于值类型的分开的类文件和值类型的装箱形式。作为又一示例,值类型和值类型的装箱形式这二者可以共享同一类文件,但是虚拟机将取决于上下文以不同方式对待值类型的使用。

行为。在一些实施例中,值类型不仅仅是数据的元组;它们还可以以方法的形式定义行为。例如,复数值类型的定义可以定义用于执行复数运算的方法。然后如果硬件原生是可用的(比如对于128位的数值),则虚拟机104可以选择内部化该行为。类似地,数据结构游标可以定义工厂方法和访问方法,同时保持其成分值被安全地封装。诸如Java中的toString和equals之类的实用程序方法(utility method)可以被强加于值类,但不一定如此。在一些实施例中,实用程序方法能够逐个值类型地定制。

名称。诸如JVM类型系统之类的一些类型系统几乎全部是标称的(nominal),而不是结构的。类似地,在一些实施例中,值类型的成分可以通过名称识别,而不是仅通过元素号识别。(这使得值类型更像记录而不是元组)。

互操作性。在诸如JVM之类的一些虚拟机中,存在针对具有值对应物的泛型对象的若干操作,诸如equals、hashCode和toString。在一些实施例中,虚拟机(在缺乏来自值类型的作者的指令的情况下)按成分实现这些方法。除了对象(Object)之外,诸如Comparable(可比较型)之类的其他类型还可以与合适的值类型互操作,从而向诸如TreeSet<UnsignedInteger>之类的类型打开大门。

封装性。在一些实施例中,值类型具有私有成分,正如对象具有私有字段。这使得例如安全的外部(foreign)“指针”或游标成为可能。值类型可以被配置为阻止潜在的攻击者通过提取私有成分或通过合法操作的勉强组合造出非法值来摧毁封装性。

可验证性。在一些实施例中,虚拟机104的指令集中的对于值类型的支持是能够被验证器106验证的。

变量。在一些实施例中,诸如字段、数组元素、局部变量和方法参数之类的变量类型都被配置为保持值类型。此外,方法被配置为返回值类型。此外,修饰符(诸如Java中的final和volatile)可以被应用于值类型以实现它们的常规效果。

数组。在一些实施例中,值类型的数组被打包(packed),非间接地、类似于原生的数组。这不一定意味着数组本身被打包在其他对象内部。因此,数组可以保持值类型元素的扁平化表示,但是如果数组是对象的字段,则对象可以存储可被分配在堆302上的数组的指针。

扁平化。值类型提供了用于表达具有较少指针间接的数据结构的自然方式。因此,使用值类型来使数据扁平化允许虚拟机104更高效地布置一些数据结构。

3.3示例性限制

由于值类型不具有标识,因此存在某些操作,一些实施例可以选择不允许对值类型进行这些操作或者为这些操作分配新的含义以供在值类型的背景下使用。在一些实施例中,对值类型的限制在虚拟机104中被实现为供验证器106遵循的附加规则。因此,如果类内的方法包含违背规则的代码,则生成异常或错误。以下是一些示例:

锁定。为了支持诸如在Java语言中执行的同步,对象有时被用作经同步的语句的锁对象。在一些实施例中,由于缺乏对于该特征所期待的头部信息,因此虚拟机104不允许针对这些语句使用值类型。此外,在一些实施例中,虚拟机104不允许针对值类型调用与同步相关的方法。例如,Java中的wait(等待)和notify(通知)方法。

标识比较。(在Java中)针对对象的“==”操作符执行标识比较,但是针对原生,该操作符执行按位比较。由于值类型不一定具有指针,因此该操作符不适用于值类型。在一些实施例中,通过递归地执行“==”操作(最终基于较低级别的对象/原生来评估)、调用equals方法和/或执行按位比较来执行解释针对值类型的诸如“==”之类的操作符。

标识散列码。诸如经由Java中的System.identityHashCode之类的用于对象的基于标识的散列码不适用于值类型。做出对象的基于标识的区分的、像序列化那样的内部操作要么不适用于值类型(由于它们不适用于原生),要么会使用由值类型定义的单独的散列码方法供应的基于值的区分。例如,按成分的散列计算可以被还原为默认。然而,当值类型的成分是私有的时,这可能泄露可被攻击使用的信息。

克隆。在一些实施例中,虚拟机104将诸如Java中的Clone方法之类的克隆解释为对于值类型的标识转变。

终止化(finalization)。在一些实施例中,对于值类型的终止化是不允许的,因此在许多实施例中对值类型的终止化将没有用处(但是值类型可以保持自身可终止化的引用类型的成分)。

以上限制中的许多限制对应于Java中针对所谓的基于值的类的限制。实际上,在实施例中,每个值类型的装箱形式是基于值的类。

在一些实施例中,虚拟机104对值类型做出“软”限制以确保值类型在存储空间和/或成分数量方面不会“太大”。由于值类型通过值来传递,因此随着成分数量增加,复制许多成分的开销越来越可能压倒通过移除指针和对象头部的开销而获得的任何节省。由于类对象是使用指针通过引用来传递,因此大组的成分值可以可替代地使用类来建模。

在一些实施例中,值类型的精心构思的使用通过省略对象指针和头部以及定期通过值传递成分来获得性能和复杂度的益处。具有少量成分的值类型通常整齐地匹配大多数当前的中央处理单元(CPU)(其对寄存器文件的尺寸具有相对较小的限制)的能力。当超过这些限制时,一些实施例中的虚拟机104具有将值类型分散到栈(诸如虚拟机栈310/313)和/或所管理的堆302存储器上的灵活性。

在一些实施例中,取决于值类型的尺寸和/或值类型是否在不能被修改的容器中,虚拟机104在通过值传递值类型或通过引用传递值类型之间切换。在后面的节中以更多细节探索该特征。

3.4指针管理

当使用值类型的扁平化表示时,值类型不被指针访问或者这种指针在虚拟机104实现内对用户隐藏。因此,即使虚拟机104在底层使用指针,虚拟机104也确保从用户的视角来看值类型遵循通过值传递的行为。在一些实施例中,某些附加操作也是虚拟机104不允许的或者被虚拟机104分配新的含义。

空值(null)。空值是每个引用类型的有效值,其意味着“没有实例”。然而,在值类型的背景下,空值可能是不适用的,这与在大多数语言中空值通常不适用于原生大致相同。因此,在一些实施例中,对于值类型来说被指派为空值以及与空值相比较是不允许的。然而,该限制将不适用于值类型的装箱形式,其中将值类型装箱的容器对象可以保持空值并且可以与空值相比较。

副作用。尽管包含整个值的变量可以被重新指派为不同的值,但是值的单独部分通常不受到分段的副作用的影响,就好像它们通过对于该值的指针被访问一样。因此,整数可以从2被更新到3,但是在一些实施例中用户被限制为整个值指派。结果,不向用户提供可以改变2的最低位以将该值改变为3的代码指令。诸如Java之类的某些语言在该方向上给出的是像intval|=1一样的复合指派操作。此外,在第一变量处将值从2改变为3不会导致其他地方的第二变量变成3(当两个变量都指向值被改变的同一存储器位置时会出现此情况)。结果,在一些实施例中,(从用户的角度来看)虚拟机104阻止了分段的副作用。然而,尽管从用户的视角来看,值类型看上去是作为整体被替换(作为不可变性的性质)并且不会导致对于其他变量的副作用(作为扁平性的性质),但是在一些实施例中在底层虚拟机104可以执行违背该性质的优化。例如,尽管在语义上代码表现的就像Point A被Point B完整地替换一样,但是在底层,虚拟机104可以仅替换不同的成分以作为优化的形式。作为另一示例,假如在语义上虚拟机104诸如通过仅在值类型不能改变的(例如,值类型是最终的)情况下执行该优化来阻止副作用,则在值类型较大的情况中,指针可以被用于最小化复制开销。

在一些实施例中,语言允许通过指针访问值类型,多达并且包括成分变化。在这种实施例中,该概念的复杂度协助本地数据结构以及虚拟机104数据。

引用转换。在一些实施例中,像原生类型那样,值类型可以被装箱以便被用作引用。因此,在一些实施例中,虚拟机104具有用于隐式地对值类型进行装箱和拆箱的规则,类似于Java当前怎样执行用于原生值的装箱和拆箱。例如,通过基于上下文在整型和相应的Integer(整数)值类之间自动转换。

指针多态性。在一些实施例中,对象并入运行时类型信息,方法可以是虚拟的,类型可以在运行时被测试,以及(许多)静态引用类型可以在运行时指向一系列具体的子类。在这种实施例中,由于指针变量尽管具有固定的尺寸但是可以指代任何尺寸和类型的数据,因此前面提到的特征起作用。由于值类型没有头部来承载类型信息并且被设计为被扁平化到它们的包含对象中,因此值类型(在一些实施例中)不能被动态地类型测试并且不能具有可变的尺寸。结果,在一些实施例中,虚拟机104阻止子类化(sub-classing),因为该特征对于值类型没什么用处。然而,诸如Java中的Comparable接口之类的抽象的超类和接口仍然可以被使用,因为这些结构体不能被分开实例化。在一些实施例中,值类型实现头部信息的缩减集合,而不是没有头部信息,诸如包括尺寸数据以支持可变尺寸,但是不包括用于实现标识特征的诸如标识号(例如,对象的存储器地址)之类的其他信息。

反射(reflection)。作为指针多态性的极端情况,在诸如Java之类的语言中,任何对象可以被转换为顶级类型Object并且可以针对它的字符串表示、它的类、它的字段和字段值的集合等等而被检查。由于在一些实施例中值类型不支持超类,因此虚拟机104可以禁用转换值类型并且因此还禁用反射。然而,在一些实施例中,值类型的方法可以包括创建与从Object类型生成的字符串表示类似的字符串表示的方法。

原子性。指针提供了到其对象(可以原子地改变)的访问路径。因此,竞争的线程观察到旧对象或新对象,而不是二者的混合。相比之下,多字段值可能经受数据竞争的,在数据竞争中竞争的线程可能观察到新字段值和旧字段值的混合。在当前的语言中,在长整型和双精度型类型的情况下该“结构撕裂”问题有时可以作为极端情况被观察到,但是在值类型的情况下该问题更加急迫,因为它可以严重地损害封装性。在一些实施例中,虚拟机104在类型使用位置和类型定义位置这二者处按照请求供应原子性。这类似于诸如Java之类的语言在在长整型和双精度型变量被声明为易变的情况下如何在类型使用站点处按照请求供应原子性。在当前硬件中保证原子性的成本足够大以致于对于不要求原子性或仅要求某些变量具有原子性的程序,始终要求原子性可能导致大量不必要的开销。精确的折衷跨平台变化。

3.5示例性定义

在一些实施例中,值类型的一些特性是类常见的:一批经命名的、类型成分(字段)、行为(方法)、隐私(访问控制)、编程初始化(构造器)等。因此,在一些实施例中,使用特殊标记的类文件来定义值类型。然而,虚拟机104可以经由验证器106对这些类文件施加特殊的规则,诸如在先前的节中所描述的限制。类似地,用于值类型的语法元素可以被精心设计以便看上去类似于源语言中的写类。

在诸如Java之类的一些语言中,存在采取该方法的先例,诸如使用类文件103来表示接口,因为这些接口还与普通类有相似之处。除了模式位和小语法的附加之外,在一些实施例中,接口的二进制可以与适当的类的二进制相同,除了接口具有附加的语法和语义限制。类似地,值类型可以在源代码文件101中定义并且使用语法的小改变来写,但是具有由验证器106应用于所得到的类文件103的附加限制。

通过使用示例性语法,之前提供的Point示例可以被重写为如下的值类型:

上面的示例和引用等价物之间的语法区别从将结构体标记为值类型而不是适当的类的关键字_ByValue开始。然而,这仅是示例性的语法,将结构体标记为值类型的任何方式是容许的,诸如通过关键字、符号或任何其他语法来标记。此外,诸如字段、结构体和/或方法之类的用于源语言的相同的编码惯用语法可以被重复使用以将这些值/行为添加到值类型的定义。在一些实施例中,如果用户在源代码文件101中未提供实用程序方法(并且语言允许该省略),虚拟机104或编译器102填补某些实用方法(诸如按成分的hashCode、equals或toString)。

如果另一个类声明类型Point的字段,则虚拟机104将分配用于在托管对象中的x和y成分的存储,而不是对Point箱的引用。这可以被视为对象内联或扁平化。类似地,Point的数组将被布局为交替的x和y值的经打包的数组,而不是对存储x和y值对的容器的一系列引用。

在一些实施例中,由于与类相似地使用特征和语法来向用户暴露值类型,因此值类型的定义对于源语言的程序员来说立刻是清楚的。然而,在各种实施例中不适用于值类型的一些熟悉的特征(诸如Java中的extends或protected)可能是不适用的。因此,如果用户尝试在值类型的定义中包括这些属性,则虚拟机104的编译器102或验证器106抛出异常或生成错误。

在一些实施例中,源语言已经具有表达限制的关键字或其他机制,诸如子字段在构造之后的不可变性。例如,Java中的final关键字。因此,如果用户不再使用final关键字,则编译器102可以提供解释该关键字必需的错误或者隐式地将关键字添加到所生成的类文件。此外,在一些实施例中,如果final属性丢失,则虚拟机104隐式地将final属性添加到所生成的类文件,或者将该属性当作已经被设置。

3.6示例性用法

前一节描述了值类型可以如何以与类相同的方式编码,因此下一个问题是在虚拟机104中如何定义行为以提供好像值类型是原生那样的对待。下面是Point类的一些示例性使用:

在这些示例中,Point可以是字段、参数、局部变量或者返回值。在所有这些情况中,在一些实施例中虚拟机104将Point的成分放置在独立地管理的位置中,诸如机器寄存器。displace方法返回新的Point值。在一些实施例中,虚拟机104通过值而不是通过(不可见的)引用来返回Point。

在一些实施例中,像原生那样,值类型可以引导重载。这里是与上面兼容的用于stringValueOf的一些重载:

使用Java作为示例,值类型的以上用法在若干方面不同于原生。

第一,用于成员选择的点语法适用于值类型,如p.x或p.equals。诸如Java之类的一些语言中的原生根本不支持点语法。在一些实施例中,使用值类型来替换或补充表示原生的值类(诸如整数(Integer)),从而为针对在一些情况中是标准原生的类型(诸如整型、浮点型、双精度型等)应用方法打开大门。

第二,在一些实施例中,虚拟机104不提供对于诸如Point之类的由用户定义的值类型的文本的支持。替代地,定义的构造函数被调用以设置成分的值。在一些实施例中,为了确保封装性,按成分的值创建除非由合适的构造函数提供,否则不被虚拟机104允许。上面的示例使用关键字__MakeValue来标记上面的两个位置:新的Point值被创建的位置以及这种构造函数被调用的位置。然而,替代的语法还可以被用于表示值类型构造函数,诸如“new Point”、“Point”以及空字符串。类似地,一些实施例不提供对于用于值类型的编译时常量的支持。在其他实施例中,虚拟机104提供对于用户定义的文本和常量表达式的支持。因此,在一些实施例中从被执行以建立常量表达式的编译时代码构建用户定义的操作符。

第三,用于诸如Java之类的一些语言的内置操作符是利用原生工作的主要方式,但是在一些实施例中这些内置操作符不适用于Point。然而,用户定义的操作符可以被用于提供功能等价物。一些例外可能是作用于每个种类的值的操作符,诸如关系操作符(==和!=)和字符串串接(+)。字符串串接可能是棘手的,因为在一些实施例中字符串串接在值类型驻留的类和原生类型之间的区分处被精确地精妙地重载。例如,如许多Java程序员通过试错法(trial and error)发现的,表达式("a"+1)+2和"a"+(1+2)具有不同的结果,因为第一个表达式首先转换为字符串从而形成“a12”,而第二个表达式在加法后转换为字符串从而形成“a3”。

在一些实施例中,关系操作符被当作用于判等方法(类似于Java中的equals)的别名。另外,低级别的按位判等是另一种候选方法。然而,在一些实施例中,关系操作符可以潜在地导致值类型的装箱形式和拆箱形式之间的行为差异,使用Java作为示例如下所示:

Point p1=__MakeValue(0,1);

Point p2=__MakeValue(1,0);

Point p3=__MakeValue(1,0);

assert(!p1.equals(p2));//值判等

assert(p1!=p2);//再次值判等

assert(p2.equals(p3)&&p2==p3);

Object box1=p1,box2=p2,box3=p3;//将它们装箱

assert(box1==box1);//x==x总成立(除了NaN(非数字))

assert(!box1.equals(box2));//Object.equals调用Point.equals

assert(box1!=box2);//还必须是

assert(box2.equals(box3));

if(box2==box3)println("the cat died");//不确定

assert((Point)box2==(Point)box3);//返回到Point.equals

if(box2==(Object)p2)println("the cat died");//不确定

利用小调整,在Java中上面所有的测试可以在整型和整数(Integer)的情况下被重现。然而,对于一些小的整数值,由于Integer.valueOf工厂方法的特点而非原生、值类型或箱的内在特征,猫不会存活。

第四,尽管原生不具有对应于空引用的值,但是原生具有一般是零的默认值。该默认值在未初始化的字段和数组元素的初始内容中出现。因此可以为每个值类型指定默认值。例如,一种技术是将复合值设置为在其所有子字段中是递归默认的。假如预定义的默认值在诸如Java之类的一些语言中是固定值,则该约定适用于许多语言背景。

在一些实施例中,即使构造函数从未创建默认(全零位)的值类型,值类型定义中的方法的作者也可以向默认的值类型提供行为,因为这些值类型在一些未初始化的变量中可以被观察到。在实施例中,虚拟机104要么禁止无参数的构造函数要么要求它们产生默认值,以便于避免“决斗的默认值”。

在一些实施例中,作为替代,虚拟机104通过强制包括公有的空构造函数来迫使显式地构造默认值,像这样:public Point(){x=0;y=0;}。在公有的空构造函数中,所有子字段被初始化为默认值,否则编译器102和/或验证器106将报告错误。可替代地,虚拟机104可以不允许空构造函数,并且为默认值保留其含义。

在一些实施例中,作为替代,虚拟机104允许通过对标准类定义施加的模式或语法来指定更精心制定的默认值。然而,对此存在三点考虑:(1)在一些情况中默认值将要求特殊的扩展而不是简单的限制,(2)在一些情况中默认值将包括特殊的规则以在来自空构造函数的副作用的情况下编码,以及(3)相比于全零位的约定,在运行时应用由用户指定的默认值可能潜在地具有更高的成本。

3.7示例性实现细节

在一些情况中,类的某些特征对于用于特定实施例的值类型将没有意义。下文以问题和回答的格式表示根据特定实施例的用于值类型的设计和/或实现决策。然而,下文所描述的回答不是决定性的;其他实施例可以提供对下文所呈现的问题的不同回答。因此,其他实施例对于每个问题可能有所不同。在一些实施例中,本节中所描述的关于值类型的限制在虚拟机104中被实现为供验证器106遵循的附加规则。

子类型化(subtyping)

值类可以扩展引用类类型吗?在大多数情况中,不可以。

引用类可以扩展值类型吗?在大多数情况中,不可以。

具体的值类可以扩展另一值类型吗?在大多数情况中,不可以。(因为具体的值类是最终的。)

值类型定义可以是抽象的或非最终的吗?在大多数情况中,不可以。

值类型定义可以实现接口吗?可以。当我们将值类看作接口实例时,装箱可以发生。

值类型可以参与基于继承的子类型化吗?在大多数情况中,不可以。(就像原生那样。)

是否存在用于值的根类型(诸如Java中的Object超类型)?不一定。在一些实施例中,值类型的超类型可以被实现为模板化机制。

包含

值类型可以包含引用类型的字段吗?可以。

引用类可以包含值类型的字段吗?可以。

值类型可以包含值类型的成分字段吗?可以。

数组可以包含值类型的元素吗?可以。(在一些语言中,数组是对象的类型)。

值类型可以包含数组类型的字段吗?可以。(例如,当数组是对象时)。

值类型可以包含非最终字段吗?在许多实施例中,不可以,但是其他实施例可以允许具有非最终字段的值类型。

值类型的所有字段都必须是递归不变的吗?不一定。(递归不变性是类型的独立特征)。

值类型可以具有成员类型吗?可以。(非静态成员类型具有记录包含值的隐藏字段)。

值类型可以是成员类型吗?可以(如果它们是非静态的,则它们具有指向包含对象的隐藏字段。)

当创建值类型的数组时,初始元素是什么?它们都是默认的全零位值。(就像原生那样。)

其类型是值类型的字段的初始值是什么?默认的全零位值,直至第一次指派或初始化。

其类型是值类型的局部变量的初始值是什么?看情况,在一些实施例中指派规则禁止观察这样的值,直至第一次指派。

值类型可以包含其自身类型的字段吗?在大多数情况中,直接地或间接地都不可以(类似于一些语言中的现有限制:类不能将其自身作为子类)。

兼容性

值类型是对象吗?不一定,尽管值类型可以被装箱成对象。

原生是值类型吗?可能是。(值类型命名的整型、布尔型等将向包装器类提供良好的继承者,就像它们存在于诸如Java之类的当前语言中那样。)

值类型的数组是否为协变的?在大多数情况中,不是。(就像原生那样,值类型箱的数组是协变的)。

值类型是否参与隐式转换?在大多数情况中,不参与。(在一些实施例中,虚拟机104不需要实现隐式转换,诸如Java中的int toUnsignedInteger)。

每个值类型都定义了类似对象的方法(toString等)吗?有时是。在一些实施例中,虚拟机104需要类似对象的方法以使得它们的装箱形式可以与期待对象而不是值类型的现有API互操作。然而,不是在所有实施例中都需要类似对象的方法。

是否存在用于这些方法的默认的按成分的实现?是的。例如,用于箱的根类型是用来放置默认的按成分的代码的逻辑位置。

值类型可以被序列化吗?看情况。在一些实施例中,为了阻止违背封装性,虚拟机104不实现用于值类型的序列化。然而,在其他实施例中,虚拟机104可以实现用于值类型的序列化。

值类型的用户可以请求原子性吗?可以。在一些实施例中,可以使用诸如关键字volatile或_AlwaysAtomic之类的关键字指定原子性。

当将值类型指派给泛型对象时发生了什么?虚拟机104基于值类型的类文件自动将值类型装箱到虚拟机104生成的类中。生成的类文件反映值类型的字段和方法,但是生成的对象包含头部信息并且因此可以被用于标识以及被用于与期待对象的API的互操作性。

当空值被分配给值类型(或者与值类型相比较)时发生了什么?如果该值类型是装箱的,则引用比较将返回不相等;如果值类型是拆箱的,则抛出异常。

封装性

值类型可以具有非公有的字段吗?可以。

值类型可以具有非公有的方法吗?可以。

值类型可以具有静态方法或字段吗?可以。

值类型是怎样构造的?通过调用构造函数。值类型构造函数事实上是工厂方法,因此(使用JVM字节码作为示例)在字节码中不存在new;dup;init跳跃(dance)。

值类型的定义者可以要求其所有成分值的原子性吗?可以。例如,通过在值类型定义中使用关键字或符号,这些关键字或符号指示操作将始终是原子的。

值类型的封装性可以被布置为排除所有默认的按成分的操作吗?可以。(例如,通过重载相关方法)。

值类型可以在不要求客户机重新编译的情况下改变尺寸或布局吗?可以。

值类型的封装性可以被布置为排除其所有字段是零的值吗?在大多数情况中,不可以。(如上文所指出的,这是慎重的妥协。如上文所指出的,一些实施例中的默认值可以与无参数的构造函数值一致。)

诸如字节码之类的虚拟机指令可以以访问值类型的私有字段的方式被使用吗?取决于实施例。

其他细节

是否存在对值类型的复杂度的限制?是的。在一些实施例中,虚拟机104对值类型的成分的数量和/或尺寸施加限制以便于做出性能保证。例如,但是不足够低以做出性能保证。例如,值类型可以被限制为诸如255字之类的阈值。

虚拟机104支持通过拆分超类来重构值类型吗?在大多数情况中,不支持,除非虚拟机104允许抽象的值类型。

虚拟机104支持将方法重构到值类型中或从值类型中将方法重构出来吗?支持,例如,与默认方法的公共接口可以被用于该目的。

虚拟机104支持对于装箱的值调用值类型的方法吗?支持。在一些实施例中,虚拟机104自动对值进行装箱和拆箱以创建用于API的桥。例如,当使用Point时,取决于上下文和环境,该表示将有时为值类型,有时为箱引用类型。

在一些实施例中,虚拟机104禁止值类型的子类化和子类型化以避免指针多态性。抽象超类型(比如Java中的接口Comparable)可以被考虑,因为它们不能被实例化。结果,虚拟机104可以因此确保所有方法以方法接收者的确切类型被明确地解析。在一些情况中,该决策还回避了值类型数组的问题,如果数组可以包含(例如)2字段Point和3字段ColoredPoint(染色点)值,则值类型数组可以包括尺寸可变的元素。对于数组,由于原生数组已经具有完全同构的元素,因此依赖原生语义是方便的。然而,该方法不是没有问题。例如,对于实现Comparable的值类型Bignum,虚拟机104可以允许箱的数组Bignum.__BoxedValue[]是接口数组Comparable[]的子类型,但是在一些实施例中扁平的数组类型Bignum[]不能也是Comparable[]的子类型。

3.8示例性字节码和类型描述符

存在可以在虚拟机104的指令集(例如,字节码)中显现值类型的许多方式。例如,用于值类型的全新指令可以通过将值类型当作全新的结构体或者重载现有指令以接受值类型或引用来实现。本节关注可以被实现为生成、加载、存储和/或以其他方式操纵值类型的新指令。然而,如上文提到的,其他实施例可以重载现有指令以实现类似的效果。

建立在上面“示例性类文件结构”中所讨论的描述符示例上,存在用于原生的单字母类型描述符(例如,用于整型的I)、用于类的“L”描述符(例如,L ClassName),并且对于任何类型,可以通过追加“[”来为该类型的数组导出类型描述符。在实施例中,为值类型添加另一形式;用于值类型com.foo.Bar的类型描述符将会是Q com/foo/Bar;这就像用于引用类型的“L”描述符一样,除了不同的符号“Q”。例如,考虑以下方法:

public static double getR(Point p){...}

用于以上方法的签名将会是(Q Point)D,这指示它采用值类型Point的单个参数并且返回双精度型。

新虚拟机104指令的示例以如下格式描述:

//描述

opcode[constant pool operands]stack operands->stack result

在一些实施例中,对于所有操作码,“qdesc”是用于值类型的Q描述符。然而,对于许多指令,验证器106可以从指令的上下文推断描述符。因此,如果这样的描述符可以替代地通过上下文来推断,则一些实施例可以选择忽略来自指令的这种描述符。用于局部变量401的表槽(slot)和用于操作数栈402的表槽可以保持值类型以及原生或引用。

在一些实施例中,值类型消耗局部变量401和/或操作数栈402中的单个槽。因此,每个槽不必具有固定长度的尺寸。然而,在其他实施例中,值类型消耗多个槽,类似于在一些JVM实现中诸如双精度型之类的尺寸过大的原生类型如何被设计为占用两个槽而不是一个槽,从而使得槽可以具有相等的尺寸。在一些实施例中,虚拟机104可以在不重新编译代码的情况下改变槽的尺寸和布局。后面的节中讨论关于虚拟机存储器布局300内的值类型的存储的附加细节。

在实施例中,为了操纵栈帧400上的值类型,虚拟机104支持以下指令。同样,尽管以下示例中的一些形式可以认为是不必要的,但是示例顾及到明确性和完整性。因此,在一些实施例中,新指令的数量可以远小于下文所描述的新指令的数量。

//从局部变量加载值类型

vload[qdesc?,index]->value type

//将值类型存储到局部变量中

vstore[qdesc?,index]value type->

//创建值类型的新数组

vnewarray[qdesc]size->arrayref

//将值类型存储到数组中

vastore[qdesc?]arrayref,index,value type->

//从数组加载值类型

vaload[qdesc?]arrayref,index->value type

//提取来自值的成分

vgetfield[field-desc]value type->result

//将成分插入到值类型中(为了构造函数的私有使用)

vputfield[field-desc]value argument->value type

//构建具有全部默认的成分的新生值类型(为了构造函数的使用)

vnew[qdesc]->value type

//对值类型调用方法

vinvoke[method-desc]value type,(argument)*->(return)?

//返回值类型

vreturn[qdesc?]value type

在一些实施例中,虚拟机104支持可以对任何类型的帧400内容操作的若干“多态的”指令,诸如JVM字节码集中的“dup”;这些指令可以被扩展为同样支持值类型。在大多数情况中,这些多态指令可以用最少的努力被重载到现有指令上。使用JVM指令码集作为示例,vinvoke功能可以被重载到invokestatic上,而vgetfield功能可以被重载到getfield上。

在一些实施例中,上文提到的字段和方法描述符将会是常量表201引用,诸如指代方法引用结构207、字段引用类型206和/或类信息结构203的引用。在一些实施例中,描述符的成分通过值类型的标称名称(例如,没有任何“Q”前缀)指代值类型。对应类的装箱形式具有例如从描述符语言(例如,L Foo)导出的它们自己的字节码名称。在一些实施例中,虚拟机104支持克隆常量表201类型,诸如CONSTANT_ValueMethodref,使用Java类文件作为示例。

在一些实施例中,值类型中的静态方法重复使用为适当的类实现的相同指令,而不需要特别指向到值类型的特殊指令。例如,使用JVM字节码集作为示例,可以使用invokestatic来调用值类型中的静态方法。在一些实施例中,类似于静态方法,静态字段可以重复使用由虚拟机104实现的用于操纵静态字段的相同指令来操纵适当类的静态字段。

在一些实施例中,虚拟机104阻止构造函数调用通过引用传递要构造的值类型。替代地,值类型构造函数被实现为将作为静态方法被调用的静态工厂方法。用于成分初始化的各变化步骤通过特权指令vputfield的使用而被内部地呈现给构造函数,该特权指令vputfield与为用于初始化属性对象的最终字段的构造函数保留的特殊指令(诸如JVM字节码中的putfield)等效地操作。要注意vputfield返回更新后的值,因此不存在副作用发生的机会。

在一些实施例中,虚拟机104将如构造函数使用的vnew和vputfield操作符的效果结合到vpack指令中,vpack指令将会采用栈上的一系列成分。这些成分的顺序和类型将由包含值类型定义隐式地定义。这将使得一些简单的构造函数稍微更加紧凑,但是可能导致虚拟机104中实现的指令及其包含类之间的新型耦合。在一些实施例中,虚拟机104对作为命名字段的簇(cluster)的复合类型进行操作,而不是对入栈的值的有序元组进行操作。在一些情况中,如果每个字段初始化具有其自己的vputfield操作码,则虚拟机104指令和源代码之间的对应关系可以更易于追踪。

在一些实施例中,当从较高级别的指令生成较低级别的指令时,改变某个值类型的值内的字段的一个或多个较高级别的指令可以被识别。该值可以是例如潜在地具有多个不同的成分字段的值类型的实例。本文描述了这种值类型的示例,诸如点(Point)或复数(Complex)。可以确定的是,在较低级别的指令的上下文中,用于该值的至少一个成分字段的数据将被集体存储在单个容器内。例如,字段可以被存储为其对应的子值的拆箱表示,而不是对存储相应子值的其他容器(多个容器)的引用。响应于该确定,生成单个较低级别的指令。这种较低级别的指令的一个示例是本文所描述的vputfield指令。

在一些实施例中,所生成的单个较低级别的指令被配置为命令解释器108识别与字段对应的容器的一部分,诸如一个或多个位。单个较低级别的指令还被配置为命令解释器108根据一个或多个较高级别的指令来改变该部分。识别和/或改变可以至少部分基于与值的值类型相关联的元数据。例如,指令可以使得解释器108使用用于值类型的字段定义来定位该部分和/或实行用于对应的值类型定义的访问控制信息、类型检查等等。在实施例中,单个较低级别的指令命令解释器108在不使用指针定位字段的情况下和/或在不必发出使值开放到其成分字段中的分开指令的情况下进行该操作。

在实施例中,可选择性地通过重新排序、合并或分开彼此相关并且与可以读或写存储在容器中的值或容器的至少一些成分字段的任何其他指令相关的两个或更多个这种较低级别的指令的效果来生成较低级别的指令。这种效果的重新排序、合并或分开可以受保护存储在容器中的值的每个成分字段以及存储在容器中作为整体的值的一致性的规则影响。因此,技术可以包括使用分层的数据结构的嵌套存储器模型,当对象拆分成值时,其可以拆分成更小的值并且最终一直向下拆分成成分原生和引用。

根据实施例,一种方法包括:当解释诸如Java字节码之类的指令时,识别指令以将第一个较小的值作为第二个较大的值的字段插入。该指令可以是例如单个较低级别的指令,诸如上面生成的指令。第二个值可以是例如具有集体存储在容器中的多个不同成分字段的经定义的值类型的值。第一个值可以是例如对应于字段的子值。在实施例中,确定第二个值具有某个值类型。基于与某个值类型相关联的元数据,对应于字段的第二个值的一部分被定位,并且第一个值被加载到该部分中。这可以包括例如基于与某个值类型相关联的值类型定义数据来实行访问控制信息和类型检查,和/或使用值类型定义数据来定位相关部分。在实施例中,选择性地通过重新排序、合并或分开两个或更多个这种较低级别的指令的效果来生成较低级别的指令,诸如上文所描述的。

3.9装箱和对象互操作性

在实施例中,每个值类型具有对应的箱类,就像在Java中Integer是用于整型的箱类型。在一些实施例中,虚拟机104或编译器102从用于值类型的类文件中所提供的定义自动导出用于值类型的箱类的类文件(或者反之亦然),而不是要求值类型及其对应的箱的分开编码。例如,方法和字段可以从值类的类文件得到,但是其中箱类文件包含标识符,该标识符指示虚拟机104应当将箱类文件解释为指代适当的对象类。箱类文件还可以包含可以不存在于值类型的类文件中的附加的元数据,诸如超类(例如,Java中的Object超类)的存在。可替代地,虚拟机104可以生成对于值类型和箱表示这二者通用的一个类文件,其中类文件取决于上下文而被虚拟机104不同地处理。例如,如果值类型使用描述符“Q Foo”,则用于对应的箱类的描述符可以是“L Foo”。对于这两种类型,虚拟机104将知道寻找被称为Foo的类文件并且基于哪个描述符被使用来生成值视图或引用视图。

在一些实施例中,诸如构建之类的公共操作具有用于值类型或箱类的不同机制。例如,编译器102可以生成多个版本的类工件(class artifact)(例如,具有签名(...)V的标准构造函数和具有签名(...)Q Foo的值构造函数)或者虚拟机104将根据需要从其中一个的指令导出另一个。在一些实施例中,对于值类型Foo,Foo可以被写为指代值形式,而Foo.__BoxedValue被写为指代Foo的装箱形式。

在实施例中,值类型的装箱表示和拆箱表示之间的转换像诸如Java之类的一些语言中当前的原生箱(例如,valueOf(boxed))那样通过遵循命名约定的方法来表示,或者可以由像下面的转换指令来表示:

//将值装箱

v2a[qdesc]value->ref

//将值拆箱

a2v[qdesc]ref->value

值之间的比较(类似于JVM字节码集中的acmp和icmp字节码)也可以由遵循命名转换的方法来表示,或者用像下面的特殊指令来表示:

//使用按成分的、按位的以及引用判等来比较两个值

vcmp[qdesc?]value,value->boolean

在一些实施例中,尽管在一些情况中像操纵原生和引用的指令那样处理新的值类型指令的“克隆”是有用的,但是在实施例中转换和比较指令可以使用方法调用(例如,vinvoke)来实现。

在一些实施例中,像数组那样,箱类由虚拟机104生成。在实施例中,系统将箱代码尽量多地分解为常见的抽象超类。在Java中,这大约是为经典对象采取的方法,其中默认行为在java.lang.Object中找到。

3.10示例性代码

示例:构造

在本节中使用上面在“新字节码和类型描述符”中介绍的新指令与来自JVM字节码集的指令的组合来描述创建和使用值类型的示例。尽管JVM字节码被用于提供示例,但是由示例描述的技术不限制于Java、JVM或任何特定语言或虚拟机架构。

在一些实施例中,通过首先执行创建未初始化的对象的指令并且然后调用构造函数来初始化对象。用于值类型的构造函数的行为与工厂方法更类似。下面示出了为Point的构造函数生成的示例性指令。

为了在栈帧400的局部变量401中创建Point的实例,工厂方法被调用,并且结果被存储在本地:

在上面的示例中,为了简化阐述,值类型的构造函数的名称被更改为<new>,以强调该构造函数是工厂方法。

示例:嵌套值

在上面的示例中,当读取嵌套值时,首先读取它的封闭值。

示例:数组

在实施例中,利用vnewarray指令来创建数组,并且非常像任何其他数组类型那样操纵数组。以下是示例:

示例:方法调用

在实施例中,静态地解析对于值类型的方法。invokestatic指令对于调用值类型的静态方法和非静态方法这二者(作为指令/字节码语法)将会是足够的,但是为了清楚地阐述,一些实施例对于值类型的非静态方法使用新的调用指令vinvoke。以下是示例:

示例:装箱

在一些实施例中,当值类型被指派了类型Object、它的箱类型或它实现的接口类型的变量时,变量的内容由编译器102和/或虚拟机104自动装箱。例如:

示例:拆箱

在实施例中,当值类型从类型Object、它的箱类型或它实现的接口类型的变量被分配时,首先检查引用以查看它是否是对应的箱类型,并且(如果是的话)然后值被拆箱。以下是示例:

在一些实施例中,装箱引用具有对它直接调用的值类型的方法,例如:

示例:扁平化的散列表

在一些实施例中,因为值类型没有指针,所以值类型可以被用于通过移除间接寻址的级别来使一些数据结构扁平化。扁平化不仅移除了来自关键路径的依赖性负载,而且(典型地)将相关数据移动到相同的高速缓存线上。

在一些情况中,调优良好的、尺寸可变的散列表可以在不超过两个高速缓存线引用中答复查询,其中一个引用用来针对表尺寸咨询表头部,一个引用用来探查表中的条目。如果表条目在承载表的数据数组内是扁平化的,则这可以实现,并且在值类型的帮助下这成为可能。

上面的代码实际上触摸了三个高速缓存线,以便于从数组头部提取数组长度。在一些实施例中,解决该问题包括将数组长度向上提升到散列表头部中。

上面的代码避开将具有默认值的条目(零散列、空字符串引用)与寻找条目失败区分开的问题。这是使面向指针的算法(例如,在Java中)适应于值类型的典型问题。这可以由不同的实施例以若干方式解决。一种方式是引入表达可选择的条目(条目加上布尔型)的值,并且从getEntry返回该值。要注意在用于getEntry的慢路径的末端处,实现默认(空)条目值的能力在此可以是有用的。出于上面给出的原因,默认值是一些语言景观(诸如Java)的部分。

在上面的示例中,__AlwaysAtomic关键字的效果是确保数组元素值在没有内部竞争的情况下被一致地读取。在没有该修改的情况下,在一些平台上,结构撕裂可能导致值可能与错误的键相关联的状态。该危害来自扁平化;它在数据结构的“指针丰富的”版本中不会呈现。

在一些实施例中,计算平台能够借助于64位存储器引用来在没有额外成本的情况下实现用于该数据结构的原子性。这假设值成分可以被打包为32位,这是通常的情况。在一些情况中,甚至四个成分的值可以容纳在128位中,并且大多数硬件平台以合理的成本提供原子的128位读和写。对于较大的值,比如具有五个或更多个成分的值,用于原子性的成本将急剧增加,这是原子性不是默认的原因。

要注意为了对于整型和字符串型起作用,该示例性数据结构是“硬连线的”。对应的泛型类将从扁平化的数组获得一些益处,但是可以不直接对拆箱的整型起作用。在一些实施例中,虚拟机104实现类似模板的机制来对非引用进行参数化以解决该问题。

示例:比较

如上文所指出的,在一些实施例中值类型可以实现接口。这是支持比较的值类型的示例:

自动创建的箱类适当地实现接口,桥接到所给出的compareTo方法。vinvoke指令还可以直接调用如所编写的compareTo方法。

这是对接口函数的调用的一些示例。

像类那样,通常存在对实现接口的方法做出直接的、无接口的调用(在该情况中vinvoke)的选项。

在上面的示例中,接口(诸如根据Java的当前规则)采用擦除的Object参量。在值类型的背景下,允许类型参量(Comparable<T>中的T)绑定到值类型Bignum并且因此将箱方法一直桥接到方法compareTo。

在一些实施例中,每个值类型实现自组织(ad hoc)接口或抽象超类型,诸如下面的示例:

3.11附加的示例性选项

子类型化。在一些实施例中,虚拟机104创建用于值类型的新的根类型以支持用于解析的抽象值超类型。

擦除vs.具体化。在实施例中,箱仅对箱操作,而值类型仅对值类型操作。某些桥方法由虚拟机104(静态地或者动态地)自动生成。

按成分方法。在一些实施例中,按成分方法是默认的。例如,过度健谈的toString方法可能彻底违背秘密值成分的封装性。用于生成用于打印、比较、散列码等的按成分方法的机制和策略可以为了选择性加入(opt-in)vs.选择性退出(opt-out)的正确混合而被平衡。

增强的变量操作。在一些实施例中,比较和交换(CAS)和类似的操作作为直接暴露CAS操作的单独特征的部分而被解决。在一些实施例中,在许多情况下值类型与引用和原生一起合并到CAS和类似的操作中。

值/原生收敛(convergence)。在实施例中,原生被集成到值类型中(例如,__ByValue class int{...}),并且成为预定义的值类型。

按字段的副作用。在一些实施例中,为了简化,值字段是被定为最终的。然而,在其他实施例中,值字段可以不一定总是最终的。在一些实施例中,可以对值中的仅一个字段产生副作用,从而实现“通过值”的所有效率以及“通过引用”的所有可变性。

值上泛型。在一些实施例中,虚拟机104支持用于值类型的泛型(例如,List<Point>)。

数组值。聚合的空间形成2×2的矩阵{标识,无标识}×{同构,异构}。对象和数组填充“标识”行;值类型填充(无标识,异构)箱;剩余的箱可以被描述为数组值。在一些实施例中,虚拟机104提供对于数组值的支持。

3.12从类型到值类型的迁移

在诸如Java之类的一些操作环境中,如果设施在许多之前实现的类型写入时已经是可用的(例如,Java中的LocalDateTime、Integer等),假设在已经是值类型的时间处这些类型已经是可用的,则许多之前实现的类型可能应当已经是值类型。在一些实施例中,虚拟机104支持将以上提到的类型实现为值类型的能力同时维持API互操作性。

迁移兼容性具有两个重要的方面:二进制兼容性(当现有的二进制指代预先迁移的类型时,二进制兼容性发生)以及源兼容性(当针对迁移的类型重新编译文件时,源兼容性发生)。源兼容性一般要求当引用类型被迁移为值类型时,对该引用类型的每个操作具有兼容的含义;这不仅包括像构造和方法调用那样的值安全的惯用语法,而且包括诸如锁定之类的值不安全的操作,因为现有代码可能执行依赖标识的操作。

假如预先存在的类型“WasARef”被迁移为值类型,并且由迁移注释为:

__Migrated__ByValue class WasARef{...}

以下事实帮助兼容性,即如果类“X”从引用类型迁移为值类型,则类型描述符“L X;”在迁移后仍然是合法的类型,它指代现在的值类型的装箱形式。如上文所讨论的,用于值类型的装箱版本的类文件可以从值类型的类文件生成。或者这二者可以共享公共的类文件。因此,只要维持了命名约定,那么导致类型不安全的操作或使用的对值类型的先前使用可以替代地利用值类型的装箱版本。因此,为了提供用于迁移的类型的源兼容性,在一些实施例中虚拟机104通过提供从“L”桥接到“Q”形式的桥方法来至少为迁移的所有值类解决值不安全的操作,以使得重新编译的API继续与旧客户机工作。

3.13更新字段的值

在实施例中,虚拟机104被配置为针对当更新一个字段和一些其他字段是易变的时可能出现的问题而使用指令的替代设计:

例如,如果代码显示为:

kludge k;

k.baz=7;

那么按照语句与以下语句同义的理论

k=kludge(k.flag,7);

可以写入“flag”字段。在实施例中,虚拟机104提供对于具有可以独立写入的两个易变字段的“像值一样的”结构的支持。然而,为了弥补,源语言可以在语言级别实现更精心制定的语义。例如,源语言可以遵循诸如“当且仅当值类型是变量的值时,值类型的字段是变量”之类的规则。因此,如果以下语句是合法的,

c.im=7;

则以下语句是不合法的:

myComplexHashMap.get(key).im=7;

只要在更新变量的字段时保持变量中的复数值,那么编码员可以在使用用于复数的值类和非值类之间高效地来回移动。

同时,能够独立写入值的两个(可能易变的)字段中的每个字段的虚拟机104指令可以被提供。因此,在一些实施例中,以下技术:不向上面介绍的那些添加新的虚拟机104指令,却将作为vstore或vastore指令的部分的“desc”从

Q-descriptor

概括为

Q-descriptor(.field-name)*

结果,描述符可以不仅描述值类型,而且可以描述该值类型的任何字段或子字段。

按照该方法,编译

c.im=7;

的示例性方式是

bipush 7

vstore"Qcomplex;.im"3

而编译

c.im*=trouble();

的示例性方式是

vload 3//c

vgetfield"Qcomplex;.im"

invokewhatever"trouble"

mul

vstore"Qcomplex;.im"3

因此,如果vload和vaload被扩展(以及可能vreturn和vgetfield和vputfield被扩展)以采用这些扩展的描述符(也可能为了对称),则它变成:

vload"Qcomplex;.im"3

invokewhatever"trouble"

mul

vstore"Qcomplex;.im"3

如果p是具有被命名为“phase”的类型复数的字段的值类型“foo”的变量,则编译

p.phase.im*=trouble();

的示例性方式是

vload"Qfoo;.phase.im"4

invokewhatever"trouble"

mul

vstore"Qfoo;.phase.im"4

该方法在某些方面比vputfield/dup_X1跳跃更高效。用于将看上去像是链式字段访问的东西打包成单个描述符的正当理由是它毕竟只是个描述符;它解析为单个偏移并且在一些实施例中使用它的vload或vstore仅执行对存储器的一次读或写操作。

在一些实施例中,类型检查弥补了上面所描述的技术。例如,对于vload"Qfoo;.phase.im"4,局部变量槽4中的数量的类型必须是Qfoo;,但是加载到栈上的元素具有与子字段的类型.phase.im相等的类型,它可以是双精度型或整型,这取决于复数的实现。

4.0修改后的加载进程

在一些实施例中,虚拟机104修改上文在“加载、链接和初始化”中所描述的加载进程来弥补值类型的引入。本节描述所述技术可以被修改的若干示例性方式。然而,示例仅适用于特定实施例并且不是决定性的。

如上文所描述的,当类文件200被加载到虚拟机存储器布局300中时,存储器被分配给所加载的类的静态字段。在类的静态字段具有值类型的情况下,然后在一些实施例中,需要被分配的空间量位于用于值类型的类文件中而不是在用于当前类的类文件中。结果,虚拟机104基于为被加载的类的类文件中的字段指定的符号引用来定位值类的类文件,并且计算所需要的存储器的量。不过,如果用于值类的类文件已经被加载,则虚拟机104可以执行计算并且在与指示总尺寸的值类相关联的元数据中高速缓存结果。该元数据然后可以被虚拟机104读取,而不是多次执行计算。例如,需要由虚拟机104为该字段在共享区域301中分配的空间量将是值类型的成分的和。这具有如下警告,即如果值类型具有值类型的字段(假设实施例允许这样的字段),则所需要的存储器的计算被递归地执行,对总数进行合计。

类似地,如果所加载的类的实例在堆302上被创建,则用于保持值类型的实例字段的所需要的空间量可以以相同的方式计算。可替代地,该计算可以提前执行并且高速缓存为与值类型和/或对象类相关联的元数据,以防止虚拟机104必须在每次实例化对象类时执行计算。

5.0存储考虑

如上文所讨论的,值类型打算使用扁平化表示来存储并且通过值传递。因此,当在保持值类型的堆302上创建对象时,值类型的内容被存储在对象内,而不是被分配在堆302上的单独区域中的对值类型的引用。此外,当值类型被加载到栈帧400中时,是值类型的内容被复制到局部变量401和/或操作数栈402中,而不是引用。

在一些实施例中,局部变量401表示保持一个或多个“容器”的数据结构,诸如保持若干槽以存储用于局部变量401的各个值的数组。分配用于局部变量401的空间的一种技术是分配具有固定尺寸的容器。例如,虚拟机104可以分配数组,在该数组中每个槽被设定尺寸以适配原生或引用。因此,原生通过值被存储在槽中,而非原生被分配在堆302上并且在槽中存储对堆302的引用。在一些情况中,表示多个其他原生的原生(诸如双精度型)被看作占用多个槽。然而,值类型由用户定义并且因此可以具有可能不整齐地适配固定尺寸的容器的可变尺寸。

在一些实施例中,诸如当JIT编译器109接管并且编译正被执行的一个或多个方法的虚拟机104指令时,执行代码的全局分析。结果,虚拟机104可以确定执行特定的经编译的方法将需要的容器尺寸。然而,当使用解释器108时,虚拟机104在没有全局分析的益处的情况下一次一个指令地执行程序。因此,当栈帧400(例如,作为方法调用的结果)被创建时所分配的用于局部变量401的容器可能被设定了不足以处理特定值类型的尺寸。例如,假设局部变量401包括两个容器,各自分配了64位的存储器。如果给定方法的虚拟机104指令指定将诸如整型之类的较小的值存储到第一容器中,则存储器分配可能是低效的(分配多于需要的空间),但是整型可以被存储在容器中。然而,如果虚拟机104指令指定在容器中存储128位值类型,则容器对于容纳值类型而言将不够大。作为响应,虚拟机104会将容器重新分配为128位以便于存储值类型。因此,存在与过大地设定被用于存储局部变量401的容器的尺寸相关联的存储开销以及与过小地设定容器的尺寸并且执行频繁的重新分配相关联的处理开销。

在一些实施例中,操作数栈402还被实现为存储一个或多个容器的数据结构。例如,操作数栈402可以被实现为存储以先进先出(FIFO)顺序访问的一个或多个容器的栈或分段的栈。因此,在一些实施例中,将值类型存储到操作数栈中可以导致上面关于局部变量401所讨论的相同问题。

在一些实施例中,虚拟机104实现分配规则和存储优化,该分配规则和存储优化降低了与在容器中存储尺寸可变的值(诸如值类型)相关联的开销。

5.1存储过程流

图5示出了根据实施例的用于在容器中存储值类型的过程。如上文所讨论的,局部变量401和操作数栈402这二者都可以被实现为存储容器的数据结构。然而,图5中示出的过程流对于被用于存储容器的数据结构是通用的。此外,图5中框执行的顺序不是关键的。在其他实施例中,与图5的图示相比,框可以被重新排列、合并或者划分为分开的框。下面的解释假设图5的过程流由虚拟机104的解释器108执行。另外,下面的解释假设在图5的过程期间帧400是当前栈帧。

在框500处,解释器108初始化一个或多个容器。在一些实施例中,当响应于方法调用而创建新的栈帧400时执行框500。例如,该一个或多个容器可以表示用于存储局部变量401的数据结构和/或用于存储操作数栈402的数据结构。

在实施例中,解释器108将所述一个或多个容器初始化为具有相同的固定尺寸。例如,解释器108可以将容器初始化为存储基本原生和/或引用所需要的尺寸。作为另一示例,解释器108可以创建具有大于基本原生和/或引用的尺寸的固定尺寸的容器。

在一些实施例中,解释器108将所述一个或多个容器初始化为可变的尺寸。例如,在一些实施例中,当方法被调用时,新的栈帧400被创建,前一个帧的操作数栈上的参数出栈并且被放置在新栈帧400的局部变量401中。因此,至少对于被用于存储参数的容器而言,解释器109可以将这些容器初始化为适合于方法调用所期待的参数类型(诸如原生类型、引用类型或值类型)的尺寸。例如,解释器108可以通过检查方法调用的描述符来确定参数类型。

然而,在一些实施例中,栈帧在存储器中重叠以提升存储效率。例如,调用者的栈帧可以与被调用者的栈帧在存储器中重叠,使得调用者的操作数栈是与新栈帧的一个或多个局部变量相同的存储器位置。在这样的实施例中,当调用者将这些值推到前一帧的操作数栈上时,表示参数的局部变量401的容器被初始化。

在一些实施例中,解释器108可能仍然需要用于在方法的主体内声明的变量(例如,无参数的局部变量)的容器。在一些实施例中,解释器108预先分配若干容器以保持这种变量的值。然而,在其他实施例中,解释器108延迟初始分配直至接收到将值存储到局部变量中的指令。一旦接收到指令,则解释器108分配对于存储该值来说足够大的容器。例如,容器可以通过编号(1、2、3、4等)索引,如果接收到在尚未被分配的容器的索引号处存储值的指令,则解释器108分配对于保持该值来说足够大的容器。在一些实施例中,解释器108修改用于保持容器的数据结构的元数据和/或头部信息以将索引号映射到容器。

在框501处,解释器108接收在所分配的容器中存储值类型的指令。在实施例中,作为逐句通过被调用的方法的代码的结果,解释器108接收指令。在框501处可以被解释器108接收的指令示例包括vstore、vastore、vload、vaload和上文描述的用于值类型的其他指令。在一些实施例中,在框501处接收的指令表示“较高级别的”指令(诸如JVM字节码),解释器108的任务是将该“较高级别的”指令翻译成一个或多个“较低级别的”指令,诸如被调整为适应执行虚拟机104的计算机系统的处理器的机器码。

在框502处,解释器108向验证器106馈送指令以针对一批规则进行检查。在一些实施例中,规则被设计为创建关于解释器108的复制和/或重新分配努力的界限。在一些实施例中,当验证器106检测到规则被违背时,验证器抛出异常,该异常由特殊的异常代码来处理。

在实施例中,验证器106约束容器以在被分配为用于一个种类的值类型后仅保持该值类型。因此,如果容器被分配为保持特定尺寸的值类型,则验证器106阻止容器被用于可能具有不同尺寸要求的任何其他种类的值类型。结果,验证器106阻止解释器108付出努力重新分配用于多个值类型的容器。然而,在一些实施例中,解释器108允许容器被重新指派为其他值类型,假设该值类型有相同的尺寸(或者在一些实施例中,有较小的尺寸)。在一些实施例中,前面提到的约束被维持直至容器的类型被“重置”或者指派的值被移动到其他地方。例如,“重置”指令可以重置类型和内容,而“移动”指令将重置类型以及将内容重新指派到另一容器(诸如在操作数栈402的顶部上)。然而,对于不实现这种指令的实施例,一旦被设置,则对于方法调用的剩余部分,类型可以被永久指派到容器。

在框503处,解释器108确定值类型是否适配所分配的容器。在实施例中,如果值类型具有与所分配的容器相同或者比分配的容器更小的尺寸,则解释器108前进到框504。否则,如果值类型比分配的容器的尺寸更大,则解释器前进到框505。

在框504处,解释器108将值类型的内容存储到容器中。

在框505处,解释器108确定值类型要通过引用还是通过值来存储。在一些实施例中,解释器108考虑各种因素来确定值类型要通过引用还是通过值来存储在容器中。例如,因素可以包括容器的性质、值类型的成分、较低级别的指令如何利用值类型、另一容器是否已经存储值类型等等。在一些实施例中,如果值类型大于阈值尺寸并且被确定为不可变,则解释器108选择通过引用来存储值类型。因此,对于大的值类型,解释器108可以选择存储对值类型的引用而不是复制值类型的内容。结果,通过阻止重复复制尺寸过大的值类型可以实现存储和处理高效率。然而,由于值类型被保证是不可变的,因此从用户的视角来看,不存在副作用的风险;原生值与值类型的处置之间不存在区别。如果解释器108确定要通过值存储值类型,则解释器108前进到框506。否则,如果解释器108确定要通过引用存储值类型,则解释器108前进到框507。

在框506处,解释器108调整容器的尺寸。在一些实施例中,解释器108通过对容器解除分配并且然后将容器重新分配为足以存储值类型的尺寸来调整容器的尺寸。然而,在其他实施例中,取决于被用于存储容器的数据结构,解释器108可以被配置为在不执行解除分配的情况下分配更多的存储器以增加容器的尺寸。

在框507处,解释器108在堆302上为值类型分配空间。在实施例中,解释器108查找值类型的尺寸并且在堆302上分配对于存储值类型来说足够大的空间。然后解释器108在分配的空间处在堆302上存储值类型。

在框508处,解释器108在容器中存储对值类型的引用。在实施例中,在框507处解释器108存储对在堆302上为值类型分配的空间的引用。

在一些实施例中,在框505处的确定中,因素是另一容器是否已经存储用于值类型的值的拷贝。在这种情况中,如果解释器108确定将值类型存储为引用,而不是在堆302上存储值类型并且在容器中存储对堆302的引用,则解释器108在容器中存储对存储值类型的其他容器的引用。例如,并非将来自局部变量的值类型复制到操作数栈402,解释器108可以替代地复制对存储用于该局部变量的值类型的容器的引用。作为另一示例,并非将来自局部变量的值复制到另一局部变量,解释器108可以替代地复制对存储第二个局部变量的值的容器的引用。在一些实施例中,仅当容器中的值不可变时才执行前面提到的优化,以防止副作用。

6.0原子操作

在一些实施例中,虚拟机104被配置为允许值类型或值类型的选定字段被原子地访问。因此,向虚拟机104中的每个运行的线程提供值类型(或选定字段)的一致视图。例如,值类型可以是具有由用户定义的多个成分的复合。取决于虚拟机104运行在其上的硬件,处理器的指令集可能不具有支持一次更新整个值类型的指令。作为示例,当构造函数为一个线程构造值类型时,另一个线程可能看到该值类型的不一致的视图,其中仅仅值类型的一部分已经更新。许多类型的硬件提供了用于执行原子操作的机制,但是这些机制具有不同程度的开销。此外,在一些情况中,硬件机制可能是不够的;这使得经常发生的由软件实现的锁定方案具有非常高的开销。因此,在一些实施例中,语言允许值类型被加标志以用于在诸如整个值类型、值类型中的选定字段或者访问值类型的独个指令之类的不同粒度级别的原子访问。

在一些实施例中,仅当值类型被存储在堆302上时遇到竞争状况和原子性的问题。这是因为在一些实施例中堆302存储在线程之间共享的数据,但是线程区域307(包括虚拟机栈)存储仅能被独个线程访问的数据。因此,(通过值)存储在栈帧400中的值类型不经历竞争状况,因为仅一个线程将访问这些值类型。然而,当值类型例如作为封装对象的字段被存储在堆302上时,多个线程可以潜在地读取该值,从而导致竞争状况。

图6示出了根据实施例的用于访问值类型的过程流。为了示出清楚的示例,将从虚拟机104的解释器108的视角来说明图6的过程流。

在框600处,解释器108接收访问存储在容器中的值类型的字段的指令。在一些实施例中,解释器108接收从值类型的字段加载值或将值存储到值类型的字段的指令。例如,上文描述的vgetfield和vputfield指令是在框600处可以接收的指令的示例。

在框601处,解释器108确定字段是否要被原子地对待。如果是,则解释器108前进到框602。否则,解释器108前进到框602。在实施例中,响应于检测到以下情况中之一,解释器108确定字段是原子的:值类型已经被标记为始终是原子的,值类型的字段已经被单独地标记为用于原子访问,或者指令被标记为是原子指令。例如,在源代码文件101中,值类型的声明、值类型的字段或使用值类型的指令包括诸如“atomic(原子的)”或“always atomic(始终是原子的)”之类的关键字。当编译器102编译源代码文件101时,利用指示这些成分要被原子地对待的标志或元数据来标记类文件103。然后解释器108可以读取这些类文件103(或者加载到虚拟机存储器布局300中的类文件103的表示)以确定字段是否要被原子地对待。

在框602处,解释器108在没有原子限制的情况下执行访问。例如,解释器108可以通过将访问转换为不提供任何原子保证的较低级别的指令而在没有原子限制的情况下执行访问。

在框603处,解释器108确定字段是否是不可变的。在一些实施例中,字段或值类型作为整体与源代码文件101中指示字段或值类型被冻结并且不能改变的关键字相关联。类似于上文描述的“atomic”关键字,这可以作为标志或元数据被反映在类文件103和/或虚拟机存储器布局300中以供解释器108在框603期间读取。如果字段是不可变的,则解释器108前进到框602。如果字段是可变的,则解释器108前进到框604。

在框604处,解释器108确定字段是否被频繁访问。在一些实施例中,解释器108跟踪在每次访问期间递增的与值类型的字段或值类型本身相关联的元数据。如果元数据指示访问的数量超过了特定阈值,则解释器108确定该字段被频繁访问。然而,在其他实施例中,还考虑时间分量,诸如追踪在时间的某个表示(真实时间、计算机时间、所执行的指令的数量等等)内访问的数量。如果解释器108确定字段被频繁访问,则解释器108前进到框605。如果解释器108确定字段不被频繁访问,则解释器108前进到框606。

在框605处,解释器108在分开的容器中存储字段。在一些实施例中,解释器108将字段从扁平化的形式改变为引用表示。与扁平化的值相比,对于原子地访问来说引用通常更快。例如,对于放置/存储指令,字段的新值可以在堆302上创建并且可以通过更新对新存储器位置的引用来原子地更新字段。此外,对于原子的加载,只要字段经由指针更新,则被加载的值要么表示整个新值要么表示整个旧值,而不可能保留部分更新的值。因此,可以避免诸如锁定之类的需要显著开销的原子技术。

在框606处,解释器108原子地执行访问。在实施例中,解释器108在框606处从多个技术中选择对于当前环境而言最高效的特定技术。例如,执行虚拟机104的硬件可以支持可被用于原子地执行访问的事务或者其他基于硬件的原子指令。然而,在一些情况中,硬件可能不支持足以执行访问的原子指令,因此在这种情况下解释器108回退到基于软件的原子技术,诸如执行锁定。在实施例中,只要可能,相比于基于软件的技术,解释器108优选基于硬件的原子指令。然而,在其他实施例中,解释器108能够访问指示技术的排名的元数据。例如,当程序运行时,解释器108可以尝试各种类型的原子访问技术并且基于速度将这些技术排名。

7.0硬件概述

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

例如,图7是示出在其上可以实现本发明的实施例的计算机系统700的框图。计算机系统700包括用于传送信息的总线702或其他通信机构,以及用于处理信息的、与总线702耦接的硬件处理器704。硬件处理器704可以是例如通用微处理器。

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

计算机系统700还包括存储用于处理器704的静态信息和指令的、耦接到总线702的只读存储器(ROM)708或其他静态存储设备。诸如磁盘、光盘或固态驱动器之类的存储设备710被提供并且被耦接到总线702以用于存储信息和指令。

计算机系统700可以经由总线702耦接到诸如发光二极管(LED)显示器之类的显示器712以用于向计算机用户显示信息。包括字母数字键和其他键的输入设备714被耦接到总线702以用于向处理器704传送信息和命令选择。另一种类型的用户输入设备是用于向处理器704传送方向信息和命令选择以及用于控制显示器712上的光标移动的光标控制器716,诸如鼠标、跟踪球或光标方向键。该输入设备典型地具有允许设备在平面上指定位置的在第一轴(例如,x)和第二轴(例如,y)这两个轴上的两个自由度。

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

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

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

各种形式的介质可以涉及将一个或多个指令的一个或多个序列承载到处理器704以供执行。例如,指令可以初始地被承载在远程计算机的磁盘或固态驱动器上。远程计算机可以将指令加载到它的动态存储器中并且使用调制解调器通过电话线发送指令。计算机系统700的本地调制解调器可以接收电话线上的数据并且使用红外传输器将数据转换成红外信号。红外检测器可以接收红外信号中承载的数据,并且合适的电路可以将数据放置在总线702上。总线702将数据承载到主存储器706,处理器704从主存储器706检索指令并且执行指令。由主存储器706接收的指令可以选择性地在被处理器704执行之前或之后存储在存储设备710上。

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

网络链路720典型地通过一个或多个网络提供到其他数据设备的数据通信。例如,网络链路720可以通过本地网络722提供到主机计算机724或到由网络服务提供商(ISP)726运营的数据装置的连接。ISP 726又通过现在通常被称为“互联网”728的全球分组数据通信网提供数据通信服务。本地网络722和因特网728都使用承载有数字数据流的电信号、电磁信号或光信号。通过各种网络的信号和在网络链路720上并通过通信接口718的信号是传输介质的示例形式,这些信号承载有到计算机系统700的数字数据以及来自计算机系统700的数字数据。

计算机系统700可以通过网络、网络链路720和通信接口718来发送消息和接收包括程序代码的数据。在互联网示例中,服务器730可以通过互联网728、ISP 726、本地网络722和通信接口718来传送用于应用程序的请求的代码。

所接收的代码可以如它被接收的那样由处理器704执行,和/或被存储在存储设备710或者其他非易失性存储中以便以后执行。

如本文所使用的,术语“一个”、“两个”、“某个”和“特定”被用作命名约定以将查询、计划、表示、步骤、对象、设备或其他项彼此区分开,以使得这些项可以在它们被介绍之后被引用。除非在此另外指定,这些术语的使用不暗示所引用的项的顺序、时序或任何其他特性。

8.0扩展和替代

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

9.0第一附加公开

本文所描述的主题的方面列举在以下标号的条款中:

1.一种方法,包括:接收指定将特定值类型的值指派到多个容器中的特定容器的一个或多个较高级别的指令,其中所述多个容器表示用于在代码块的执行期间维护一个或多个变量的数据结构,其中所述多个容器中的至少两个容器具有不同的尺寸;基于根据所述特定值类型将一个或多个指派规则应用于所述一个或多个较高级别的指令,生成将所述值指派到所述特定容器的一个或多个较低级别的指令;执行所述一个或多个较低级别的指令;其中所述方法由一个或多个计算设备执行。

2.如条款1所述的方法,其中所述一个或多个指派规则包括以下中的一个或多个:限制哪些值类型能够被指派到所述特定容器的约束,在向所述特定容器中加载所述值的拆箱表示或对保持所述值的第二容器的引用之间进行选择的规则。

3.如条款2所述的方法,其中在向特定容器中加载所述值的所述拆箱表示或对保持所述值的第二容器的引用之间进行选择的规则基于以下中的一项或多项:所述值是否是不可变的、所述值是否能够适配在所述特定容器内、所述值是否超出了特定的尺寸阈值、所述多个容器中的另一个容器是否已经存储了所述值、所述值的成分的性质、或者所述一个或多个较低级别的指令将如何利用所述值。

4.如条款2-3中的任一项所述的方法,其中所述多个容器被存储在为多个线程中的特定线程预留的存储器位置中,并且所述第二容器在由所述多个线程共享的存储器位置中位于所述多个容器的外部。

5.如条款2-4中的任一项所述的方法,其中所述约束基于以下中的一项或多项:(1)确定所述特定容器已经被分配为适配一个或多个值类型,并且将对所述特定容器的指派限制为所述一个或多个值类型的值,(2)将对所述特定容器的指派限制为与所述特定容器共享尺寸的值或具有比所述特定容器小的尺寸的值,(3)将对所述特定容器的指派限制为一个或多个特定值类型直至接收到重置所述特定容器的指令。

6.如条款2-5中的任一项所述的方法,还包括:响应于确定要在所述特定容器中加载所述值的所述拆箱表示以及确定所述特定容器不能适配所述值,调整所述特定容器的尺寸以适配所述值。

7.如条款1-6中的任一项所述的方法,还包括:接收指定改变第二特定容器中存储的第二值的成分的一个或多个第二较高级别的指令,其中所述第二值具有第二特定值类型;至少部分地基于与所述第二特定值类型相关联的元数据来生成识别所述第二特定容器的与所述成分部分对应的部分的单个较低级别的指令;执行所述单个较低级别的指令。

8.如条款2-7中的任一项所述的方法,其中识别所述第二特定容器的所述部分包括以下中的一项或多项:使用对应于所述第二特定值类型的值类型定义的字段定义来定位所述部分,使用所述值类型定义的访问控制信息来实行对所述部分的访问限制,或者基于所述值类型定义来执行类型检查。

9.如条款1-8中的任一项所述的方法,还包括:接收指定访问第二特定容器中存储的第二值的成分的一个或多个第二较高级别的指令,其中所述第二值具有第二特定值类型;基于与所述第二特定值类型相关联的元数据来确定对所述成分的访问是否应当是原子的;响应于确定对所述成分的访问应当是原子的,确定所述成分是否存储在不可变容器中;响应于确定所述成分存储在不可变容器中,生成不提供原子保证的一个或多个第二较低级别的指令并且执行所述一个或多个第二较低级别的指令;响应于确定所述成分没有存储在不可变容器中,选择能够提供对所述成分的原子访问的一个或多个机制中的特定机制,基于所述特定机制生成提供原子保证的一个或多个第三较低级别的指令,以及执行所述一个或多个第三较低级别的指令。

10.如条款2-9中的任一项所述的方法,其中所述第二值存储在第一容器中,并且所述方法还包括:响应于确定所述成分没有存储在不可变容器中,确定所述成分的访问频率是否超过访问阈值;响应于确定所述成分的所述访问频率超过所述访问阈值,将所述成分存储在分开的第二容器中并且针对所述成分在所述第一容器中存储对所述第二容器的引用。

11.如条款2-10中的任一项所述的方法,其中能够提供对所述成分的原子访问的所述一个或多个机制基于由所述一个或多个计算设备的一个或多个处理器支持的一组指令。

12.存储有指令的一个或多个计算机可读介质,所述指令当被一个或多个计算设备执行时使得执行如条款1-11中的任一项所述的步骤。

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

10.0第二附加公开

根据实施例,诸如当将Java或其他源代码编译为字节码或在任何其他合适的编程环境中时,根据较高级别的指令生成较低级别的指令。较低级别的指令包括访问诸如运行时数据区域的局部变量、栈元素、寄存器、虚拟寄存器和/或其他命名的虚拟变量的之类的容器内的值的指令。容器具有变化的尺寸以便于例如能够直接地而不是通过对堆或其他存储器区域中的容器的引用来存储具有可变尺寸的值。

较高级别的指令包括某个值类型的值。值可以是例如由若干成分组成的非原生的复合值,诸如Point或Complex。该某个值类型可以例如由类定义,其中该类的值是实例。响应于较高级别的指令,较低级别的指令可以被生成以将值或者(在一些情况中)对值的引用加载到尺寸可变化的容器之一。这种较低级别的指令的一个示例是本文所描述的vaload指令。或者,较低级别的指令可以存储、修改、移动和/或重置容器。在任何一种情况中,基于特定值类型以及基于与特定值类型相关的一个或多个指派规则来生成较低级别的指令。这种指派规则可以被配置为例如限制与维护支持可变尺寸的容器而非具有预定义的固定尺寸的容器相关联的成本,和/或限制维护在容器之间复制的值的多个拷贝的成本。

在实施例中,指派规则可以包括例如一个或多个约束,在该一个或多个约束下,在方法的调用期间,不同的值类型可以被加载到容器中。在实施例中,这种约束的一个示例可以指定在方法的过程期间被加载到容器中的值可以具有最多一种数据类型或者数据类型的有限集合。因此,给定的容器可以仅保持容器最初被分配的值类型的值以及选择地保持原生或引用的值。选择性地,该约束可以持续至容器的类型被重置或者被指派的值被移动到其他地方为止。例如,“重置”指令将重置类型以及内容,而“移动”指令将重置类型以及将内容重新指派到另一个容器,诸如栈的顶部。移动操作将线性类型或所有权(ownership)类型的概念应用于解释器效率。

在实施例中,另一示例性约束可以是被指派到容器的值必须全部具有如下值类型:该值类型被保证具有不大于指定尺寸的尺寸或者可替代地被保证具有与指定尺寸完全相同的尺寸。在实施例中,另一示例性约束可以是在方法的过程期间被记载到给定容器的值可以具有变化的数据类型,但是如果给定容器被用于存储值类型,则它不能被用于存储不同的类型(可选择地,直至容器被重置)。

在实施例中,指派规则的另一示例是在将值的拆箱表示或者对保持值的外部容器的引用加载到容器中之间最优地选择的规则。这种规则可以考虑诸如外部容器或值的成分的性质或较低级别的指令内如何利用值之类的因素。例如,规则可以基于值在外部容器内是否有效地是不可变的。在实施例中,指派规则的另一示例是关于当第二容器已经包含第一容器的值的拷贝时将值加载到第一容器中的约束。任何其他合适的指派规则也可以同时或者替代地被利用。

根据实施例,在一组指令的验证(诸如在解释器解释字节码之前由字节码验证器进行的验证)期间执行某些步骤。例如,当验证根据上文所描述的技术生成的较低级别的指令时,步骤可以被执行。所述步骤包括识别将值加载到具有变化的尺寸的一组容器中的容器的指令。步骤还包括基于要被加载在容器中的值的值类型来验证指令遵从一个或多个约束。在实施例中,约束可以包括上文所描述的指派规则和/或约束中的一个或多个。

根据实施例,当从较高级别的指令生成较低级别的指令时,改变某个值类型的值内的字段的一个或多个较高级别的指令可被识别。值可以是例如潜在地具有多个不同成分字段的值类型的实例。本文描述了这种值类型的示例,诸如Point或Complex。可以确定的是,在较低级别的指令的背景下,用于值的至少一个成分字段的数据将被集体存储在单个容器内。例如,字段可以被存储为其对应的子值的拆箱表示,而不是对存储对应的子值的其他容器的引用。响应于该确定,单个较低级别的指令被生成。这种较低级别的指令的示例是本文所描述的vput指令。

所生成的单个较低级别的指令被配置为命令解释器识别容器的对应于字段的部分,诸如一个或多个位。单个较低级别的指令还被配置为命令解释器根据一个或多个较高级别的指令改变该部分。识别和/或改变可以至少部分基于与值的值类型相关联的元数据。例如,指令可以使得解释器使用值类型的字段定义来定位该部分,和/或实行用于对应的值类型定义的访问控制信息和类型检查等。在实施例中,单个较低级别的指令命令解释器在不使用指针来定位字段和/或不必发出打开值进入到其成分字段中的另一命令的情况下来执行这些操作。

在实施例中,通过重新排序、合并或分开与彼此相关并且与可以读取或写入存储在容器中的值或该值的至少一些成分字段的任何其他指令相关的两个或更多个这种较低级别的指令的效果,选择地生成较低级别的指令。这种效果的重新排序、合并或分开可以经受保护存储在容器中的值的每个成分字段以及作为整体存储在容器中的值的一致性的规则。因此,技术可以包括使用分层的数据结构的嵌套存储器模型,当对象拆分成值时,该数据结构可以拆分成更小的值并且最终一直向下拆分成成分原生和引用。

根据实施例,方法包括:当解释诸如Java字节码之类的指令时,识别用于插入较小的第一值作为较大的第二值的字段的指令。该指令可以是例如单个较低级别的指令,诸如上面生成的指令。第二值可以是例如具有被集体存储在容器中的多个不同成分字段的经定义的值类型的值。第一值可以是例如对应于字段的子值。在实施例中,确定第二值具有某个值类型。基于与某个值类型相关联的元数据,对应于字段的第二值的一部分被定位,并且第一值被加载到该部分中。这可以包括例如基于与某个值类型相关联的值类型定义数据来实行访问控制信息和类型检查,和/或使用值类型定义数据来定位相关部分。在实施例中,通过重新排序、合并或分开两个或更多个这种较低级别的指令的效果来选择性生成较低级别的指令,诸如上文所描述的。

根据实施例,代码可以访问意欲是原子的值的字段。这种代码可以包括例如诸如getfield或putfield之类的指令。由于例如其值是实例的值类型的定义中的声明、或者当值被实例化时的声明、和/或解释器或编译器的默认配置,值可以是原子的。响应于该代码,编译器或解释器可以做出与决定在访问字段时是否实现锁定策略和/或实现哪个锁定策略相关的一个或多个确定。

在实施例中,一种这样的确定是字段是否为不可变的。例如,字段可以被局限于正在执行的线程,字段可以是“冻结”的字段,和/或字段可以被声明为是最终的。如果字段不可变,则可以生成在不锁定的情况下访问字段的指令。在实施例中,另一种这样的确定是执行硬件是否具有事务支持。如果是,则为了访问字段生成对硬件的指令的事务版本,以便于确保访问被以事务方式处理。

在实施例中,另一种这样的确定是字段是否是频繁改变的和/或值是否是复杂的或频繁访问的。如果是,则生成指令来将字段存储在诸如通过指针在值内引用的容器之类的分开的容器内,而不是直接将字段存储在存储值的容器内。指令经由分开的容器访问字段。在实施例中,另一种这样的确定是值是否为已经被划分为子值的大值。值可以实际地被划分并且存储在分开的容器中,或者出于锁定的目的值可以在逻辑上被划分。如果值是已经被划分为子值的大值,则通过锁定字段在其中驻留的子值来生成访问字段的指令。在实施例中,另一种这样的确定是值本身是否为另一个值的字段。如果是,则在此基础上选择锁定策略。任何其他合适的确定可以被同时或者替代地做出。在实施例中,做出一系列的确定。从资源的视角被估计为最便宜的确定首先被执行,而其他确定仅在需要的时候执行。

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

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