[Javawatch Live]一个请求请求的故事。 SubstrateVM中的os.version

自上一个技巧成功以来已经过去了一年:在YouTube上发布视频而不是发布。 在文字版本中, “关于单例的可耻讨论”在YouTube上获得了7,000的观看次数,在Habré上获得了两倍的观看次数。 对于完全顽固的状态写的文章,并讲述古老的纽扣手风琴-这是成功的一点。

今天,我整夜都在安装新版本。 这次的话题是新近出现的:致力于实验技术的历史-SubstrateVM。 但是坚韧程度已经上升到一个新的水平。



真的很期待您的评论! 我提醒您,如果您真的想改善本文中的内容,最好将其提交到Github上 。 我想说“喜欢并订阅新频道 ,但是所有发行版本仍会在您的Java集线器中吗?”

从技术上讲:视频在结尾处有一层胶合。 我刚刚写了一个未压缩的视频,而我仅有500 GB的m2 ssd迅速溢出。 而且没有其他硬盘驱动器可以承受如此大的数据压力。 因此,我不得不断开半个小时的连接,然后又设法找到另外五十个演出来记录最后几分钟。 这是通过删除GoogleChrome编译的文件来实现的。 我在录制时就在FB中写了关于录制软件的文章,这有很多痛苦。

技术上的另一个有趣之处:YouTube由于某种原因阻止了我进行实时流式传输。 同时,帐户上没有任何罢工和污名。 希望这只是一个门框,在90天后一切都会退还。

本文将引用Oracle拥有的代码。 您不能自己使用此代码(除非您阅读了原始许可证,并且在例如GPL的条件下允许使用它)。 这不是在开玩笑。 奥尔索,我警告过。

普里卡兹卡(童话故事将会出现)


许多人已经听说过“新的Java将用Java编写”的故事,并想知道这将是怎么回事。 有一份《大都市计划》政策文件和John Rose的相应信函,但那里的一切都很模糊。

这听起来像是一种令人毛骨悚然的血腥魔法。 您可以立即尝试进行同样的操作,不仅没有魔法,而且当您用铁锹敲牙时,一切都像铲子的背面一样愚蠢。 当然,有一些细微差别,但这将是在很晚以后的某一天。

我将以一个发生在夏天的启发性故事为例进行演示。 他们如何在学校撰写论文“我如何度过了夏天”。

首先,我要说一点。 当前在Oracle Labs中进行提前编译的项目是GraalVM。 实际上,进行nishtyaki并将Java代码转换为可执行文件(转换为可执行文件)的组件是SubstrateVM或SVM。 请勿将其与数据讽刺专家(支持向量机)使用的缩写混淆。 关于SVM,作为关键部分,我们将进一步讨论。

问题陈述


因此,“我如何度过夏天”。 我坐在度假中,在Grail github上花了两个F5,碰到了一个



一个人希望os.version提供正确的值。

好吧,我想修复该错误? 男孩说-男孩做了。

我们将检查客户是否在撒谎。

 public class Main { public static void main(String[] args) { System.out.println(System.getProperty("os.version")); } } 

首先,实际Java中的排气效果是: 4.15.0-32-generic 。 是的,这是新鲜的Ubuntu LTS Bionic。

现在尝试在SVM上执行相同操作:

 $ ls Main.java $ javac -cp . Main.java $ ls Main.class Main.java $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 151.77 ms (cap): 1,662.32 ms setup: 1,880.78 ms error: Basic header file missing (<zlib.h>). Make sure libc and zlib headers are available on your system. Error: Processing image build request failed 

好吧,是的。 这是因为我专门为“干净”测试制作了一个全新的虚拟机。

 $ sudo apt-get install zlib1g-dev libc6 libc6-dev $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 135.17 ms (cap): 877.34 ms setup: 1,253.49 ms (typeflow): 4,103.97 ms (objects): 1,441.97 ms (features): 41.74 ms analysis: 5,690.63 ms universe: 252.43 ms (parse): 1,024.49 ms (inline): 819.27 ms (compile): 4,243.15 ms compile: 6,356.02 ms image: 632.29 ms write: 236.99 ms [total]: 14,591.30 ms 

