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

Esp*_*osa 16 java concurrency multithreading jvm new-operator

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

// Thread 1:
  new Cat()

// Thread 2:
  new Dog()

// Thread 3:
  new Mouse()
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

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

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

apa*_*gin 12

我在这个答案中简要介绍了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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

3.老一代的分配.

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

  • 还有逃逸分析导致堆栈上的分配。 (3认同)

Smi*_*_61 5

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

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