您是否要在Java线程中不消耗内存,就好像它们本身并不会减慢速度一样? 信誉良好,这个问题回答了这个问题。
我们在披萨盒上解释Loom项目的工作! 加油!
所有这些都已删除并专门为Habr编写。
呼号
您是否经常在Web服务上看到此图片:起初一切都很好,然后有一百万中国人来找您,该服务创建了一百万个线程并淹没在地狱中?

你想要这么漂亮的照片吗?

换句话说,您是否要在Java线程中不占用内存但又不在内存中并且不会减慢速度? 信誉良好,这个问题回答了这个问题。
实际上,我们将参与拆解新框架 。 还记得Wylsacom如何打开iPhone包装吗? 有些人已经不记得以前的评论者了,但是为什么呢? 因为哈勃(Habr)是主流据点,而对不起,付费的vidos就是腹泻的光芒 。 在这篇文章中,我们将专门讨论技术核心。
首先,两分钟到眼球,免责声明和其他垃圾,必须要说。 如果您太懒,可以跳过它。
首先,视频中所说的一切只是我的个人想法,与雇主或光荣的甲骨文公司,蜥蜴世界政府以及在砂浆中的该死的东西无关。 我什至在凌晨三点写下这封信,这很明显是我的个人主动性,也是我的个人垃圾。 所有比赛完全是随机的。
但是还有另一个奖励目标。 我们经常谈论科特林的协程。 最近,对Corutin的神罗姆·伊利扎罗夫( Roma Elizarov)以及将要为其编写后端的Pasha Finkelstein进行了采访。 不久将接受Kotlin的父亲Andrei Breslav的采访。 在Loom项目的任何地方都以一种或另一种方式提及,因为它是协程的类似物。 而且,如果您不知道Loom是什么,那么在阅读这些访谈时您可能会感到愚蠢。 有一些很酷的家伙,他们讨论很酷的事情。 你在那儿,而你不在他们那里,你是傻子。 这很愚蠢。
不要这样做,请阅读Loom在本文中的内容,或者进一步观看此视频,我将解释所有内容。
那么,并发症是什么。 有这样的家伙,罗恩·普雷斯勒。

去年,他去了邮件列表 ,说Java中的线程很烂,并建议他运行运行时并对其进行修复。 如果不是因为他早些时候写过《 Quasar》 ,每个人都会嘲笑他,扔石头,拉屎,这实际上很酷。 您可以在Quasar宣誓很长时间,但是它似乎确实存在并且可以奏效,并且从所有方面看,这很可能是一项成就。
只是说,有很多政府无所事事而无所作为。 好吧,弄对了,我是一样的。 或者有些人似乎是很酷的工程师,但是总的来说,在无意识的情况下,他们会说:“在Java中,您需要改进线程。” 有什么改进? 什么是线程?
人们通常懒得思考。
像个玩笑:
佩特卡(Petka)和瓦西里·伊万诺维奇(Vasily Ivanovich)坐飞机。
瓦西里·伊万诺维奇(Vasily Ivanovich)问:-Petka,设备吗?
Petka回复:-200!
瓦西里·伊万诺维奇:-那200呢?
佩特卡:-电器呢?
我讲一个故事。 我今年春天在乌克兰,我们是从白俄罗斯飞来的(您知道为什么直接从圣彼得堡来不可能做到这一点)。 在海关,我们坐了大约两个小时。 海关官员非常友善,严肃地说,问Java是否是过时的技术。 附近有坐飞机的人,他们飞到同一个konf。 而且我是一名演讲者,我必须下蹲,站起来,并且如预期的那样,无耻地谈论我根本不使用的东西。 在他谈论称为Liberica的JDK发行版的过程中,这就是Raspberry Pi的JDK。
你怎么看? 甚至不到六个月,人们就敲了我的购物车,然后说,看,我们已经在Liber上放下了解决方案,我已经在白俄罗斯语jfuture.by konf上获得了有关此问题的报告。 这就是方法。 这不是一个糟糕的传教士,而是一个普通的工程师。
顺便说一句,我们很快将举行2018年Joker大会 ,其中包括Andrei Breslav (显然是在协程中翻腾 )和Pasha Finkelshtein的 , Josh Long可以被问到Spring对Loom的支持。 好吧,还有很多很酷的杰出专家,快来!
现在,返回到线程。 人们试图通过两个退化的神经元来思考,他们wind起拳头,喃喃自语:“ Java中的线程不是这样,Java中的线程不是这样。” 设备! 什么电器? 这通常是地狱。
普雷斯勒(Presler)来了,这是一个普通的,没被贬低的家伙,起初他做得很理智。 一年后,看到了一个工作演示。 我说了所有这些,是为了让您理解问题的常规描述,常规文档是这种特殊的英雄主义。 演示通常是空间。 这是第一个实际上在这个方向上做任何事情的人。 他最需要。
Presler与演示一起在会议上发表了讲话,并发布了以下视频:
实际上,整篇文章都是对那里所说内容的回顾。 我完全不假装这种材料的独特性,本文中的所有内容都是Ron发明的。
讨论涉及三个痛苦的主题:
可能是,看到Quasar并与他的小故障作斗争使他感到非常恶心,以至于没有力量-您需要将其推入运行时。
一年前,从那时起,他们就开始制作原型。 有些人已经不再希望我们有一天能看到一个演示了,但是一个月前他们诞生了该演示,并展示了此推文中可见的内容 。

