
今天结束了第十二届JVM LS峰会。 像往常一样,这是一次硬性事件,其中包含有关虚拟机及其上运行的语言的技术演示。 与往常一样,峰会在甲骨文校园的圣塔克拉拉举行。 像往常一样,希望到达这里的人多于地方:参与者的数量不超过120。像往常一样,没有行销,只有内脏。
这次峰会对我来说已经是第三次了,尽管时差很糟,每次我高兴地参观它。 在这里,您不仅可以收听报告,还可以结识来自JVM领域的更好的人,参加非正式的对话,在研讨会上提问,并且通常会感到自己取得了很大的成就。
如果您没有参加峰会,那没关系。 大多数报告几乎在峰会之后立即发布到YouTube上。 实际上它们已经可用 。 为了使浏览更容易,我将在此处简要介绍我设法参加的所有报告和研讨会。
7月29日
多数人认为,这与Clojure语言中的Future编译功能无关,而仅与语言的发展,代码生成的复杂性以及它们遇到的问题有关。 例如,事实证明,在Clojure中,在最后一次使用之后使局部变量无效非常重要,因为如果在局部变量中延迟生成的列表的开头然后绕过它,则垃圾收集器可能不会收集已经绕过的节点,并且程序可能会因OutOfMemory崩溃。 通常,C2 JIT编译器本身在上次使用后会释放变量,但是该标准不能保证这一点,例如,HotSpot解释器不能保证。
了解功能调用的动态调度的实现也很有趣。 我还了解到,直到最近,Clojure才针对JVM 6,直到最近才切换到JVM8。现在,编译器作者着眼于invokedynamic。
Loom项目是Java的轻量级光纤。 一年前,艾伦(Alan)和罗恩(Ron)已经讨论了这个项目,然后一切似乎都进行得很顺利,即将准备就绪。 但是,该项目尚未正式进入Java,并且仍在存储库的单独分支中进行开发。 当然,事实证明有必要解决许多细节。
从ReentrantLock.lock到Socket.accept的许多标准API已经适用于光纤:如果在光纤内部进行这样的调用,将保存执行状态,将取消堆栈堆栈,并将操作系统线程释放用于其他任务,直到事件唤醒光纤(例如, ReentrantLock.unlock)。 但是,例如,良好的旧同步块仍然无法正常工作,而且看来,如果不认真重构JVM中的所有同步支持,就无法做到。 如果在光纤的起点和断点之间的堆栈中有本机帧,则无法再次展开堆栈。 在这两种情况下,都不会发生爆炸,但光纤不会释放流。
关于Fiber如何与旧的java.lang.Thread类进行比较,存在很多问题。 一年前,有一个想法使Fiber成为Thread的子类。 现在,他们已经拒绝了它并使其成为一个独立的实体,因为在每根光纤中模拟常规流的所有行为都非常昂贵。 在这种情况下,光纤内部的Thread.currentThread()将返回生成的blende,而不是执行所有操作的真实线程。 但是这种障碍会表现得很好(尽管它会减慢工作速度)。 重要的想法是在任何情况下都不能给出光纤在光纤内部运行的实际媒体流。 这很危险,因为光纤很容易移动到另一根线。 欺骗将会继续。
奇怪的是,项目参与者已经将一些准备性更改推入了主要的JDK存储库中,以使他们的生活更轻松。 例如,在Java 13中,完全用Java从本地代码重写了doPrivileged方法,从而使性能提高了约50倍。 为什么这是一个Loom项目? 事实是,这种特殊方法经常出现在堆叠的中间,尽管它是本机的,但具有该堆叠的光纤并没有停止。 一种或另一种方式,该项目已经受益。
在项目页面上,您可以阅读文档并下载源代码树,并且还有今天可以使用和播放的二进制程序集 。 我们希望在未来的几年中一切都将被整合。
Brian Goetz-工作坊“琥珀项目”
同时,正在进行有关Loom项目的研讨会,但我去了Amber。 在这里,我们简要讨论了该项目的目标以及正在进行的主要JEP- 模式匹配 , 记录和密封类型 。 然后,整个讨论都落入了范围界定的私人问题。 我在去年的Joker大会上谈到了这一点 ,原则上没有说什么很新。 我试图用隐含的联合类型if(obj instanceof Integer x || obj instanceof Long x) use(x.longValue())
来提出一个想法,但是我没有看到热情。
从各个方面来说,Google的一个出色项目,就是使用数据来搜索种族,这些形式是从不同的流中读取和写入相同的非易失性字段或数组元素,而无需建立先发生后关系。 该项目最初是作为用于本机代码的LLVM模块编写的,现在已经适用于HotSpot。 这是带有邮件列表和存储库的官方 OpenJDK 项目 。
根据作者的说法,事情现在已经很正常了,您可以组装和玩。 此外,她不仅在Java代码中发现竞争,而且在本机库代码中发现竞争。 不会在虚拟机本身的代码中搜索种族,因为所有同步原语都以它们自己的方式编写,并且TSan无法检测到它们。 这组作者说,TSan不会给出误报。
主要问题是性能。 现在,对于Java代码,仅分别对解释器进行检测,完全禁用了JIT编译,并且已经很慢的解释器会减慢几次。 但是,如果您有足够的资源(当然Google也有足够的资源),则可以偶尔使用TSan来驱动测试套件。 还计划向JIT添加工具,但这是对JVM的更为严重的干预。
有人问禁用JIT编译是否不会影响结果,因为某些种族可能不会出现在解释器上。 发言人没有排除这种可能性,但说他们已经发现了很多比赛,需要很长时间才能完成。 因此,在TSan下运行项目时要小心:您可能会发现令人不快的事实。
每个人都在等待Java中的值类型,但没人知道它们何时出现。 但是,运动越来越严重。 已经有具有当前L2里程碑的测试二进制程序集 。 在目前的计划中,完整的瓦尔哈拉(Valhalla)将会达到L100里程碑,但作者们仍然乐观,并认为已经完成了超过百分之二的任务。
因此,从语言的角度来看,我们有带有内联修饰符的类,这些类由虚拟机以特殊方式处理。 此类的实例可以嵌入其他对象中,包含内联类实例的平面数组也是可能的。 实例没有标题,这意味着没有身份,哈希代码由字段计算, ==
也由字段计算,在此类上尝试进行同步或Object.wait()
会引发IllegalMonitorStateException。 当然,将null
写入此类型的变量将不起作用。 但是,作者提供了另一种选择:如果您声明了一个内联类Point
,那么您可以声明一个字段或类型为(surprise-surprise!) Point?
的变量Point?
,然后堆上将有一个完整的对象(如拳击),其中包含标头,标识和null
。
严重的开放问题仍然是泛型的专业化以及现有类(例如Optional
)向内联类的迁移,以免破坏现有代码(是的,人们在Optional
类型的变量中写入null
)。 但是,图像隐约可见,并且可以看到间隙。
令我惊讶的是,原来的Java拼图游戏的合著者Neil Gafter现在可以在Microsoft的.Net运行时上工作。 看到有关JVM LS上CLR(所谓的.Net运行时)的报告也令人惊讶。 但是结识来自其他世界的同事的经验总是有用的。 该报告讨论了CLR中的引用和指针的种类,用于值类型的字节码指令以及关于reduce的精美通用化通用函数的方式。 有趣的是,.Net中值类型的目标之一是与本机代码互操作。 因此,值类型中字段的位置严格固定,可以直接投影到结构上而无需进行转换。 JVM从未执行过这样的任务,以及如何处理本机互操作-参见下文。
再次更新去年的报告 。 同样,问题是,如果一年前一切看起来还不错,为什么他们仍然没有发布任何东西。
向量是多个数字的集合,在硬件中,可以由单个向量寄存器(例如用于AVX512的zmm0)表示。 在向量中,您可以从数组加载数据,对它们执行操作,例如逐元素乘法,然后将它们扔回去。 JIT编译器将所有包含处理器指令的操作都包含在这些指令中。 操作数量非常庞大。 如果缺少某些内容,则使用替代的慢速实现。 理想情况下,不会创建矢量中间对象;转义分析会起作用。 使用处理器的所有功能,所有标准计算算法都可以一口气进行矢量化处理。
不幸的是,对于作者而言,没有瓦尔加拉(Valgalla)很难:逃逸分析非常脆弱,可能无法轻松进行。 这些向量必须只是内联类,然后所有问题都将消失。 尚不清楚是否可以在Valgalla的第一个版本之前发布此API。 似乎已经准备好了。 其中的问题称为在代码支持下的困难。 对于不同大小的寄存器和不同类型的数据,有很多重复的片段,因此大多数代码是从模板生成的,很难维护它。
使用也不完美。 Java中没有运算符重载,因此数学看起来很丑陋:您必须编写va.lanewise(SUB, vb.lanewise(MUL, 42)).lanewise(MAX, 0)
而不是max(va-vb*42, 0)
va.lanewise(SUB, vb.lanewise(MUL, 42)).lanewise(MAX, 0)
。 像在C#中一样可以访问AST lambda会很好。 然后可以生成一个自定义的lambda操作,例如MYOP = binOp((va, vb) -> max(va-vb*42, 0))
并使用它。
7月30日
第二天在汇编的旗帜下过去了。
JVM OpenJ9项目的一名IBM员工谈论了他们在JIT和AOT编译方面的经验。 总会有问题:JIT启动缓慢,因为它正在预热; 编译的CPU成本。 AOT-由于缺少概要文件(可以进行概要分析,但性能不佳,但在执行过程中概要文件并非平凡且并非总是与执行时的概要文件匹配),使用,绑定到目标平台,操作系统,垃圾收集器更加困难。 通过组合方法可以解决一些问题:从AOT编译的代码开始,然后从JIT结束。 所有这些的一个很好的选择是缓存JIT。 如果您有很多虚拟机(Hello,微服务),它们都将转向单独的服务-JIT编译器(是,JITaaS),那里的一切就像成人,业务流程,负载平衡一样。 该服务会编译。 通常,他可以将现成的代码提供给某个方法,因为该方法已经在另一个JVM上进行了编译。 这大大改善了预热,从JVM服务中消除了资源消耗,并通常减少了总资源消耗。
通常,JITaaS可能是JVM世界中的下一个流行词。 不幸的是,我没有发现这是否可以立即播放还是仍然是封闭开发。
GraalVM本机映像是一个编译成本机代码的Java应用程序,该程序无需JVM就可以运行(不同于使用jaotc之类的AOT编译器编译的模块)。 更准确地说,这不是Java应用程序。 为了正常工作,他需要一个封闭的世界,也就是说,所有代码在编译阶段都应该可见,没有Class.forName。 您可以使用反射和方法句柄,但是在进行编译时,必须明确指出将通过反射使用哪些类和方法。
另一个有趣的事情是类初始化。 在编译过程中会初始化许多类。 也就是说,默认情况下,静态字段将由编译器计算,并且结果将被写入组装的映像,并且在您启动应用程序时,只需读取它即可。 为了获得更好的编译质量,这是必需的:如果编译器知道静态字段的值,则可以进行任何常量折叠。 使用JIT一切都很好,解释器执行静态初始化,然后知道常量,即可进行编译。 并且在构建本机应用程序时,您必须要技巧。 当然,这会导致有趣的迷幻效果。 因此,类通常按照它们被访问的顺序进行初始化,并且在编译期间该顺序是未知的,并且可以以其他顺序进行初始化。 如果在类初始化程序之间存在循环引用,则可以看到JVM代码的行为与本机映像的区别。
车间Schatzl-热点GC。
整理所有与垃圾收集器有关的痛苦。 不幸的是,我听了最多。 我记得曾经讨论过操作系统内存的召回问题,其中包括对所有人的令人讨厌的Xmx。 有个好消息:在Java 13中添加了一个新选项 -XX:SoftMaxHeapSize。 到目前为止,仅ZGC收集器支持它,但是G1也可以赶上。 它设置了一个堆大小的限制,除非在紧急情况下不能以其他方式工作,否则不应超过该大小。 因此,您可以设置一个较大的Xmx(例如,等于整个RAM的大小)和一些合理的SoftMaxHeapSize。 然后,JVM大部分时间会将自己保持在框架中,但是在峰值负载下,它仍然不会抛出OutOfMemoryError,而是会从OS中获取更多内存。 当负载下降时,内存将返回。
微软蔡仔(Mai-Chin Tsai)谈到了CLR中的JIT和AOT编译功能。 AOT编译已经为他们开发了很长时间,但是最初(ngen.exe)它是在目标平台上进行的,就像第一次启动时一样(如果您使用Windows,则在Windows文件夹中查找* .ni.dll文件)。 获得的文件取决于本地Windows的版本,甚至取决于其他DLL-ek。 因此,如果依赖项已更新,则必须重新编译所有本机模块。 在第二代(crossgen)中,作者预编译了相对独立于硬件和OS版本以及相关性的应用程序和模块。 这使代码变慢,因为现在必须诚实地将依赖关系调用虚拟化。 通过在应用程序中连接JIT并重新编译热代码,可以解决此问题。 然后,我们讨论了多级(分层)编译(在CLR中这似乎还处于起步阶段,尽管它已经用Java进行了至少十年的开发),还谈到了使AOT真正跨平台的未来计划。
阿里巴巴的同事介绍了他们解决JVM预热问题的方法。 他们将JVM用于许多Web服务。 原则上,快速启动并不是那么重要,因为平衡器总是可以等到机器启动后才开始向它发送请求。 但是,问题在于机器不会在没有请求的情况下进行预热:描述处理请求逻辑的代码不会被调用,这意味着它不会编译。 当第一个请求到达时,它将被编译,也就是说,无论平衡器等待了多少时间,第一个请求都会出现性能故障。 以前,他们试图通过向即将到来的服务发送虚假请求,然后再向其发送真实请求来解决此问题。 这种方法很有趣,但是生成这样一个伪造的流会导致所有必要代码的编译相当困难。
另一个问题是去优化。 在前一千个查询中, if
始终沿第一个分支进行查询,则JIT编译器通常会抛出第二个查询,并在其中插入反优化陷阱以减小代码大小。 但是第1001个请求转到第二个分支,去优化工作成功,整个方法转到了解释器。 当统计信息再次被编译时,而方法是由C1编译器编译,然后是由C2编译器的完整配置文件编译,则用户会遇到速度变慢的情况。 然后使用相同的方法可以优化另一种方法,然后一切都会进行新的方法。
JWarmUp解决了以下问题。 在该服务的第一次运行期间,将编写几分钟的编译日志:它记录了已编译的方法以及按分支机构,类型等进行的必要配置信息。如果重新启动此服务,则在启动后立即初始化日志中的所有类,并编译记录的方法考虑到以前的配置文件。 结果,编译器将在启动时运行良好,然后平衡器将开始向该JVM发送请求。 到此时,她已经编译了所有热代码。
值得注意的是,此处没有解决快速入门问题。 由于编译了许多方法,因此启动可能会更慢,其中一些方法可能在启动后仅需要几分钟。 但是该日志证明是可重用的:与AOT不同,您可以在不同的体系结构上或在不同的垃圾收集器上提升服务,并重用以前的日志。
作者已经尝试了很长时间,将JWarmUp推入OpenJDK。 到目前为止,未成功,但是工作正在进展。 最主要的是,您可以在Code Review服务器上完全访问完整的补丁程序,因此您可以轻松地将其应用于HotSpot源,并使用JWarmUp自己构建JVM。
这是曼彻斯特的研究论文,但作者声称该项目已经在某些地方实施。 它也是OpenJDK的附加组件,它使将某些Java代码轻松转移到GPU,iGPU,FPGA或将其并行化到其处理器内核变得非常容易。 为了在GPU上进行编译,他们使用了GraalVM,并在其中构建了后端-TornadoJIT。 正确编写的Java方法将透明地转到相应的设备。 没错,他们说在FPGA上编译可能需要几个小时,但是如果您的任务被认为是一个月,那为什么不这样做。 某些基准测试(例如,离散傅里叶变换)比裸Java快一百倍以上,这在原则上是可以预期的。 该项目已完全上载到GitHub ,在这里您还可以找到有关该主题的科学出版物。
都是同一首歌-一项长期的项目,每一次峰会演讲,一年前一切看起来都已经准备好了,但是仍然没有发行。 事实证明,自那时以来,重点已经转移。
该项目的想法是与本机代码的改进的互操作。 每个人都知道使用JNI有多痛苦。 真的好痛 巴拿马项目消除了这种痛苦:使用jextract Java类是从本机库的* .h文件生成的,通过调用本机方法使用它们非常方便。 在C / C ++方面,您根本不需要写一行。 此外,一切变得更快:调用Java-> native和native-> Java的开销有时会减少。 您还想要什么?
一个已经存在了很长时间的问题-将数据数组传输到本机代码中。 到目前为止,推荐的方法是DirectByteBuffer,它存在很多问题。 最严重的问题之一是非托管生存期(当垃圾收集器选择适当的Java对象时,缓冲区将消失)。 由于这个问题和其他问题,人们使用Unsafe,通过尽职调查,可以很容易地放下整个虚拟机。
这意味着您需要在Java堆之外进行新的常规内存访问。 分配,结构化访问器,显式删除。 结构化访问器-因此,如果需要编写,则不必自己计算偏移量,例如struct { byte x; int y; }[5]
struct { byte x; int y; }[5]
struct { byte x; int y; }[5]
。 相反,您一次描述了此结构的布局,然后执行例如VarHandle
,它可以通过跳过y
来读取所有x
。 当然,在这种情况下,应该像通常的Java数组一样始终进行边界检查。 此外,应该禁止进入已经封闭的区域。 如果我们想将性能保持在不安全级别并允许从多个线程进行访问,那么这将是一项艰巨的任务。 简而言之,观看视频,非常有趣。
讲习班:弗拉基米尔·科兹洛夫-大都会项目
Metropolis项目结合了所有用Java重写JVM部分的尝试。 今天,它的主要部分是Graal编译器。 近年来,它发展得非常好,并且已经有关于完全替代老化的C2的真正话题。 曾经存在一个引导问题:圣杯开始缓慢,因为他本人必须被JIT编译或解释。 然后出现了AOT编译(是的,AOT编译项目的主要目标是圣杯本身的引导程序)。 但是,对于AOT而言,圣杯吞噬了Java应用程序堆中相当一部分,而这些部分实际上可能并不想共享其堆。 现在我们已经学会了使用Graal Native Image将grail转换为本地库,最终使我们能够将编译器与常规堆隔离。 在圣杯编译的代码达到最佳性能的情况下,某些基准仍然存在问题。 例如,在固有和向量化方面,圣杯落后于C2。 但是,由于非常强大的内联和转义分析,它简单地破坏了功能代码中的C2,在功能代码中创建了许多不可变的对象和许多小的函数。 如果您在Rock上书写但仍不使用圣杯,请运行以使用。 而且,在最新版本的JDK中,做几个键非常简单,套件中已经包含了所有内容。
7月31日
Kevin Bourrillion-Java的空注释
凯文宣布了一个新项目,但要求不要公开发言,也不要在YouTube上发布他的演讲录音。 不好意思 , .
Sorbet (!) Ruby, Ruby . , Stripe Ruby , , . , .
Lightning Talks
- . Remi Forax , , . , :
, - , .
ML AI , . , Facebook — getafix , --, , . . , , . , , .
. . OpenJDK Committer Workshop.