问题 对象创建是多线程环境中Java的瓶颈吗?


基于以下理解:

在堆栈中还是在堆中分配变量引用的位置?

我想知道因为所有对象都是在公共堆上创建的。如果多个线程创建对象然后为了防止数据损坏,必须进行一些必须发生的序列化以防止多个线程在相同位置创建对象。现在,有了大量的线程,这个序列化将导致一个很大的瓶颈。 Java如何避免这个瓶颈?或者我错过了什么?

任何帮助赞赏。


10884
2018-02-20 00:08


起源



答案:


现代VM实现为每个线程保留堆上的自己区域以创建对象。因此,只要此区域未满(然后垃圾收集器移动幸存的对象)就没有问题。

进一步阅读: TLAB如何工作 在Sun的JVM中。 Azul的VM 使用略有不同的方法(查看“新线程和堆栈布局”),文章展示了JVM可能在幕后执行的一些技巧,以确保现在的Java速度。

主要思想是保持每个线程(非共享)区域分配新对象,就像在C / C ++堆栈上分配一样。复制垃圾收集非常快速地释放短期对象,少数幸存者(如果有的话)被移动到不同的区域。因此,创建相对较小的对象非常快 锁定免费

无锁分配非常重要,尤其是问题所在 multithreaded environment。它还允许存在真正的无锁算法。即使算法本身是无锁的,但新对象的分配是同步的,整个算法也是有效同步的,并且最终可扩展性较差。 java.util.concurrent.ConcurrentLinkedQueue 这是基于 Maged M. Michael Michael L. Scott的作品 是一个典型的例子。


如果一个对象被另一个线程引用会发生什么? (由于讨论要求)

那个对象(称之为 A)将被转移到一些“幸存者”区域。幸存区域的检查频率低于ThreadLocal区域。它包含,如名称所示,其引用设法逃脱的对象,或特别是 A 设法活着。复制(移动)部分发生在某个“安全点”(安全点排除正确的JIT代码),因此垃圾收集器确保该对象未被引用。更新对象的引用,发布必要的内存围栏,应用程序(java代码)可以继续。进一步阅读此内容 简单的场景

对非常感兴趣的读者,如果可能的话,咀嚼它:非常先进 无气GC算法


12
2018-02-20 00:23



感谢您的回答。总是这样吗?假设我有一个包含10个线程5个生产者和5个消费者的系统。生产者生成数据(对象创建)并将对象放入某个队列中。消费者使用所产生的数据(比如他们将文件中的记录保存起来,并且系统效率高,他们正在进行批量更新)。现在,生产者只创建新对象而消费者不创建任何对象。堆中具有单独区域的系统不会增加堆填充的可能性吗?此外,还有任何重新调整大小的东西吗?它不会导致更多碎片吗? - chiku
此外,我可以通过从用户那里获取输入来动态地创建线程。现在,JVM如何处理这种情况?此外,如果线程死亡然后会发生什么? - chiku
问题是,我真的不知道虚拟机工作的细节,这只是我在某处听到的......我会将我的答案标记为 社区维基,所以其他人可以添加细节。 (我也感兴趣,但现在还不足以进行适当的研究。)你可能会看到“世代垃圾收集器” - 在这种情况下,它是“伊甸园一代”,我认为,这是每个线程的本地。 (在这里报告。) - Paŭlo Ebermann
非常感谢!我会在这里阅读并报告我的发现。 - chiku
@Paŭlo,添加了一些JVM如何工作的东西 - bestsss


答案:


现代VM实现为每个线程保留堆上的自己区域以创建对象。因此,只要此区域未满(然后垃圾收集器移动幸存的对象)就没有问题。

进一步阅读: TLAB如何工作 在Sun的JVM中。 Azul的VM 使用略有不同的方法(查看“新线程和堆栈布局”),文章展示了JVM可能在幕后执行的一些技巧,以确保现在的Java速度。

主要思想是保持每个线程(非共享)区域分配新对象,就像在C / C ++堆栈上分配一样。复制垃圾收集非常快速地释放短期对象,少数幸存者(如果有的话)被移动到不同的区域。因此,创建相对较小的对象非常快 锁定免费

