OpenJDK-11的新特征
JDK 11是Java SE 11平台版11的开源参考实现JSR 384在Java Community Process中指定。
JDK 112018年9月25日达到一般可用性.GPL生产就绪二进制文件可以从Oracle获得;其他供应商的二进制文件很快就会出现。
通过这个版本的功能和时间表JEP提出和跟踪和跟踪JEP 2.修改了0提案。使用JDK Release Process(JEP 3)生成发布。
- 181:基于嵌套的访问控制
- 309:动态类 - 文件常量
- 315:改进Aarch64内在函数
- 318:Epsilon:无操作垃圾收集器
- 320:移除Java EE和CORBA模块
- 321:HTTP客户端(标准)
- 323:本地变量Lambda参数的语法
- 324:与Curve25519和Curve448的密钥协议
- 327:Unicode 10
- 328:飞行记录器
- 329:ChaCha20和Poly1305加密算法
- 330:启动单文件源代码程序
- 331:低开销堆分析
- 332:传输层的安全性(TLS)1.3
- 333:ZGC:可扩展的低延迟垃圾收集器(实验)
- 335:弃用Nashorn JavaScript引擎
- 336:弃用Pack200工具和API
2018年6月28日 | Rampdown第一阶段(主线分叉) | |
2018年7月26日 | 减速阶段二 | |
2018年8月16日 | 候选人的初始发布 | |
2018年8月30日 | 最终候选人 | |
2018年9月25日 | 一般可用性 |
作者 | 约翰罗斯 |
所有者 | 大卫霍姆斯 |
类型 | 特征 |
范围 | SE |
状态 | 关闭/交付 |
发布 | 11 |
零件 | 热点/运行时 |
讨论 | valhalla dash dev在openjdk dot java dot net |
功夫 | 中号 |
持续时间 | 中号 |
评论人 | Karen Kinnear,Mikael Vidstedt,Vladimir Kozlov |
受认可 | Mikael Vidstedt |
创建 | 2013/03/19 20:00 |
更新 | 2018/10/26 21:17 |
问题 |
介绍嵌套,这是一种访问控制上下文,与Java编程语言中现有的嵌套类型概念一致。嵌套允许逻辑上属于同一代码实体但被编译为不同类文件的类,以访问彼此的私有成员,而无需编译器插入可访问性扩展桥接方法。
此JEP不关心大规模的访问控制,例如模块。
许多JVM语言支持单个源文件中的多个类(例如Java的嵌套类),或者将非类源工件转换为类文件。然而,从用户的角度来看,这些通常被认为都属于“同一类”,因此用户期望它们共享共同的访问控制机制。为了保持这些期望,编制者经常不得不扩大private
成员的访问权限package
通过添加访问桥:将私有成员的调用编译为目标类中编译器生成的包私有方法的调用,该方法又访问预期的私有成员。这些桥接器破坏了封装,略微增加了已部署应用程序的大小,并且可能会混淆用户和工具。形成嵌套的一组类文件的正式概念,其中嵌套配合共享公共访问控制机制,允许以更简单,更安全,更透明的方式直接实现期望的结果。
公共访问控制上下文的概念也出现在其他地方,例如主机类机制Unsafe.defineAnonymousClass()
,其中动态加载的类可以使用主机的访问控制上下文。一个正式的嵌套成员概念会使这个机制更加稳固(但实际上提供一个支持的替代方案defineAnonymousClass()
将是一个单独的努力。)
Java语言规范允许类和接口彼此嵌套。在顶级声明(JLS 7.6)的范围内,任何数量的类型都可以嵌套。这些嵌套类型具有相互无限制的访问权限(JLS 6.6.1),包括私有字段,方法和构造函数。我们可以描述一个顶级类型,以及嵌套在其中的所有类型,形成一个嵌套,并且嵌套的两个成员被描述为嵌套。
私有访问在包含顶级类型的整个声明中是完整的(无差别的,扁平的)。(人们可以将其视为定义一种“迷你包”的顶级类型,在其中授予额外访问权限,甚至超出提供给同一Java包的其他成员的权限。)
今天,JVM访问规则不允许在嵌套之间进行私有访问。为了提供允许的访问,Java源代码编译器必须引入一个间接级别。例如,私有成员的调用被编译为目标类中编译器生成的package-private,bridging方法的调用,该方法又调用预期的私有方法。仅在需要时生成这些访问桥以满足嵌套内请求的成员访问。
缺乏JVM支持嵌套内私有访问的另一个后果是,核心反射也拒绝访问。java.lang.reflect.Method.invoke
从一个嵌套到另一个嵌套的反射方法调用(使用)抛出IllegalAccessError
(除非禁用访问控制)。考虑到反射调用应该与源级调用相同,这是令人惊讶的。类似地,MethodHandle
API拒绝直接“查找”私有嵌套方法,但提供特殊支持Lookup.in
以允许表达源级别调用语义。
通过编写嵌套的概念和JVM中相关的访问规则,我们简化了Java源代码编译器的工作,加强了现有的访问检查,并从核心反射和MethodHandle
API中删除了令人惊讶的行为。我们还允许未来的增强功能,以利用“巢”概念。例如:
- 在通用专业化中,每个专用类型都可以创建为泛型类型的嵌套。
- 安全且受支持的
Unsafe.defineAnonymousClass()
API 替代品可以将新类创建为现有类的嵌套。 - “密封类”的概念可以通过仅允许作为同伴的子类来实现。
- 真正的私有嵌套类型可能会受到影响(目前私有嵌套类型是使用包访问定义的)。
可在此处找到当前提出的JVMS更改集。
现有的类文件格式定义了InnerClasses
和EnclosingMethod
属性(JVMS 4.7.6和4.7.7),以允许Java源代码编译器(例如javac
)来实现源级别嵌套关系。每个嵌套类型都编译为自己的类文件,不同的类文件由这些属性的值“链接”。虽然这些属性足以让JVM确定嵌套,但它们并不直接适用于访问控制,并且本质上与单个Java语言概念相关联。
为了允许更广泛,更一般的巢式概念超越简单的Java语言嵌套类型,并且为了有效的访问控制检查,建议修改类文件格式以定义两个新属性。一个嵌套成员(通常是顶级类)被指定为嵌套主机,并包含一个attribute(NestMembers
)来标识其他静态已知的嵌套成员。每个其他嵌套成员都有一个attribute(NestHost
)来标识其嵌套主机。
我们将通过向JVMS 5.4.4添加类似以下子句的内容来调整JVM的访问规则:
当且仅当满足以下任一条件时,字或方法R才可被类或接口D访问 :
- ...
- R是私有的,在不同的类或接口C中声明,而C和D是同伴。
对于类型C和D是嵌套,它们必须具有相同的嵌套主机。如果类型C在其属性中列出D,则它声称是由D托管的嵌套的成员。如果D还在其属性中列出C,则验证成员资格。D隐含地是它承载的嵌套的成员。NestHost
NestMembers
具有no NestHost
或NestMembers
attribute的类隐式地形成一个嵌套,其自身作为嵌套主机,以及唯一的嵌套成员。
松散的访问规则将影响以下活动期间的访问检查:
- 解决领域和方法(JVMS 5.4.3.2等)
- 解决方法句柄常量(JVMS 5.4.3.5)
- 解析呼叫站点说明符(JVMS 5.4.3.6)
- 通过实例检查Java语言访问
java.lang.reflect.AccessibleObject
- 在查询期间检查访问权限
java.lang.invoke.MethodHandles.Lookup
通过更改访问规则,并对字节代码规则进行适当调整,我们可以允许生成调用字节码的简化规则:
invokespecial
对于私人嵌套构造函数,invokevirtual
对于私有非接口,nestmate实例方法,invokeinterface
用于私有接口,nestmate实例方法; 和invokestatic
对于私人的巢穴,静态方法
这放宽了必须使用invokespecial
(JVMS 6.5)调用私有接口方法的现有约束,并且更通常允许invokevirtual
用于私有方法调用,而不是添加到周围的复杂使用规则invokespecial
。可以对MethodHandle
调用的语义进行类似的更改(这反映了调用字节代码约束)。
必须先验证Nest成员身份,然后才能继续进行依赖于nestmate访问的访问检查。这可能发生在成员访问的时间,或者早在类的验证时间或中间的某个地方,例如方法的JIT编译。如果尚未加载嵌套成员资格验证,则需要加载嵌套成员资格验证。为避免可能不必要的类加载,应尽可能晚地(即访问检查时)执行嵌套成员资格验证。如果依赖于嵌套访问,则通过要求存在嵌套主机类来减轻所引入的不兼容性的影响。
为了保持嵌套的完整性,建议至少在开始时,禁止使用任何形式的类转换或类重新定义来修改嵌套类文件属性。
在我们引入新的类文件属性时,习惯上提供一种使用核心反射检查/查询这些属性的方法。这是目前设想三种方法java.lang.Class
:getNestHost
,getNestMembers
,和isNestmateOf
。
建议的更改虽然在概念上很简单,但会影响所有明确或隐含地涉及访问控制或与方法调用模式相关的规范和API。这些包括:
- Java虚拟机规范(建议的更改)
- Classfile属性更改
- 访问控制规则更改
- 调用字节码规则更改
- 核心反思
Method
调用规则Field
访问规则
MethodHandle
查找规则- 类转换/重新定义:JVM TI和
java.lang.instrument
API,JDWP和JDI(com.sun.jdi.VirtualMachine
)- 禁止修改与nest相关的classfile属性
- Pack200规范
- 识别新的类文件属性
建议的更改简化了将Java源代码构造映射到类文件的规则,因此对选择使用它们的Java源代码编译器有很多影响:
- 正确生成与nest相关的classfile属性
- 完善以前需要的访问桥接方法并为私人嵌套成员生成直接成员访问指令
- 发出正确/适当的调用字节码
- 能够将其他合成方法更改为私有而不是包私有(甚至消除它们,或者用共享但私有的方法句柄常量替换它们)
该javac
编译器将生成更新最新版本的类文件时要充分利用nestmates。(现在使用访问桥等生成旧版本)
对类文件进行操作或生成或处理字节码的任何工具都可能受到这些更改的影响。这些工具至少必须容忍新类文件属性的存在,并允许更改字节码规则。例如:
- 在
javap
类文件检测工具, - Pack200实现,和
- 在ASM字节码操作框架,其也被在JDK内部使用。
访问检查的额外复杂性是必须要检查的。特别是围绕nest主机类的解析和可能出现的错误的问题。我们已经遇到并解决了编译器线程需要加载嵌套主机类的问题 - 这在编译器线程中是不允许的。我们需要确保实施能够处理这些条件,并确保规范不受其引入的影响。
我们可以根据需要继续在Java编译器中生成桥接方法。这是一个难以预测的过程。例如,Project Lambda在存在内部类时难以解析方法句柄常量,从而导致一种新类型的桥接方法。由于编译器生成的桥接方法很棘手且难以预测,因此它们也很麻烦,很难通过各种工具进行分析,包括反编译器和调试器。
最初的提案考虑使用现有InnerClasses
和 EnclosingMethod
属性来建立nestmate-ship。但是引入特定的nestmate相关属性都会使得巢友比仅与语言级嵌套类型相关更通用,并且允许更有效的实现。此外,如果我们选择了急切的嵌套成员资格验证检查,它将改变现有属性的语义,这可能是兼容性问题。虽然javac
编译器可能会保持“内部类”和“嵌套成员”属性对齐,但这是编译器的选择,JVM将完全独立地处理它们。
在讨论当前方法之前,讨论了如何通过classfile属性最好地表达嵌套关系。对于非集中式方法,一个建议是每个嵌套由UUID识别。该讨论的结论如下:
这样的提议有两个部分:
基于UUID的嵌套新命名约定。这是JVM中的一个新概念,需要新的基础架构来管理(生成,转码,验证,反映,调试)。这意味着新的错误和新的攻击面。在没有决定性好处的情况下,最好重用现有的名称空间,并且(特别是)JVM的类型名称字典。
单向链接。UUID是一个没有内容的纯身份,不包含其嵌套成员的列表。嵌套成员指向嵌套(通过UUID)。只需提及适当的UUID,任何类都可以将自身注入嵌套(在同一个包中)。单向链接意味着无法枚举嵌套。这使一些优化变得复杂(基于密封类型)。巢的安全性和密封性降低到包装的安全性和密封性。PRIVATE只是默认范围访问控制的别名。
对不起,但与目前的提案相比,这对我来说都没有吸引力。
我们需要一组广泛的JVM测试来验证新的访问规则并调整字节代码语义以支持同伴。
类似地,我们还需要针对核心反射,方法句柄,var-handle和外部访问API(如JDWP,JVM TI和JNI)的其他测试。
由于此处未提出语言更改,因此不需要新的语言合规性测试。
在javac
修改编译器以利用nestmate访问之后,对语言符合性测试自然会出现对巢友的充分功能测试。
新规则必须与新的类文件版本号相关联,因为我们需要确保Java源编译器仅在定位了解它们的JVM时生成依赖于新属性和规则的字节码。这样做的必然结果是,如果JVM出现在具有合适版本号的类文件中,它将仅识别新属性并对其进行操作。新的类文件版本给更广泛的Java生态系统中的工具带来了负担,但我们不希望Nestmates成为唯一的技术,在目标JDK版本中,它将依赖于新的类文件版本号。
放松访问几乎没有一致性风险。今天编译和运行的所有Java语言访问都将使用nestmate更改进行编译和运行,而不会更改源代码。禁用访问检查(via setAccessible
)以反复访问nestmates的代码将继续使用nestmate更改正确运行 - 但可以更新为不禁用访问检查。
在某些情况下,检查禁止行为的合规性测试可能会失败。例如:
- 对私有嵌套方法的直接反射访问当前失败(除非禁用访问检查),但在应用这些更改时“意外”成功。
- 现在,
invokeinterface
无法用于专用接口方法的测试将失败,因为可以使用这些更改。
用户兼容性几乎没有风险,因为提案放宽了访问权限。但是,如果用户已“发现”并利用了访问桥接方法,则在删除网桥后,他们将无法执行此操作。这种风险非常小,因为桥接方法首先没有稳定的名称。
系统完整性几乎没有或没有风险,因为提议的规则仅在单个运行时包内授予新访问权限。通过消除对桥接方法的需求,将系统地降低不同顶级类之间的潜在访问。
嵌套成员资格验证需要存在nest-host类,即使该类本身未使用(除了作为嵌套成员的容器)。这可能会在以下三个方面产生影响:
-
类加载的顺序可能会更改,因为访问检查可能需要嵌套主机,而不是直接使用嵌套主机时。这不是一个问题,因为类只加载,而不是初始化,并且对类加载顺序的依赖性(与类初始化顺序不同)非常罕见。
-
这可能会影响从分布式表单中删除未使用的类的测试/应用程序,并且未使用嵌套主机。通过将嵌套成员资格验证留到需要进行嵌套访问检查的时间,我们的目标是最小化此问题的影响,但在某些情况下,最终用户将不得不改变他们分发代码的方式。我们认为这是一个非常小的风险,因为将顶级类纯粹用作无状态容器并不常见,它只包含静态嵌套类型,其中嵌套类型将依赖于彼此的私有访问。
-
嵌套主机的解析还将类加载(以及相关异常的可能性)引入JVM的访问检查逻辑。这主要是JVM实现者关注的问题。必须注意确保所有可能导致VM访问检查的路径或者排除加载嵌套主机的可能性,否则可以应对它。同样可能发生的潜在异常。从用户角度来看,由于Java代码很少对类加载的时间和位置进行假设,因此风险非常小,只有存在格式错误的类文件才会出现异常。
作者 | Brian Goetz |
所有者 | 路易斯福尔坦 |
类型 | 特征 |
范围 | SE |
状态 | 关闭/交付 |
发布 | 11 |
零件 | 热点/运行时 |
讨论 | 在openjdk dot java dot net的琥珀色破折号开发 |
功夫 | 中号 |
持续时间 | 中号 |
涉及到 | JEP 303:LDC和INVOKEDYNAMIC指令的内在函数 |
评论人 | 马克莱因霍尔德 |
受认可 | 马克莱因霍尔德 |
创建 | 2017/03/20 20:26 |
更新 | 2018/09/10 18:57 |
问题 | 8177279 |
扩展Java类文件格式以支持新的常量池形式 CONSTANT_Dynamic
。CONSTANT_Dynamic
将委托创建加载到引导方法,就像链接invokedynamic
调用站点将链接委托给引导方法一样。
我们寻求降低创建新形式的可实现类文件常量的成本和中断,这反过来又为语言设计者和编译器实现者提供了更广泛的表达性和性能选择。我们通过创建一个新的常量池形式来实现这一点,该形式可以使用用户提供的行为进行参数化,采用带有静态参数的bootstrap方法。
我们还将调整JVM和引导程序方法之间的链接时握手,以便使所使用的引导程序APIinvokedynamic
适用于动态常量。
根据invokedynamic
我们的经验,我们将调整两者invokedynamic
和动态常量的自举握手,放松对参数列表处理到引导方法的某些限制。
这项工作需要JDK库的一些原型设计支持几种常量类型的代表性样本,特别是变量句柄(JEP 193)。为了支持这种原型设计,这项工作将与其他关于常量表达式的基本语言支持的工作相协调(JEP 303)。
此JEP旨在支持常量池中的任意常量。虽然有关于引导方法的其他用途的建议,例如方法配方,但是这个JEP专注于一种用途。
此JEP的成功不依赖于Java语言或Java编译器后端的支持,但如果编译器后端使用它,则更有可能成功。
尽管大的聚合常量是Java翻译策略中的一个弱点,但是在有更好的方法将它们封装成常量形式(例如冻结数组或原始专用列表)之前,这个JEP无法解决聚合问题。
作为一个最低要求,暴露常量池形式来描述基本类镜像(int.class
)null
,enum
常量和大多数形式都应该是实用VarHandle
的CONSTANT_Dynamic
。
动态常量必须可用于当前允许常规常量池常量的任何上下文中,例如CONSTANT_String
和CONSTANT_MethodType
。因此,它们必须是ldc
指令的有效操作数,并且必须被允许作为引导方法的静态参数。
bootstrap-method握手应该支持包含数千个组件参数的复杂常量,从而提升了251个常量参数的当前限制。作为一个伸展目标,还应该有一种方法让引导方法更准确地控制通过解析引导方法参数产生的链接错误。
在工作结束时,我们还应该有理由相信这种机制可以用于各种各样的库类型,例如派生方法句柄,小型不可变集合(列表,映射,集合),数字,正则表达式,字符串格式化程序或简单数据类。
应确定并记录后续工作。请参阅下面的“可能的扩展”。
Java虚拟机规范的4.4节描述了常量池的格式。添加新的常量池形式(例如Java 7中的支持MethodHandle
和MethodType
引入)是一项重大的工作,并且会在生态系统中发出涟漪,因为它会影响解析或解释类文件的所有代码。这为创建新的恒定池形式提供了很高的标准。
使用invokedynamic
,将常量池中存储复杂数据的值相乘,因为invokedynamic
引导程序的静态参数列表 是一系列常量。invokedynamic
协议的设计者 (例如LambdaMetafactory
Java 8中添加的)通常很难满足根据现有常量集编码行为的需要 - 这反过来又需要在引导程序本身中具有额外的容易出错的验证和提取逻辑。更丰富,更灵活,更高类型的常量消除了invokedynamic
协议开发的摩擦,这反过来又促进了复杂逻辑从运行时到链接时的移动,提高了程序性能并简化了编译器逻辑。
正如invokedynamic
调用站点的链接涉及从JVM到基于Java的链接逻辑的上行调用一样,我们可以将同样的技巧应用于常量池条目的解析。甲CONSTANT_Dynamic
恒定池条目编码以执行分辨率自举方法(A MethodHandle
),所述常数(的类型Class
),以及任何静态引导参数(常数的任意序列,在动态常数之间的常量池限制周期。)
我们添加了一个新的常量池形式,CONSTANT_Dynamic
(新的常量标记17),它的标记字节后面有两个组件:bootstrap方法的索引,格式与a中的索引相同 CONSTANT_InvokeDynamic
,a CONSTANT_NameAndType
,编码预期类型。
在行为上,CONSTANT_Dynamic
通过对以下参数执行其引导方法来解析常量:1。本地Lookup
对象,2。String
表示常量的名称组件,3。Class
表示期望的常量类型,以及4.任何剩余的引导参数。与此同时invokedynamic
,多个线程可以竞争解决,但将选择一个独特的赢家,并丢弃任何其他竞争的答案。而不是CallSite
像invokedynamic
指令所要求的那样返回一个对象,bootstrap方法将返回一个值,该值将立即转换为所需的类型。
与此同时invokedynamic
,除了类型之外,name组件是一个附加通道,用于将表达式信息传递给bootstrap方法。预期正如invokedynamic
指令查找名称组件的用途(例如,方法名称或某些特殊描述符)一样,动态常量也将找到名称的用途(例如,enum
常量的名称或符号常量的拼写) )。把CONSTANT_NameAndType
在两地使得一个更经常的设计。在效果上,CONSTANT_Methodref
和 CONSTANT_Fieldref
常数用来指的类名称的成员,而类似的CONSTANT_InvokeDynamic
和 CONSTANT_Dynamic
常数用于指命名实体与用户编程的引导程序。
具有两个invokedynamic
和 的常量的类型组件CONSTANT_Dynamic
确定调用站点的有效类型或常量(分别)。引导方法不会贡献或约束此类型信息,因此引导方法可能(通常是)弱类型,而字节码本身始终是强类型的。
为了放宽对引导说明符的长度限制,将调整定义引导方法调用的语言(具有完全向后兼容性),以允许变量arity(ACC_VARARGS
)引导方法将所有剩余的静态参数吸收到其尾随参数中,即使存在是2 ^ 16-1。(类文件格式已经允许这样,但是没有办法读取过长的引导参数列表。)为了保持一致性,如果目标方法具有可变的arity ,那么 invokeWithArguments
方法MethodHandle
也将以这种方式扩展。这样的引导方法调用可以在弱类型的方法来指定invokeWithArguments
和invoke
,就像今天它在来指定invoke
一个人。
控制引导链接错误已被证明是来自用户的错误和RFE的反复出现的来源,并且invokedynamic
随着引导方法变得更加复杂(因为它们必须具有动态常量),趋势可能会加速。如果我们能找到一种方法来提供对引导方法异常的更全面控制,并且可以简单地完成,我们将考虑将其作为此JEP的一部分提供。否则,它将列入未来的增强功能列表。
Java虚拟机规范草案CONSTANT_Dynamic
可以在JDK-8189199中找到,这是与此JEP的主要开发问题相关的CSR问题。
未来可能的扩展包括:
- 支持批量比例常量,例如数组或资源表
- 进一步调整bootstrap方法握手
- 自举方法的其他用途,可以与动态常数协同作用
- 将动态常量附加到
ConstantValue
静态字段的属性 - 展示Java语言中常量的延迟初始化
- 将新常量与特殊Java语言规则集成在一起用于常量表达式
有关设计选择的讨论可以在JDK-8161256中找到 ,它涉及许多相关的RFE。目前的JEP是从这个更大的特征列表中提炼出来的。
许多用途CONSTANT_Dynamic
可以用等效的invokedynamic
调用代替 。(调用将采用零参数并绑定到返回所需常量的方法句柄。)但是,这样的解决方法对于关键要求没有帮助,但是,它能够将合成常量作为引导参数传递。
另一种替代方法CONSTANT_Dynamic
是使用static final
字段命名所需的常量,并在静态初始化器(<clinit>
)中计算它们的值。这种方法需要额外的元数据(每个常量的一次性字段定义)并且不足以避免引导循环问题。这些问题通过使用解耦的静态初始化器构建私有嵌套类来解决,但这也需要额外的元数据。如果语言演变为使用许多这样的常量,那么来自过多元数据的应用程序就会膨胀。
另一种方法是旋转静态方法,这些方法执行常量精化逻辑,然后懒惰地调用它们invokedynamic
。同样,这种一次性方法是元数据开销,与之相比较大CONSTANT_Dynamic
。
实际上,用于模拟这些功能的元数据开销太大。
此功能以JVM为中心,因此不依赖于更高的软件层。
为了确保正确的设计,它至少需要通过几个用例进行实验性采用。即使原型被丢弃,库原型也是必须的。
与此同时invokedynamic
,广泛采用需要javac
后端使用,这反过来可能需要语言扩展。作为基本的第一步int.class
,如果可能的话,应该检查需要隐藏静态方法的转换变通方法,例如转换或切换映射表,并用新常量重新构造。
所有者 | Dmitrij Pochepko |
类型 | 特征 |
范围 | 履行 |
状态 | 关闭/交付 |
发布 | 11 |
零件 | 热点/编译器 |
讨论 | hotspot dash编译器开发人员在openjdk dot java dot net |
功夫 | 大号 |
持续时间 | 大号 |
评论人 | Mikael Vidstedt,Vladimir Kozlov |
受认可 | 弗拉基米尔科兹洛夫 |
创建 | 2017/10/10 12:40 |
更新 | 2018/09/10 14:45 |
问题 | 8189104 |
改进现有的字符串和数组内在函数,并java.lang.Math
在AArch64处理器上实现sin,cos和log函数的新内在函数。
- 比较并匹配其他架构的性能
- 调整通用AArch64端口内在函数,仅在单个ARM64体系结构实现上获得最佳性能
- 端口内部到ARM CPU端口
专用的CPU架构特定的代码模式可提高用户应用程序和基准测试的性能。
内部函数用于利用CPU体系结构特定的汇编代码,而代码是针对给定方法执行的通用Java代码,以提高性能。虽然大多数内在函数已经在AArch64端口中实现,java.lang.Math
但仍然缺少以下方法的优化内在函数:
- sin(正弦三角函数)
- cos(余弦三角函数)
- log(数字的对数)
该JEP旨在通过为这些方法实施优化的内在函数来弥补这一差距。
同时,虽然大多数内在函数已经在AArch64端口中实现,但是某些内在函数的当前实现可能不是最佳的。具体而言,AArch64架构的一些内在函数可能受益于软件预取指令,存储器地址对齐,多流水线CPU的指令放置,以及用更快的指令或SIMD指令替换某些指令模式。
这包括(但不限于)这样的典型操作String::compareTo
,String::indexOf
,StringCoding::hasNegatives
,Arrays::equals
,StringUTF16::compress
,StringLatin1::inflate
,和各种校验和计算。
根据内在算法,最常见的内部用例和CPU细节,可以考虑以下更改:
- 使用ARM NEON指令集。如果
UseSIMDForMemoryOps
现有算法具有非NEON版本,则此类代码(如果将创建任何代码)将被置于标志(例如)下。 - 使用prefetch-hint指令(PRFM)。此指令的效果取决于各种因素,例如CPU硬件预取器的存在及其功能,CPU /内存时钟比,内存控制器细节以及特定的算法需求。
- 重新排序指令并减少数据依赖性,以允许在可能的情况下执行无序执行。
- 如果需要,请避免未对齐的内存访问。某些CPU实现在跨16字节边界,dcache-line边界发出加载/存储指令时会受到惩罚,或者对于不同的加载/存储指令具有不同的最佳对齐(例如,参见Cortex A53指南)。如果对齐版本的内在函数不会减慢与对齐无关的CPU上的代码执行速度,那么改进地址对齐以帮助那些确实有一些损失的CPU可能是有益的,只要它不会显着增加代码复杂性。
- 将使用JMH基准测试对Cavium ThunderX,ThunderX2和Cortex A53硬件进行内在性能测试。
- 将使用
jtreg
测试套件测试功能正确性。如果现有测试库未提供足够的覆盖范围,则可能会创建其他测试。
- 将努力实现AArch64内在函数的最佳性能通用版本。如果无法做到这一点,可能需要编写给定硬件供应商的特定版本的内在函数。
- 无法对所有AArch64硬件变体执行测试和性能测量。我们将依靠OpenJDK社区对我们目前在内部没有的硬件进行测试,如果他们在提交补丁进行审核时发现它们是必要的。
- 此JEP范围内的内在函数是特定于CPU体系结构的,因此更改它们不会影响共享的HotSpot代码。
所有者 | Aleksey Shipilev |
类型 | 特征 |
范围 | 履行 |
状态 | 关闭/交付 |
发布 | 11 |
零件 | 热点/ gc |
讨论 | hotspot dash gc dash dev at openjdk dot java dot net |
功夫 | 小号 |
持续时间 | 小号 |
涉及到 | JEP 304:垃圾收集器接口 |
评论人 | Andrew Haley,罗曼肯克 |
受认可 | Mikael Vidstedt |
创建 | 2017/02/14 08:23 |
更新 | 2018/09/24 15:53 |
问题 | 8174901 |
开发一个处理内存分配但不实现任何实际内存回收机制的GC。一旦可用的Java堆耗尽,JVM将关闭。
提供完全被动的GC实现,具有有限的分配限制和尽可能低的延迟开销,但代价是内存占用和内存吞吐量。成功的实现是孤立的代码更改,不会触及其他GC,并且在JVM的其余部分中进行最小的更改。
将手动内存管理功能引入Java语言和/或JVM并不是一个目标。引入新API来管理Java堆不是目标。更改或清理内部JVM接口以适应此GC不是目标。
众所周知,Java实现可广泛选择高度可配置的GC实现。各种可用的收集器最终满足不同的需求,即使它们的可配置性使它们的功能相交。有时更容易维护单独的实现,而不是在现有GC实现上堆积另一个配置选项。
有一些用例,其中一个简单的无操作GC证明是有用的:
-
性能测试。拥有几乎不做任何事情的GC是一个有用的工具,可以为其他真正的GC进行差异性能分析。使用无操作GC可以帮助过滤掉GC引起的性能假象,例如GC工作人员调度,GC障碍成本,不幸时间触发的GC循环,位置变化等。此外,还存在非GC引发的延迟伪像(例如,调度打嗝,编译器转换打嗝等),并删除GC引起的伪像有助于对比这些。例如,使用no-op GC可以估算低延迟GC工作的自然“背景”延迟基线。
-
记忆压力测试。对于Java代码测试,为分配内存建立阈值的方法对于断言内存压力不变量很有用。今天,我们必须从MXBeans中获取分配数据,甚至使用解析GC日志。使GC只接受有限数量的分配,并在堆耗尽时失败,简化了测试。例如,知道测试应该分配不超过1 GB的内存,我们可以使用-Xmx1g配置no-op GC,如果违反了该约束,则让它与堆转储一起崩溃。
-
VM接口测试。对于VM开发目的,使用简单的GC有助于理解VM-GC接口具有功能分配器所需的绝对最低要求。对于无操作GC,接口不应该有任何实现,良好的接口意味着Epsilon的BarrierSet只使用默认实现中的无操作屏障实现。这可以证明VM-GC接口是理智的,这对于代替JEP 304(“垃圾收集器接口”)很重要。
-
非常短暂的工作。短期工作可能依赖于快速退出以释放资源(例如堆内存)。在这种情况下,接受GC循环以徒劳地清理堆是浪费时间,因为无论如何堆都将被释放。请注意,GC周期可能需要一段时间,因为它取决于堆中的实时数据量,这可能很多。
-
最后一次延迟改进。对于超级延迟敏感的应用程序,开发人员可以清楚地了解内存分配并准确了解应用程序内存占用,甚至拥有(几乎)完全无垃圾的应用程序,接受GC循环可能是一个设计问题。还有一些情况下,重新启动JVM - 让负载均衡器找出故障转移 - 有时是比接受GC周期更好的恢复策略。在那些应用中,长GC循环可能被认为是错误的,因为这会延长故障的检测,并最终延迟恢复。
-
最后一次吞吐量改进。即使对于非分配工作负载,GC的选择意味着选择工作负载必须使用的GC障碍集,即使实际上没有GC循环也是如此。所有OpenJDK GC都是世代的(非主线Shenandoah和ZGC除外),它们至少发出一个参考写屏障。避免这种障碍可以带来最后一点的吞吐量改进。对此有一些地方警告,见下文。
Epsilon GC的外观和感觉与其他任何OpenJDK GC一样-XX:+UseEpsilonGC
。
Epsilon GC通过在单个连续的已分配内存块中实现线性分配来工作。这允许GC中的简单无锁TLAB(线程局部分配缓冲区)发布代码,然后可以重用现有VM代码处理的无锁内TLAB分配。发布TLAB还有助于保持由实际分配的进程限制的进程占用驻留内存。Humone / out-of-TLAB分配由相同的代码处理,因为在此方案中分配TLAB和分配大对象之间几乎没有区别。
Epsilon使用的屏障集是完全空的/无操作,因为GC不执行任何GC循环,因此不关心对象图,对象标记,对象复制等。引入新的屏障集实现是可能是此实现中最具破坏性的JVM更改。
由于Epsilon运行时接口的唯一重要部分是发布TLAB,因此其延迟在很大程度上取决于发布的TLAB大小。对于任意大的TLAB和任意大的堆,延迟开销可以用任意低的正值来描述,因此名称。(替代原始故事:“epsilon”经常表示“空符号”,与本GC的无操作性质一致)。
一旦Java堆耗尽,就不可能进行分配,也不可能进行内存回收,因此我们必须失败。那时有几种选择; 大多数都符合现有的GC所做的:
- 扔
OutOfMemoryError
一个描述性的消息。 - 执行堆转储(像往常一样启用
-XX:+HeapDumpOnOutOfMemoryError
)