绝对的运行时数据可能令人恐惧。 但是,首先,这就是它的原意:非常可怕的优化在这里应用。 其次,这是您想要的脆弱的虚拟机。

最后,关键时刻:

 $ ./main null 

看来我们的客人没有说谎,真的行不通。

第一种方法:从主机窃取属性


然后,我在全局os.version搜索os.version ,发现所有这些属性都在SystemPropertiesSupport类中。

我不会写该文件的完整路径,因为为IntelliJ IDEA和Eclipse生成正确项目的功能直接内置于SVM中。 这非常酷,完全不让人想起大多数OpenJDK必须经历的折磨。 让IDE为我们打开类。 因此:

 public abstract class SystemPropertiesSupport { private static final String[] HOSTED_PROPERTIES = { "java.version", ImageInfo.PROPERTY_IMAGE_KIND_KEY, "line.separator", "path.separator", "file.separator", "os.arch", "os.name", "file.encoding", "sun.jnu.encoding", }; //... } 

然后,我完全不加思索,只是向该集合添加了另一个变量:

 "os.arch", "os.name", "os.version" 

我重建,运行并获得令人垂涎的行4.15.0-32-generic 。 万岁!

但这是问题所在:现在,在运行此代码的每台计算机上,它总是发出4.15.0-32-generic 。 即使在uname -a也会在旧的Ubunt上返回存储桶的先前版本。

很明显,这些变量在编译时已写入源文件。
实际上,您需要仔细阅读以下注释:

 /** System properties that are taken from the VM hosting the image generator. */ private static final String[] HOSTED_PROPERTIES 

需要使用其他方法。

结论


  • 如果要使“ main Java”中的系统属性出现在SVM中,这非常简单。 我们将所需的属性写在正确的位置,仅此而已。
  • 您可以在IDE中工作,该IDE同时支持Java和Python。 例如,在具有Python插件的IntelliJ IDEA Ultimate中,或在Eclipse中相同。

第二种方法


如果浏览SystemPropertiesSupport SystemPropertiesSupport ,我们会发现更加合理的东西:

 /** System properties that are lazily computed at run time on first access. */ private final Map<String, Supplier<String>> lazyRuntimeValues; 

除其他外,使用这些属性仍然不会阻止构建可执行文件的过程。 显然,如果我们对HOSTED_PROPERTIES进行大量HOSTED_PROPERTIES ,那么一切都会变慢。

通过引用返回以下内容的方法,惰性属性的注册以明显的方式发生:

 lazyRuntimeValues.put("user.name", this::userNameValue); lazyRuntimeValues.put("user.home", this::userHomeValue); lazyRuntimeValues.put("user.dir", this::userDirValue); 

而且,所有这些方法引用都是接口,并且对每个受支持的平台都实现了this::userDirValue 。 在这种情况下,这些是PosixSystemPropertiesSupportWindowsSystemPropertiesSupport

如果我们出于好奇而无法使用Windows的实现,我们将看到可悲的事情:

 @Override protected String userDirValue() { return "C:\\Users\\somebody"; } 

如您所见,尚不支持Windows :-)但是,真正的问题是Windows可执行文件的生成尚未完成,因此支持这些方法实际上是完全不必要的工作。

也就是说,您需要实现以下方法:

 lazyRuntimeValues.put("os.version", this::osVersionValue); 

然后在两个或三个可用接口中支持它。

但是在那里写什么呢?

结论


  • 如果要添加在运行时计算的新属性,则只需编写一个方法即可。 结果可能取决于当前的操作系统,切换机制已经在起作用并且没有询问。

一点考古


首先想到的是偷看OpenJDK中的实现并大胆地复制粘贴。 一点考古学和抢劫将永远不会阻止一个勇敢的探险家!