该演示中的所有三个痛苦的话题都直接存在于代码中,或者至少在道德上存在。 好吧,是的,他们还没有掌握尾声,但是他们想要。
发行
不满意的用户,应用程序开发人员在制作API时被迫在两把椅子之间进行选择。 山峰建在一把椅子上,而花在另一把椅子上生长。 而且没有一个适合我们。

例如,如果您编写一个可同步运行的服务,则该服务可与旧版代码很好地配合使用,那么调试和监视性能就变得很容易。 带宽和可伸缩性会出现问题。 仅仅因为您现在可以在一个简单的硬件上运行的线程数就可以在商品硬件上运行-假设有两千个。 这远少于可以为此服务器打开的连接数。 从网络代码的角度来看,这几乎是无止境的。
(嗯,是的,这与Java中的套接字排列得很早有关,但这是另一个对话的主题)
假设您正在编写某种MMO。

例如,在《 EVE Online》的北战争期间,有2,400名飞行员聚集在太空中的一个点,每个人-有条件的,是否用Java编写-都不是一个线程,而是多个线程。 而且,试点当然是复杂的业务逻辑,而不是可以通过放大镜手动排除的任何HTML发行。
那场战斗的响应时间太长,以至于玩家不得不等待几分钟来等待投篮。 据我所知,CCP专门为那场战斗投入了其集群的巨大硬件资源。
虽然,我可能以徒劳的EVE为例,因为据我所知,一切都是用Python编写的,而在具有多线程的Python中,它仍然比我们的更糟糕-我们可以认为语言功能竞争不佳。 但是,这个例子很清楚,而且带有图片。
如果您总体上对IMO主题感兴趣,特别是对“北战争”的历史感兴趣,那么最近在Bulzhat频道上已经出现了关于该主题的非常不错的视频(无论名称是什么意思),请从我的时间戳记下观看。
我们回到主题。
另一方面,您可以使用某种异步框架。 它是可扩展的。 但是我们将立即陷入非常困难的调试阶段,对性能进行复杂的分析,我们将无法将其与旧版软件无缝集成,您将不得不重写很多东西,将它们包裹在可恶的包装器中,并且通常感觉就像我们被强奸了一样。 连续几次。 事实上,实际上,在整整几天里,我们写这篇文章的时候,您都会有那样的感觉。
我问专家,著名的院士埃斯科巴(Escobar),他对此有何看法:

怎么办 所谓的费伯斯急忙营救。
在一般情况下,光纤是这样的轻量级线程,它们也会在地址空间中泛滥(您知道,因为不会发生奇迹)。 但是与普通线程不同,它们不使用抢占式多任务,而是协作式多任务。 在Wikipedia上阅读更多内容 。
光纤可以同时实现同步和异步编程的优点。 结果,铁的利用率提高了,并且同一任务在群集中使用的服务器更少。 好吧,在我们的口袋里,我们会薰衣草。 巴斯 奴隶 钱 好吧,你明白了。 对于已保存的服务器。
在十字路口
我想讨论的第一件事。 人们不了解延续和纤维之间的区别。
现在将有一个邪教启示!
我们将宣布一个事实:Continuation和Fiber是两个不同的事物。
延续
纤维建立在称为Continuations的机制的顶部。
连续性(更确切地说是定界的连续性)是一种计算,执行,是程序的一部分,可以入睡,然后从入睡的地方唤醒并继续执行。 甚至在他睡觉时,有时甚至可以将其克隆或序列化。
我将使用“ continuation”一词,而不是“ continuation”(如Wikipedia所写),因为我们所有人都使用英语进行交流。 使用普通的俄语术语,很容易会遇到俄语和英语术语之间的差异太大而其他人都无法理解所说内容的情况。
我有时还会使用“挤出”一词,而不是英文版本的“ yield”。 只是“收益”一词-确实很讨厌。 因此,将会出现“挤出”。
所以在这里。 非常重要的一点是,连续体中不应存在任何竞争。 它本身是此过程的最小原语。
您可以将延续视为Runnable
,在其中可以调用pause()
方法。 它在内部并且直接存在,因为我们的多任务协作。 然后您可以再次运行它,而不是重新计算所有内容,他将继续从他离开的地方开始。 那种魔术。 我们将回到魔术。
哪里可以继续进行演示-我们将在最后讨论。 现在让我们谈谈那里有什么。
延续类本身位于java.base中,所有链接都将在描述中。 ( src/java.base/share/classes/java/lang/Continuation.java
)。 但是此类非常庞大,数量庞大,因此仅查看从中进行的某种挤压是有意义的。
public class Continuation implements Runnable { public Continuation(ContinuationScope scope, Runnable body); public final void run(); public static void yield(ContinuationScope scope); public boolean isDone(); protected void onPinned(Reason reason) { throw new IllegalStateException("Pinned: " + reason); } }
请注意,实际上此文件在不断变化。 例如,从前一天开始,延续未实现Runnable
接口。 将此视为一种草图。
看一下构造函数。 body
-这是您要运行的代码, scope
-是一种skop,可让您在延续中嵌套延续。
因此,您可以使用run
方法将这段代码预先安排到最后,也可以使用yield
方法将其替换为某些特定的数组(此处需要使用该数组来将动作转发到嵌套处理程序,但我们并不关心用户)。 您可以使用isDone
方法询问是否一切都完成了。
并且由于仅由当前实现的需求所决定的原因(但很有可能,它也将包含在发行版中),所以并非总是能够产生yield
。 例如,如果在延续过程中我们过渡到本机代码并且本机框架出现在堆栈上,那么就不可能卡住。 如果您在连续主体内部使用本机监视器(例如同步方法)时尝试将其挤出,也会发生这种情况。 默认情况下,当您尝试伪造此方法时,会引发一个异常...,但是基于连续性构建的光纤会重载此方法并执行其他操作。 这将稍后。
您可以大约以下方式使用它:
Continuation cont = new Continuation(SCOPE, () -> { while (true) { System.out.println("before"); Continuation.yield(SCOPE); System.out.println("after"); } }); while (!cont.isDone()) { cont.run(); }
这是Presler演示文稿中的一个示例。 同样,这不是一个“琐碎的”代码,这是某种草图。
这是我们正在进行的延续的草图,在此延续的中间,我们被排挤了,然后在一个无休止的循环中,我们询问延续是否有效,是否应该继续。
但是总的来说,这并不意味着普通的应用程序程序员会与此API相关。 它适用于系统框架的创建者。 诸如Spring框架之类的系统形成框架将在推出后立即采用此功能。 您会看到的。 考虑这是一个预测。 如此轻松的预测,因为这里的一切都很明显。 所有用于预测的数据都是。 此功能太重要了,无法适应。 因此,无需事先担心有人会以这种形式的编码来折磨您。 好吧,如果您是Spring开发人员,那么您就知道自己在做什么。
现在,在接续的基础上,构建了光纤。
纤维类
因此,在我们的情况下意味着光纤。
这是一种抽象,即:
- 轻量级线程是在JVM本身而不是在操作系统中处理的;
- 创建,维护生命,切换任务的开销极低;
- 可以运行数百万次。
许多技术正在尝试以一种或另一种方式制造光纤。 例如,在Kotlin中,协程是在非常智能的字节码生成上实现的。 非常聪明 。 但是运行时是实现此类事情的更好场所。
JVM至少已经知道如何很好地处理线程,而我们所需要做的就是简化多线程的编码过程。 您可以使用异步API,但这很难被称为“简化”:即使使用Reactor和Spring Project Reactor之类的工具,它允许编写看似线性的代码,如果您需要调试复杂的问题,也无济于事。
纤维。
纤维由两部分组成。 这是:
那就是:

