JVM内存区域与多线程

Java并发的机制的背后是Java虚拟机(JVM)的工作机制,本文从几个关于并发和多线程的疑问开始,引出Java内存区域的介绍,希望能帮助大家更好的理解Java并发机制。

1. 线程创建和切换的代价——JVM的内存区域

在《从任务到线程:Java结构化并发应用程序》和《尝试Java加锁新思路:原子变量和非阻塞同步算法》中,曾经分别介绍过,创建线程和线程间切换对于性能和资源的消耗是不容忽视的,无限制地创建线程会消耗过多的内存资源并不可取,过多的线程间上下文切换也会降低多线程并发的性能。但是线程创建和切换的代价到底是怎么产生的呢?这就不得不提到Java的运行时数据区了。

1.1 JVM运行时数据区

JVM运行时数据区

根据《Java虚拟机规范》 JVM 将所管理的内存区域划分为 Method Area方法区),Heap),Program Counter Register程序计数器), VM Stack(虚拟机栈),Native Method Stack本地方法栈),其中Method Area和Heap是线程共享的,VM Stack,Native Method Stack 和Program Counter Register是线程隔离的。

如果读者对于JVM运行时数据区不是很了解,由于篇幅有限,请参看JVM初探 -JVM内存模型浅析Java虚拟机结构与机制,这里不再展开说明,只提供一份思维导图,帮助大家梳理内容:

Java内存区域.png

概括地说来,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程,就为其分配一个Program Counter Register(程序计数器), VM Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。

1.2 线程创建的内存代价

每当有线程被创建的时候,JVM就需要为其在内存中分配虚拟机栈和本地方法栈来记录调用方法的内容,分配程序计数器记录指令执行的位置,这样的内存消耗就是创建线程的内存代价。

内存作为有限的资源,如果JVM创建了过多的线程,必然会导致资源的耗尽。因此,使用Executor架构复用线程可以节省内存资源,是十分必要的。

1.3 线程切换的性能代价

JVM的并发是通过线程切换并分配时间片执行来实现的. 在任何一个时刻, 一个处理器内核只会执行一条线程中的指令。因此, 为了线程切换后能恢复到正确的执行位置, JVM需要先保存被挂起线程的上下文环境:将线程执行位置保存到程序计数器中,将调用方法的信息保存在栈中;同时将待执行线程的程序计数器和栈中的信息写入到处理器中,完成线程的上下文切换。维护线程隔离数据区中的内容在处理器中的导入导出,就是线程切换的性能代价。

控制线程上下文切换次数的方法有很多:

  1. 使用基于CAS的非拥塞算法,详见尝试Java加锁新思路:原子变量和非阻塞同步算法
  2. 无锁并发编程,尽量使用线程封闭(ThreadLocal)或者不变量,而不是用锁,详见对象共享:Java并发环境中的烦心事
  3. 使用线程池+等待队列的方式,控制线程数目,详见从任务到线程:Java结构化并发应用程序

2. 对象访问的定位 VS 内存可见性

根据JVM运行时的内存模式,在一个方法中使用某个变量,JVM要现在栈中找到该变量的引用,然后通过引用找到该对象在堆中保存到实例数据,如下图所示:


对象访问的定位

但是这个过程为什么会出现多线程间的内存可见性的问题呢?

这个过程从单个线程的角度来说,是没有问题的,线程可以找到自己需要的变量,但是得到的变量不一定是最新的,这是由于主存和缓存内容不一致造成的。

JVM不光有内存区域的划分,还有内存模式(JMM)来控制Java线程见的通信,其决定了一个线程的共享变量的写入合适对另一个线程可见。在Java中通过多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域主存(main memory),而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理(操作码+操作数),数据写入时,先被写入到工作缓存中,JMM选择合适的时机将其同步到主存中,以此提高访问效率。


JMM中的主存和缓存

工作缓存其实是一个抽象的概念,Java的内存分区中并没有专门的一块区域叫线程的工作缓存,其实际上是缓存、对读写缓冲区、寄存器以及其他硬件和编译器优化的统称。

因此虚拟栈到Java堆中寻找实例数据和JMM并不矛盾,二者是JVM不通角度的阐述。

3. 对象的创建——静态区域加载的多线程安全性

为了保证多线程安全性,一些必要的操作都需要加锁来保证其原子性和可见性,但是类中静态区的代码是不需要加锁就能保证多线程安全性。这是因为什么呢?

答案在于JVM类加载过程的保护机制。和普通类的实例被分配在Java堆上不同,类的静态属性都保存在方法区,其创建收到类加载过程的影响。


方法区,又名非堆

类的加载过程大题分为:加载(Loading),连接(Linking),初始化(Initialization),使用(Using)和卸载(UnLoading)五个步骤。这里和静态属性有关的主要是连接和初始化:在连接步骤的准备阶段,静态属性会分配内存;在初始化步骤,JVM会生成一个特别的方法——<clinit>方法来专门执行静态代码块和静态变量初始化。

<clinit>方法执行的过程中,JVM会对类加锁,保证在多线程环境下,只有一个线程能成功执行<clinit>方法,其他线程都将被拥塞,并且<clinit>方法只能被执行一次,被拥塞的线程被唤醒之后也不会再去执行<clinit>方法。如果类有继承关系,JVM还会保证父类的<clinit>方法将先于子类的<clinit>方法执行。

由此可见,静态代码块的多线程安全性是由JVM为其加锁实现的,这也是延迟初始化占位类模式的安全性基础,详见《从Java内存模型角度理解安全初始化》。

参考文献

  1. 深入理解JVM之JVM内存区域与内存分配
  2. 深入理解JVM—JVM内存模型
  3. JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )
  4. Java常量池详解之一道比较蛋疼的面试题
  5. JVM中锁优化简介
  6. JVM初探 -JVM内存模型
  7. 浅析Java虚拟机结构与机制
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,528评论 3 83
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,735评论 14 507
  • JVM内存模型Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: ...
    光剑书架上的书阅读 2,477评论 2 26
  • 在一个阳光明媚的上午,我到外公家去玩。 离外公家的不远处是一些低低矮矮的山丘,山丘上是一片片的橘子林,这里是我和哥...
    翱蓝阅读 975评论 2 1