随意在Idea中打开任何Java项目,在其中编写System.getProperty("os.version") ,然后按ctrl +单击转到getProperty()方法的实现。 事实证明,所有这一切都愚蠢地存在于Properties

看起来,只需复制粘贴这些Properties被填充的地方,然后大笑起来,就会消失在空白中。 不幸的是,我们遇到一个问题:

 private static native Properties initProperties(Properties props); 

不,不,不。



但这一切开始得如此顺利。

有男孩吗?


众所周知,使用C ++是不好的。 SVM中使用C ++吗?

就这样! 甚至还有一个特殊的软件包: src/com.oracle.svm.native

在此包中,horror-horror位于getEnviron.c文件中,如下所示:

 extern char **environ; char **getEnviron() { return environ; } 

是时候搞砸C ++了


现在深入研究并打开完整的OpenJDK源代码。

如果某人还没有,则可以在网上查找或下载。 我警告您,他们仍然在Mercurial的帮助下从这里摇摆,仍然需要大约半小时。

我们需要的文件位于src/java.base/share/native/libjava/System.c

是否注意到这是文件的路径,而不仅仅是名称? 没错,您可以推销您新的闪亮时尚主意,该主意是每年200美元购买的。 您可以尝试CLion ,但是为了避免不可逆转的精神损害,最好只使用Visual Studio Code 。 他已经突出显示了某些内容,但仍然不明白自己所看到的内容(他没有将所有内容划掉红色)。

重播System.c

 java_props_t *sprops = GetJavaProperties(env); PUTPROP(props, "os.version", sprops->os_version); 

依次将它们放在src/java.base/unix/native/libjava/java_props_md.c
每个平台都有其自己的此类文件,它们通过#define切换。

从这里开始。 有很多平台。 由于GraalVM并未正式支持AIX,因此可以评分任何像AIX的死灵患者(据我所知,GNU-Linux,macOS和Windows最初计划在其中)。 GNU / Linux和Windows支持使用<sys/utsname.h> ,它具有用于获取操作系统名称和版本的预定义方法。

但是macOS 的govnokod非常糟糕

  • 名称“ Mac OS X”用硬涂层表示(尽管它早已是macOS)。
  • 这取决于makoshi的版本。 在10.9之前的版本中,SDK不具有operatingSystemVersion函数,您必须手动阅读SystemVersion.plist
  • 对于此减法,它使用ObjC扩展名,如下所示:

 // Fallback if running on pre-10.9 Mac OS if (osVersionCStr == NULL) { NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile : @"/System/Library/CoreServices/SystemVersion.plist"]; if (version != NULL) { NSString *nsVerStr = [version objectForKey : @"ProductVersion"]; if (nsVerStr != NULL) { osVersionCStr = strdup([nsVerStr UTF8String]); } } } 

如果最初有一个想法以良好的样式手动重写它,那么它很快就会变成现实。 如果我从ifs弄乱了面条的丛林中某个地方,有人会打碎它,然后将我吊死在中央广场怎么办? 好吧,nafig。 有必要复制粘贴。

结论


  • 不需要IDE;
  • 与C ++的任何通信都是痛苦的,令人不愉快的,一见钟情。

复制粘贴是常态吗?


这是进一步折磨所依赖的重要问题。 我确实不想手动重写,但因违反许可而上法庭更为糟糕。 所以我去了github并直接问Codrut Stancu。 这是他的回答

»重复使用OpenJDK代码,例如,从许可的角度来看,复制粘贴是正常的事情。 但是,这有一个很好的理由。 如果可以通过重复使用JDK代码而不复制来实现某个功能,例如用替换对其打补丁,那就更好了。”

这听起来像是官方的复制粘贴许可!

我们正常交谈...


我开始移植这段代码,但遇到了懒惰。 要测试不同版本的macOS,您需要至少找到一个具有死灵性10.8 Mountain Lion的软件。 我有两个可用的苹果设备,一个是从朋友那里获得的,我还可以将其部署到一些试用版的VMWare中。