您可以决定谁在这里。 我认为这里的策划者是杰伊。
- Fiber连续包装要执行的代码
- 调度程序在承载线程池上启动它们
我将它们称为载体线。

当前的原型使用java.util.concurrent.Executor
,而内置的调度程序ForkJoinPool
。 我们拥有一切。 将来,可能会出现一些更智能的东西,但现在这样。
延续如何表现:
- 当发生锁定时(例如,在IO上),它被挤出(屈服)。
- 准备好继续时继续执行(例如,IO操作已完成,您可以继续)。
工作现状:
- 主要关注哲学,观念;
- API不固定,而是“用于显示”。 这是一个研究原型;
java.lang.Fiber
类有一个现成的编码工作原型。
将进行讨论。
已经在光纤中看到了什么:
- 它运行任务启动;
- 在承运人上停车的停车场;
- 等待光纤完成。
电路图
mount(); try { cont.run(); } finally () { unmount(); }
- 我们可以将光纤安装在线架上。
- 然后继续运行;
- 等到她被挤走或说实话停下来;
- 最后,我们总是离开线程。
该伪代码将在ForkJoinPool
或其他一些ForkJoinPool
上执行(最终将在最终版本中执行)。
实际使用
Fiber f = Fiber.execute( () -> { System.out.println("Good Morning!"); readLock.lock(); try { System.out.println("Good Afternoon"); } finally { readLock.unlock(); } System.out.println("Good Night"); });
看,我们正在创建一种纤维,其中:
- 欢迎大家;
- 我们正在阻止可重入的oke;
- 回来后,恭喜你吃了午饭;
- 最终释放锁;
- 说再见
一切都非常简单。
我们不会直接造成拥挤。 Project Loom本身知道在触发readLock.lock();
时readLock.lock();
他应该干预并暗中进行镇压。 用户看不到它,但是它在那里发生。
堆,到处都是堆!
让我们以披萨堆栈为例来说明发生了什么。
最初,载体线程处于等待状态,什么也没有发生。

回想一下堆栈顶部的顶部。
然后安排执行光纤,然后光纤任务开始运行。

在他自己内部,他显然发起了一个延续,其中已经找到了真实的代码。

从用户的角度来看,我们在这里还没有启动任何功能。
那只是用户代码的第一帧出现在堆栈上,并用紫色标记。
此外,代码被执行,执行,在某个时刻任务试图捕获锁并在其上阻塞,这导致自动挤出。

延续堆栈上的所有内容都存储在某个神奇的地方。 并消失。

如您所见,流返回到光纤,回到Continuation.run
的指令。 这是光纤代码的结尾。
光纤任务结束,媒体载体正在等待新的作业。

光纤停在某处,连续体完全被挤出。
迟早,拥有锁的人将其释放的时刻到了。
这导致这样一个事实,即正在等待释放锁的光纤已打开包装。 该光纤的任务再次开始。
- 重新进入解锁
- Locksupport.unpark
- 光纤断开
- ForkJoinPool.execute
然后我们快速返回到最近的堆栈。

而且,载体线可以完全不同。 这是有道理的!
再次运行继续。

魔术来了! 恢复堆栈,并从Continuation.yield
之后的指令Continuation.yield
。

我们从刚刚停放的锁中爬出,并开始执行延续中剩余的所有代码:

用户任务结束,控制在continuation.run指令之后立即返回到光纤任务

同时,光纤的执行结束,我们再次处于待机模式。

纤维的下一次发射再次启动了上述整个重生周期。

