让我们假设这将在一个真正的并行环境中同时发生在一个虚拟机上:
// 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。
答案 0 :(得分:12)
我在this answer中简要描述了HotSpot JVM中的分配过程 分配对象的方式取决于分配它的堆的区域。
TLAB是Eden为线程本地分配保留的区域。每个线程可以创建许多TLAB:只要一个填充,就会使用#2中描述的技术创建新的TLAB。即创建一个新的TLAB就像直接在Eden中分配一个大的元对象一样。
每个Java线程都有两个指针:tlab_top
和tlab_limit
。 TLAB中的分配只是一个指针增量。由于指针是线程本地的,因此不需要同步。
if (tlab_top + object_size <= tlab_limit) {
new_object_address = tlab_top;
tlab_top += object_size;
}
默认情况下启用 -XX:+UseTLAB
。如果将其关闭,对象将在Eden中分配,如下所述。
如果TLAB中没有足够的空间用于新对象,则创建新的TLAB或直接在Eden中分配对象(取决于TLAB废弃限制和其他人体工程学参数)。
Eden中的分配与TLAB中的分配类似。还有两个指针:eden_top
和eden_end
,它们对整个JVM都是全局的。分配也是一个指针增量,但由于Eden空间在所有线程之间共享,因此使用原子操作。线程安全是通过使用特定于体系结构的原子指令实现的:CAS(例如x86上的LOCK CMPXCHG
)或LL/SC(在ARM上)。
这取决于GC算法,例如CMS使用免费列表。旧生成中的分配通常仅由垃圾收集器本身执行,因此它知道如何同步其自己的线程(通常与分配缓冲区,无锁原子操作和互斥锁的混合)。
答案 1 :(得分:5)
这不在Java规范中指定。这意味着每个JVM都可以执行它,只要它能够工作并遵循Java的内存保证。
关于如何使用移动GC的一个很好的猜测,每个线程都会获得自己的分配区域。在分配对象时,它会在简单的指针处增加。非常简单,非常快速的分配,没有锁定。当它已满时,它将获得分配给它的新分配区域,或者GC将所有活动对象移动到堆的连续部分,并将现在为空的区域返回到每个线程。我不确定这是否在任何JVM中实际实现,并且GC同步会很复杂。