无锁分配非常重要,尤其是问题所在 multithreaded environment。它还允许存在真正的无锁算法。即使算法本身是无锁的,但新对象的分配是同步的,整个算法也是有效同步的,并且最终可扩展性较差。 java.util.concurrent.ConcurrentLinkedQueue 这是基于 Maged M. Michael Michael L. Scott的作品 是一个典型的例子。


如果一个对象被另一个线程引用会发生什么? (由于讨论要求)

那个对象(称之为 A)将被转移到一些“幸存者”区域。幸存区域的检查频率低于ThreadLocal区域。它包含,如名称所示,其引用设法逃脱的对象,或特别是 A 设法活着。复制(移动)部分发生在某个“安全点”(安全点排除正确的JIT代码),因此垃圾收集器确保该对象未被引用。更新对象的引用,发布必要的内存围栏,应用程序(java代码)可以继续。进一步阅读此内容 简单的场景

对非常感兴趣的读者,如果可能的话,咀嚼它:非常先进 无气GC算法


12
2018-02-20 00:23



感谢您的回答。总是这样吗?假设我有一个包含10个线程5个生产者和5个消费者的系统。生产者生成数据(对象创建)并将对象放入某个队列中。消费者使用所产生的数据(比如他们将文件中的记录保存起来,并且系统效率高,他们正在进行批量更新)。现在,生产者只创建新对象而消费者不创建任何对象。堆中具有单独区域的系统不会增加堆填充的可能性吗?此外,还有任何重新调整大小的东西吗?它不会导致更多碎片吗? - chiku
此外,我可以通过从用户那里获取输入来动态地创建线程。现在,JVM如何处理这种情况?此外,如果线程死亡然后会发生什么? - chiku
问题是,我真的不知道虚拟机工作的细节,这只是我在某处听到的......我会将我的答案标记为 社区维基,所以其他人可以添加细节。 (我也感兴趣,但现在还不足以进行适当的研究。)你可能会看到“世代垃圾收集器” - 在这种情况下,它是“伊甸园一代”,我认为,这是每个线程的本地。 (在这里报告。) - Paŭlo Ebermann
非常感谢!我会在这里阅读并报告我的发现。 - chiku
@Paŭlo,添加了一些JVM如何工作的东西 - bestsss


没有.JVM有各种各样的技巧,以避免在“新”时出现任何类型的简单序列化。


1
2018-01-28 01:08



感谢您的回答。我知道简单的序列化将是一个太天真的解决方案。我想了解的是JVM使用什么样的技巧来解决这个问题? - chiku
@chiku,在“PaŭloEbermann”的答案中添加了一些关于技巧部分的信息。 - bestsss
@bestsss,感谢您添加信息。我自己无法找回答案。应该在某个时候这样做。您的编辑回答了我的大部分问题。但是,在我看来仍然没有答案的一个问题是:线程如何处理对象的转义。创建的对象可以通过多种机制进行转义,例如将其添加到共享列表或通过返回对象。有任何想法吗? - chiku
@chiku,这就是垃圾收集的功能。 - bestsss
@bestsss,我很遗憾听起来如此愚蠢。我的问题是:假设我有两个线程T1和T2。 T1调用一个具有func(List <Object> list)之类接口的函数。 T1在函数中创建新对象并将其添加到列表中。现在,T1创建的新对象分配在T1的TLAB上。考虑到T2可以访问列表对象(可能因为它是静态的)。现在,由T1创建的对象不能被垃圾收集,因为List具有对该对象的引用,而T2具有对列表对象的引用。 GC如何帮助?我是否错过了任何太明显的事情?谢谢! - chiku


有时。我写了一个递归方法,生成整数排列并从中创建对象。该方法的多线程版本(来自root = task的每个分支,但并发线程数限制为核心数)并不快。并且CPU负载不高。任务没有共享任何对象。从两种方法中删除对象后,多线程方法的速度提高了约4倍(6个核心)并使用了100%的CPU。在我的测试用例中,这些方法产生了大约4,500,000个排列,每个任务1500个。 我认为TLAB没有用,因为它的空间有限(见: 线程本地分配缓冲区)。


1