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

时间:2014-08-26 17:50:55

标签: java multithreading concurrency jvm new-operator

让我们假设这将在一个真正的并行环境中同时发生在一个虚拟机上:

// Thread 1:
  new Cat()

// Thread 2:
  new Dog()

// Thread 3:
  new Mouse()

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

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

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

当为 Cat Dog Mouse 实例分配堆空间时,线程之间必定存在某种同步,否则它们很容易结束相互覆盖。这是否意味着每个 new 运算符都隐藏在某些同步块中?通过这种方式,很多人可以免费使用"算法实际上并不完全无锁;)

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

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

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

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

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

2 个答案:

答案 0 :(得分:12)

我在this answer中简要描述了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。如果将其关闭,对象将在Eden中分配,如下所述。

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

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

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

3。老一代的分配。

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

答案 1 :(得分:5)

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

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