现场例子
谁曾说这一切可行? 这是关于晚上写的几个微基准测试吗?
作为鞭炮操作的一个例子,奥拉克洛夫派人编写了一个小型Web服务器,并向其提供了请求,以使其窒息。 然后他们转移到纤维上。 服务器停止阻塞,由此得出结论,光纤工作正常。
我没有该服务器的确切代码,但是如果这篇文章获得足够的喜欢和评论,我将尝试自己写一个示例并构建真实的图形。
问题所在
这里有什么问题吗? 当然可以! 费伯斯的整个故事是关于持续存在的问题和权衡取舍的故事。
哲学问题
- 我们需要重新发明线程吗?
- 所有现有代码是否都应在光纤内部正常工作?
当前的原型运行有局限性。 可能会发布,尽管我不想这么做。 尽管如此,OpenJDK仍然尊重无限的兼容性。
有哪些技术限制? 最明显的限制是2件。
问题是一次-您无法取代本机框架
PrivilegedAction<Void> pa = () -> { readLock.lock();
在这里, doPrivileged调用了本机方法。
您doPrivileged
调用doPrivileged
,跳出VM,堆栈中将显示一个本机框架,然后尝试将其停放在readLock.lock()
。 并且在那一刻,载体线将被弄脏直到未被选中。 也就是说,线程消失了。 在这种情况下,承载线可能会终止,并且通常这会破坏整个光纤的概念。
解决该问题的方法是已知的,并且正在对此进行讨论。
问题二-同步块
这是更严重的垃圾
synchronized (object) {
synchronized (object) {
如果监视器捕获在光纤中,则载体线也会踢动。
显然,在全新的代码中,您可以将监视器更改为直接锁定,而不是等待+通知,您可以使用条件对象,但是该如何处理旧式? 这是一个问题。
线程API? Thread.currentThread()? 线程本地人?
在当前的原型中, Thread
和Fiber
了一个通用的超类Strand
。
这使您能够以最小的方式传输API。
下一步要做什么-与该项目一样,这是一个问题。
现在线程API发生了什么?
- 光纤中首次使用
Thread.currentThread()
会创建一种影子线程,即Shadow Thread; - 从系统的角度来看,这是一个“未发布”的线程,并且其中没有VM元信息。
- ST试图效仿所有可能的方法。
- 但是您必须了解旧的API有很多垃圾;
- 更具体地说,Shadow Thread对
stop
, suspend
, resume
和未捕获的异常以外的所有事物都实现了Thread API。
线程本地变量怎么办?
- 现在线程本地变量变成了光纤本地变量;
- 这有很多问题,所有这些都在讨论中;
- 特别讨论了一组用途;
- 线程在历史上既正确又错误地使用(错误使用线程的人仍然希望得到某些东西,而您不能完全让他们失望);
- 通常,这会创建整个应用程序:
- 高级:容器中的连接或密码缓存;
- 低级:系统库中的处理器。
吃多少

线程:
- 堆栈:内核数据结构上的1MB和16KB;
- 每个线程实例:2300字节,包括VM元信息。
纤维:
- 连续堆栈:从数百字节到千字节;
- 每个光纤实例:200-240字节。
差别很大!
这正是让数百万人解雇的原因。
什么可以停车
显然,最神奇的事情是发生某些事件时自动停车。 目前支持什么?
- Thread.sleep,加入;
- java.util.concurrent和LockSupport.lock;
- IO:套接字上的网络(套接字读取,写入,连接,接受),文件,管道;
- 所有这些都还没有完成,但是隧道中的光是可见的。
光纤之间的通讯
每个人都问的另一个问题是:如何在光纤之间竞争性地交换信息。
- 当前的原型在
Runnable
启动任务,如果出于某些原因,可以将其转换为CompletableFuture
; - java.util.concurrent“有效”。 您可以以标准方式混淆所有内容;
- 可能有用于多线程的新API,但这并不准确;
- 一堆小问题,例如“纤维应该返回值吗?”; 一切都在讨论中,它们不在原型中。
原型中的延续如何实现?
延续上有明显的要求:您需要使用尽可能少的RAM,并且需要尽快在它们之间切换。 否则,将其保留在数百万中将是行不通的。 这里的主要任务是以某种方式不为每个停车位做完整的堆栈副本。 而且有这样的计划! 让我们尝试在图片中对此进行解释。
当然,最酷的方法是将所有堆栈放在Java臀部上并直接使用它们。 但是目前尚不清楚如何编码,因此原型使用复制。 但是,复制时会有一个很小但很重要的hack。
我们有两把椅子……我是说,有两叠。 臀部有两个java数组。 一个是对象数组,我们将在其中存储对对象的引用。 第二个是原始的(例如,亲密的),它将处理其他所有内容。

现在,我们处于第一次将要执行连续的状态。
run
调用一个称为enter
的内部方法:

然后执行用户代码,直到第一个挤出为止。

此时,将进行VM调用,该调用将freeze
。 在此原型中,这是直接在物理上完成的-使用复制。

我们开始将帧从本地堆栈顺序复制到java hip的过程。

有必要检查监视器是否存放在此处或是否使用了本机代码,或者是否确实不允许我们继续工作。

如果一切顺利,我们首先将其复制到原始数组中:

然后我们隔离对对象的引用,并将其保存在对象数组中:

其实,给读过这个地方的每个人都喝两杯茶!
此外,我们对本机堆栈的所有其他元素继续此过程。

万岁! 我们将所有内容复制到臀部的巢穴中。 您可以安全地跳到通话地点,而不必担心我们丢失了一些东西。 一切都是时髦的。

现在,迟早,调用代码将再次调用我们的延续。 她必须从上次离开的地方继续。 这是我们的任务。

检查延续是否正在运行,是的,它正在运行。 因此,您需要调用VM,清理堆栈上的一些空间,然后调用内部的VM函数。 “解冻”被翻译成俄文“解冻”,“解冻”,听起来很合逻辑。 有必要将帧从连续堆栈中解冻到我们的主要本机堆栈中。
我不确定解冻的茶是否很清楚。 错误的抽象就像带门的小猫。 但这将为我们做。

我们制作了很明显的副本。
首先使用原始数组:

然后从链接:

您需要修补复制的内容以获得正确的堆栈:

对所有帧重复淫秽:

现在,您可以返回yield
并继续进行,就好像什么都没发生一样。

问题在于堆栈的完整副本根本不是我们想要的。 这是非常抑制性的。 所有这一切都是隔离链接,检查是否固定,这不是很快。 最重要的是-所有这些线性地取决于堆栈的大小! 总之,地狱。 不需要这样做。
相反,我们有另一个想法-懒惰复制。
让我们回滚到已经冻结的地方。

我们像以前一样继续该过程:

与以前一样,我们清理本地堆栈上的位置:

但是,我们并不是连续复制所有内容,而是仅复制一个或几个框架:

现在的黑客。 您需要修补方法C
的返回地址,以便它指向某个返回屏障:

现在您可以放心地返回yield
:

反过来,这将导致调用C
方法中的用户代码:

现在想象一下, C
想要返回调用它的代码。 但是他的召唤者是B
,他不在筹码中! 因此,当他尝试返回时,将转到返回地址,该地址现在是返回屏障。 而且,您知道,这将再次引起thaw
:

thaw
后将解冻连续堆栈上的下一帧,这是B
:

实际上,我们根据要求懒惰地复制了它。
接下来,我们从连续堆栈中删除B
并再次设置障碍(需要设置障碍,因为在连续堆栈上还剩下一些东西)。 依此类推。

但是,假设B
不会返回到调用代码,而是首先调用其他方法D
而且这种新方法也希望被挤出。

在这种情况下,当需要进行freeze
,我们将仅需要将本机堆栈的顶部复制到延续堆栈中:

因此,完成的工作量并不线性地取决于堆叠的大小。 它线性地取决于我们在工作中实际使用的帧数。
还剩下什么?
开发人员牢记某些功能,但并未进入原型。
- 序列化和克隆。 在另一台计算机上,在其他时间等继续运行的能力
- JVM TI和调试,就好像它们是常规线程一样。 如果您在读取套接字时受阻,那么您将不会从yield处看到漂亮的跳跃,在原型中,该线程将像其他任何普通线程一样被阻塞。
- 甚至没有碰到尾递归。
后续步骤:
去哪儿
原型在OpenJDK存储库中作为早午餐制作。 您可以通过切换到早午餐fibers
在此处下载原型 。
这样做是这样的:
$ hg clone http://hg.openjdk.java.net/loom/loom $ cd loom $ hg update -r fibers $ sh configure $ make images
, OpenJDK. , -, - , .

-, C++ GNU . , Windows. , VirtualBox Ubuntu , Cygwin msys64. , msys , Cygwin.
, , , .
- , mercurial extension fsmonitor. , , hg help -e fsmonitor
.
~/.hgrc :
[fsmonitor] mode = on
- . -, cp -R ./loom ./loom-backup
.
, . , Java- , .
sh configure
- . , Ubuntu, Autoconf ( sudo apt-get install autoconf
). — OpenJDK Ubuntu, , . Windows , .
, , hg diff --stat -r default:fibers
.
, , , .
结论
«, ». «», . «Loom» — « ». Project Loom .
, . , «» , , — , , , — .
, , XIX , .

. -, .
, . IDE .
, , , « », «», « » .
? . .
谢谢啦