萍资讯网

Java 堆内存是线程共享的!面试官:你确定吗?

原标题:Java堆内存由线程共享!记者:你确定吗?

Author l Hollis

本文授权转载自Hollis (ID: Hollis Chung)

Java,作为一种面向对象和跨平台的语言,其对象、内存等。一直是难点知识点。因此,即使是一个Java初学者,他或多或少也必须对JVM有所了解。可以说,关于JVM的知识基本上是每个Java开发人员必须学习的知识,也是在面试中必须测试的知识。

在JVM的内存结构中,两个常见的区域是堆内存和堆栈内存(如果没有指定,本文中提到的堆栈是指虚拟机堆栈)。许多开发人员都熟悉堆和栈的区别,互联网上有许多书籍或文章可能是这样介绍的:

1。堆是线程共享的内存区域,堆栈是线程专用的内存区域。

2。堆主要存储对象实例,堆栈主要存储对各种基本数据类型和对象的引用。

然而,作者可以负责任地告诉每个人,上述两个结论并不完全正确。

本文首先向您展示了为什么我说“堆是线程共享的内存区域,而堆栈是线程专有的内存区域。”这句话不完全正确!

在谈正事之前,请允许我问一个似乎与这个问题无关的问题:Java对象的内存分配过程如何确保线程安全?

Java对象的内存分配过程如何确保线程安全?

我们知道Java是一种面向对象的语言,我们在Java中使用的所有对象都需要被创建。在Java中,有很多方法可以创建对象,但是在任何情况下,在创建对象的过程中,都需要分配内存。

在对象的内存分配过程中,主要是对象的引用指向这个内存区域,然后进行初始化操作。

但是,因为堆是全局共享的,所以可能有多个线程同时在堆上申请空间。如果在并发场景中,两个线程连续地将对象引用指向同一个内存区域,会怎么样?

为了解决这个并发问题,必须同步控制对象的内存分配过程。然而,我们都知道,无论使用哪种同步方案(事实上,虚拟机可能使用CAS),都会影响内存分配效率。

而Java对象分配是Java中的一个高频操作,所以人们想到了另一种提高效率的方法。这里我们关注一个HotSpot虚拟机解决方案:

每个线程预先在Java堆中分配一小块内存,然后直接在自己的“私有”内存中为对象分配内存,并在这部分内存用完时分配新的“私有”内存。

此方案称为TLAB分配,或线程本地分配缓冲区。缓冲区的这一部分是从堆中分离出来的,但只属于本地线程。

什么是TLAB

TLAB是一个特殊的空间,由堆内存中虚拟机的伊甸园划分,它是特定于线程的。当虚拟机的TLAB功能启动时,在线程初始化期间,虚拟机为每个线程分配一个TLAB空间,并且仅将其用于当前线程,因此每个线程都有单独的空间。如果内存需要分配,它将被分配到自己的空间,因此没有竞争,分配效率可以大大提高。

注意上面对“特定线程”、“仅由当前线程使用”和“每个线程都有自己的线程”的描述?

因此,由于TLAB技术,堆内存并不完全由线程共享,并且在其伊甸园区域中仍有一些空间专门分配给线程。

这里值得注意的是,我们说TLAB是线程专有的,但它只在“分配”操作中对线程专有,线程在读取、垃圾收集等操作中共享。在使用上没有区别。

也就是说,虽然每个线程在初始化期间都会在堆内存中申请一个TLAB,但这并不意味着这个TLAB区域中的其他线程根本不能访问内存,其他线程的读取仍然是可能的,但是内存不能在这个区域中分配。

此外,在TLAB分配后,对象的移动和回收不会受到影响,也就是说,尽管对象最初可能通过TLAB分配内存并将其存储在伊甸园区,但它仍将被垃圾回收或移动到幸存者空间、旧世代等。

另外要注意的是,我们说TLAB是在伊甸园区分配的,因为伊甸园区本身并不太大,而且TLAB空间中的内存也很小,默认情况下只占整个伊甸园空间的1%。因此,一定有一些大型对象不能在TLAB中直接分配。

遇到无法在TLAB中分配的大对象时,对象可能仍会在伊甸园区或老年区分配,但这种分配需要同步控制,这就是为什么我们常说小对象比大对象更有效。虽然TLAB在一定程度上大大提高了对象的分配速度,但TLAB也不是没有问题。

如前所述,因为TLAB内存区域不是很大,所以经常会出现不足的情况。《实战Java虚拟机》中有一个例子:

例如,一个线程的TLAB空间有100KB,其中80KB已经被使用了。当需要分配另一个30KB对象时,不能直接在TLAB中分配。在这种情况下,有两种处理方案:

1。如果某个对象所需的空间超过了TLAB中剩余的空间,则该对象将直接分配到堆内存中。

2。如果对象所需的空间超过了TLAB中的剩余空间,则丢弃当前的TLAB,并再次申请TLAB空间以进行内存分配。

以上两种方案各有优缺点。如果采用方案1,可能会出现TLAB只剩下1KB的极端情况,这将导致直接分配堆内存中要分配的大多数对象。

如果采用方案2,也可能会频繁放弃TLAB和频繁应用TLAB。我们知道,虽然TLAB上的内存分配是线程专有的,但是TLAB内存从堆中分区的过程可能确实会有冲突。因此,TLAB的分配过程也需要并发控制。然而,频繁的TLAB分配失去了使用TLAB的重要性。

为了解决这两个方案中存在的问题,虚拟机定义了一个refill_waste值,该值可以转换为“最大浪费空间”。

当请求的内存分配大于refill_waste时,它将选择在堆内存中分配。如果它小于再填充浪费值,则当前的TLAB将被丢弃,并且TLAB将被重新创建用于对象内存分配。

在前面的例子中,TLAB的总空间为100KB,使用80KB,剩下20KB。如果设置的refill_waste值是25KB,那么如果新对象的内存大于25KB,那么直接分配堆内存。如果它小于25KB,则先前的TLAB将被丢弃,并将重新分配一个TLAB空间来为新对象分配内存。

TLAB默认为伊甸园面积的1%,TLAB空间所占的伊甸园空间百分比可以通过选项-Xx : LabWastTargetPercent来设置。

TLAB函数可以打开或关闭,是否打开TLAB分配可以通过设置-XX: /-UseTLAB参数来指定。

TLAB默认为伊甸园面积的1%,TLAB空间所占的伊甸园空间百分比可以通过选项-Xx : LabWastTargetPercent来设置。

默认情况下,运行时TLAB的空间会不断调整,使系统达到最佳运行状态。如果需要禁用TLAB大小的自动调整,可以使用-XX:-ResizeTLAB禁用,也可以使用-xx: tlabsize手动指定TLAB的大小。

TLAB的再填充_浪费也可以调整。默认值为64,这意味着大约1/64的空间大小被用作refill_waste,参数:-xx: tlabrefill _ waste被用于调整。

如果您想观察TLAB的使用,您可以使用参数-XX PringTLAB进行跟踪。

Summary

为了确保对象内存分配期间的线程安全,HotSpot虚拟机提供了一种称为TLAB(线程本地分配缓冲区)的技术。

在线程初始化期间,虚拟机为每个线程分配一个TLAB空间,并且只将它用于当前线程。当需要分配内存时,它会在自己的空间上进行分配,因此没有竞争,分配效率可以大大提高。

因此,“堆是线程共享的内存区域”的说法并不完全正确,因为TLAB是堆内存的一部分。它在读取时确实是线程共享的,但在内存分配时是线程独占的。

TLAB没有太多空间,因此可能需要在堆内存中直接分配大型对象。然后,对象的内存分配步骤是先尝试TLAB分配,然后在空间不足后判断是否应该直接进入老年,然后再决定是否在老年进行伊甸园分配或分配。

多说

我相信,读完这篇文章后,有些人可能会认为作者有点太“字面”和“吹毛求疵”。可能会有一些不耐烦的人,他们只看文章的开头,然后翻到文章的结尾,为开篇做准备。

不管你是否同意作者所说的“堆是线程共享的内存区域”并不完全正确。事实上,这并不重要。重要的是,当提到堆内存、线程共享和对象内存分配时,您可以想到另一个非常特殊的TLAB。

有时候,最可怕的事情不是我不知道,而是我不知道我不知道。

更重要的是,TLAB只是HotSpot虚拟机的一个优化方案,而在Java虚拟机规范中没有对TLAB的规定。因此,这并不意味着所有虚拟机都具有此功能。

这篇文章的概述是基于HotSpot虚拟机的,作者并不是有意“概括”它,而是因为HotSpot虚拟机是目前最流行的虚拟机。默认情况下,我们的大多数讨论也基于HotSpot。

作者简介:霍利斯,一个对编码有独特追求的人,目前是阿里巴巴的技术专家,个人技术博客,以及拥有数千万在线阅读量的技术文章的合着者。回到搜狐看更多“负责任的编辑”:

-