JVM如何确保新对象的内存分配的线程安全性

让我们假设这将在一个真正的并行环境中同时发生,一个VM:

// Thread 1: new Cat() // Thread 2: new Dog() // Thread 3: new Mouse() 

JVM如何确保堆上内存分配的线程安全性?

堆是所有线程的一个,它有自己的内部数据。

为简单起见假设一个简单的压缩垃圾收集器实现,-XX:+ UseSerialGC -XX:+ UseParallelGC,带有简单的增量指针,用于标记可用空间的开始和Eden(堆)中的一个连续可用空间。

当为CatDogMouse实例分配堆空间时,线程之间必须存在某种同步,否则它们很容易最终被覆盖。 这是否意味着每个运算符都隐藏在一些同步块中? 这样,许多“无锁”算法实际上并不完全无锁;)

我假设内存分配是由应用程序线程本身同步进行的,而不是由另一个专用线程进行的。

我知道TLAB或线程本地分配缓冲区。 它们允许线程在Eden中具有单独的存储区域以进行分配,因此不需要同步。 但我不确定TLAB是否默认设置,这是一个非常模糊的HotSpotfunction。 注意:不要混淆TLAB和ThreadLocal变量!

我还假设,对于更复杂的垃圾收集器,如G1或非压缩垃圾收集器,必须维护更复杂的堆结构数据,如CMS的空闲块列表,因此需要更多同步。

更新 :请让我澄清一下。 我接受HotSpot JVM实现的答案以及有和没有活动TLAB的变体。

更新 :根据我的快速测试 , TLAB在我的64位JDK 7上默认设置为串行,并行和CMS垃圾收集器,但不适用于G1 GC。

我在这个答案中简要描述了HotSpot JVM中的分配过程。
对象的分配方式取决于分配对象的堆区域。

1. TLAB。 最快,最常用的方式。

TLAB是Eden为线程本地分配保留的区域。 每个线程可以创建许多TLAB:只要一个填充,就会使用#2中描述的技术创建新的TLAB。 即创建新的TLAB就像在Eden中直接分配大型元对象一样。

每个Java线程都有两个指针: tlab_toptlab_limit 。 TLAB中的分配只是一个指针增量。 由于指针是线程本地的,因此不需要同步。

 if (tlab_top + object_size <= tlab_limit) { new_object_address = tlab_top; tlab_top += object_size; } 

-XX:+UseTLAB默认情况下启用-XX:+UseTLAB 。 如果将其关闭,对象将在Eden中分配,如下所述。

2.伊甸园(年轻一代)的分配。

如果TLAB中没有足够的空间用于新对象,则创建新的TLAB或直接在Eden中分配对象(取决于TLAB废弃限制和其他人体工程学参数)。

Eden中的分配类似于TLAB中的分配。 还有两个指针: eden_topeden_end ,它们对整个JVM都是全局的。 分配也是指针增量,但由于Eden空间在所有线程之间共享,因此使用primefaces操作。 线程安全性通过使用特定于体系结构的primefaces指令来实现: CAS (例如x86上的LOCK CMPXCHG )或LL / SC (ARM上)。

3.老一代的分配。

这取决于GC算法,例如CMS使用免费列表。 旧生成中的分配通常仅由垃圾收集器本身执行,因此它知道如何同步其自己的线程(通常与分配缓冲区,无锁primefaces操作和互斥锁的混合)。

这在Java规范中没有规定。 这意味着每个JVM都可以执行它,只要它能够工作并遵循Java的内存保证。

很好地猜测它如何与移动的GC一起工作,每个线程都会获得自己的分配区域。 在分配对象时,它会在简单的指针处增加。 非常简单,非常快速的分配,没有锁定。 当它已满时,它会获得一个分配给它的新分配区域,或者GC将所有活动对象移动到堆的连续部分,并将现在为空的区域返回给每个线程。 我不确定这是否是在任何JVM中实际实现的方式,并且GC同步会很复杂。