但是懒惰。 这种懒惰救了我。

我去了聊天室 ,问克里斯·西顿,哪个工具链最适合组装。 支持哪个版本的OS,C ++编译器等等。

作为回应,他从聊天中获得了惊讶的沉默,克里斯回答说他不理解问题的实质。

克里斯花了很长时间才弄清楚我想做什么,并要求他不再做
确实缺少SVM的想法。 SVM是纯Java,不应从OpenJDK插入代码。 您可以阅读它,并将其转换为Java,但是没有人想要OpenJDK的C ++代码。 那是我们想要的最后一件事。

数学库的示例无法说服他。 至少它们是用C编写的,而C ++的包含意味着将全新的语言连接到代码库。 还有就是fufufu。

你需要做什么? 用System Java编写。

并且如果无法避免对C / C ++平台SDK的调用,那么这应该是包装在C API中的某些单个系统调用。 即使Platform SDK具有方便的现成方法来在C ++方面以不同的方式处理数据,都用Java提取数据,然后严格用Java编写业务逻辑。

我叹了口气,开始研究源代码,以了解如何以不同的方式完成此工作。

结论


  • 聊天中的人交谈,以了解任何晦涩的细节。 如果问题不是完全白痴,他们就会回答。 尽管此示例表明Chris准备好讨论白痴问题,即使这并不能节省他个人的时间;
  • 项目中根本没有C ++。 没有理由相信有人会让他把他拖到地板下面;
  • 相反,您需要使用C作为最后的手段在System Java中进行编写(例如,在调用平台SDK时)。

不需要小提琴手


亲爱的,不需要小提琴手。 他只吃多余的燃料。

然后我不知所措,因为看看这里。 如果在Windows上有<sys/utsname.h> ,我们非常希望得到他的回答-这很简单。

但是,如果他不在那,那该怎么办?

  • 调用cmd内置命令还是Windows实用程序? 用俄语发布需要解析的文本。 这是最底层的,可能与真正的OpenJDK在这里所要回答的不一致。
  • 从注册表中拿走吗? 例如,即使在这里也有细微差别,例如,当从Windows 7切换到10时,在注册表中存储数字数字的方法也发生了变化,在Windows 10中,您需要粘住主要和次要组件的手,或者简单地回答它是一位数字的Windows 10。 尚不清楚哪种方法更正确(不会使用户的资产悔改)。

幸运的是,保罗·威格勒 Paul Woegerer)提出的要求将我的痛苦打断了 ,后者解决了所有问题。

有趣的是,起初,所有内容都在向导中修复( os.version停止在测试中提供null ),然后才注意到pullrequest。 问题在于,该提交未在github上标记为pullrequest-这是一个简单的提交,注释中带有铭文PullRequest: graal/1885 。 事实是,Oracle实验室的帅哥不使用Github,他们只需要它与外部提交者进行交互即可。 我们所有人都不够幸运,无法在Oracle Labs工作,我们需要订阅有关对存储库的新提交的通知,并全部阅读它们。

但是现在您可以放松下来,看看如何正确实现此功能。

让我们看看System Java是什么样的野兽。

正如我之前说的,当他们试图敲掉牙齿时,一切都很简单,就像铲子的背面一样。 同样痛苦。 看一下池中的报价:

 @Override protected String osVersionValue() { if (osVersionValue != null) { return osVersionValue; } /* On OSX Java returns the ProductVersion instead of kernel release info. */ CoreFoundation.CFDictionaryRef dict = CoreFoundation._CFCopyServerVersionDictionary(); if (dict.isNull()) { dict = CoreFoundation._CFCopySystemVersionDictionary(); } if (dict.isNull()) { return osVersionValue = "Unknown"; } CoreFoundation.CFStringRef dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("MacOSXProductVersion"); CoreFoundation.CFStringRef dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); if (dictValue.isNull()) { dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("ProductVersion"); dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); } if (dictValue.isNull()) { return osVersionValue = "Unknown"; } osVersionValue = DarwinCoreFoundationUtils.fromCFStringRef(dictValue); CoreFoundation.CFRelease(dictValue); return osVersionValue; } 

