专栏名称: HollisChuang
Developer
目录
相关文章推荐
51好读  ›  专栏  ›  HollisChuang

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

HollisChuang  · 掘金  ·  · 2020-03-10 02:04

正文

阅读 443

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

Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解。可以说,关于JVM的相关知识,基本是每个Java开发者必学的知识点,也是面试的时候必考的知识点。

在JVM的内存结构中,比较常见的两个区域就是堆内存和栈内存(如无特指,本文提到的栈均指的是虚拟机栈),关于堆和栈的区别,很多开发者也是如数家珍,有很多书籍,或者网上的文章大概都是这样介绍的:

1、堆是线程共享的内存区域,栈是线程独享的内存区域。

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

但是,作者可以很负责任的告诉大家, 以上两个结论均不是完全正确的。

本文首先带大家了解一下为什么我会说“堆是线程共享的内存区域,栈是线程独享的内存区域。”这句话并不完全正确!?关于JVM内存结构的相关知识,大家可以阅读 JVM内存结构 VS Java内存模型 VS Java对象模型 万万没想到,JVM内存结构的面试题可以问的这么难? 等文章。

在开始进入正题之前,请允许我问一个和这个问题看似没有任何关系的问题: Java对象的内存分配过程是如何保证线程安全的?

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

我们知道,Java是一门面向对象的语言,我们在Java中使用的对象都需要被创建出来,在Java中,创建一个对象的方法有很多种,但是无论如何,对象在创建过程中,都需要进行内存分配。

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

但是,因为堆是全局共享的,因此在同一时间,可能有多个线程在堆上申请空间,那么,在并发场景中,如果两个线程先后把对象引用指向了同一个内存区域,怎么办。

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

而Java对象的分配是Java中的高频操作,所有,人们想到另外一个办法来提升效率。这里我们重点说一个HotSpot虚拟机的方案:

每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块”私有”内存中分配,当这部分区域用完之后,再分配新的”私有”内存。

这种方案被称之为TLAB分配,即Thread Local Allocation Buffer。这部分Buffer是从堆中划分出来的,但是是本地线程独享的。

什么是TLAB

TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。

注意到上面的描述中"线程专属"、"只给当前线程使用"、"每个线程单独拥有"的描述了吗?

所以说,因为有了TLAB技术,堆内存并不是完完全全的线程共享,其eden区域中还是有一部分空间是分配给线程独享的。

这里值得注意的是,我们说TLAB是线程独享的,但是只是在“分配”这个动作上是线程独占的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。

也就是说,虽然每个线程在初始化时都会去堆内存中申请一块TLAB,并不是说这个TLAB区域的内存其他线程就完全无法访问了,其他线程的读取还是可以的,只不过无法在这个区域中分配内存而已。







请到「今天看啥」查看全文