换句话说,我们用Java逐字写了用C语言写的东西。

DarwinExecutableName一下DarwinExecutableName写法:

  @Override public Object apply(Object[] args) { /* Find out how long the executable path is. */ final CIntPointer sizePointer = StackValue.get(CIntPointer.class); sizePointer.write(0); if (DarwinDyld._NSGetExecutablePath(WordFactory.nullPointer(), sizePointer) != -1) { VMError.shouldNotReachHere("DarwinExecutableName.getExecutableName: Executable path length is 0?"); } /* Allocate a correctly-sized buffer and ask again. */ final byte[] byteBuffer = new byte[sizePointer.read()]; try (PinnedObject pinnedBuffer = PinnedObject.create(byteBuffer)) { final CCharPointer bufferPointer = pinnedBuffer.addressOfArrayElement(0); if (DarwinDyld._NSGetExecutablePath(bufferPointer, sizePointer) == -1) { /* Failure to find executable path. */ return null; } final String executableString = CTypeConversion.toJavaString(bufferPointer); final String result = realpath(executableString); return result; } } 

所有这些CIntPointerCCharPointerPinnedObject

就我的口味而言,这不舒服且丑陋。 您需要手动使用类似于Java类的指针。 您需要及时调用适当的release ,以免内存泄漏。

但是,如果您认为这些是不合理的措施,那么您可以再次查看.NET中GC实现,并且如果您不及时停止,就会对C ++导致的结果感到震惊。 我提醒您,这是一个很大的CPP文件,大于一个兆字节。 关于他的工作有一些描述 ,但是显然不足以使外部贡献者理解。 上面的代码虽然看起来很讨厌,但可以通过Java的静态分析来很好地理解和分析。

至于承诺的实质,我对他有疑问。 至少那里没有实现Windows支持。 当出现Windows的代码源时,我将尝试执行此任务。

结论


  • 需要用System Java编写。 赞美,叫甜面包。 仍然没有选择。
  • 从GitHub上的存储库订阅通知并阅读提交,否则重要的PR将会飞逝;
  • 如果可能,请询问负责此领域的人员的主要功能。 有很多实现的方法,但是它们尚未为大众所知。 有机会发明一辆自行车,这比甲骨文实验室的家伙制造的自行车差得多。
  • 处理功能时,请务必在github上通知负责人。 如果他不回答-写封信,所有团队成员的地址都可以轻松地用Google搜索。

结语


这场战斗结束了,但战争根本没有结束。

战斗机,敏感地等待有关哈布雷的新文章并进入我们的行列

我想提醒您,Oracle唯一的GraalVM 官方推广者Oleg Shelaev将参加下一次Joker会议 。 不仅是“唯一的俄语使用者”,而且是“总体上唯一的俄语使用者”。 该报告的标题( “使用GraalVM提前编译Java” )提示SubstrateVM不能没有。

顺便说一句,奥列格(Oleg)最近获得了一种服务武器-在谢拉耶夫·奥勒格(Haslaé)的帐户上。 尚无帖子,但您可以使用此用户名进行投射。

您可以在Telegram的聊天室聊天中与Oleg和Oleg交谈: @graalvm_ru 。 与Github上的ishshuyev不同,您可以在其中进行任何形式的交流,并且不会有人被禁止( 但这并不准确 )。

我还提醒您,每周我们都会与Debriefing播客一起发布Java Digest。 例如,这是最后的摘要 。 有时,有关GraalVM的新闻会不时出现(事实上,我不会因为尊重观众而将整个问题转为GraalVM新闻发布:-)

感谢您阅读此书-很快再见!

Source: https://habr.com/ru/post/zh-CN420455/


All Articles