专栏首页无敌码农一张图看懂JVM(升级版)

一张图看懂JVM(升级版)

在上一版文章发出后,作者收到了很多朋友的反馈,有反馈图片不清晰的,也有反馈说内存部分画的不是太细、缺少必要的文字描述,在这里小码农要向这些朋友表示抱歉,同时也向这些朋友表示感谢,正是因为有了你们的鞭策,小码农才有了持续学习的动力。 所以,这两天小码阅读了更为详细的资料,并对之前的内容进行了更为细化的梳理,希望这次能让大家对JVM相关的知识点有更加深刻的理解,也欢迎大家多多批评指正。

JVM结构示意图

JVM总体概述

JVM总体上是由类装载子系统(ClassLoader)、运行时数据区、执行引擎、内存回收这四个部分组成。其中我们最为关注的运行时数据区,也就是JVM的内存部分则是由方法区(Method Area)、JAVA堆(Heap)、虚拟机栈(Stack)、程序计数器、本地方法栈这几部分组成;除此以外,在概念中还有一个直接内存的概念,事实上这部分内存并不属于虚拟机规范中定义的内存区域,但是因为在JDK1.4+后新加的NIO类,以及JDK1.8+后的Metaspace的关系,所以在讨论JVM时也经常会被放到一起讨论。

JVM内存概述

各内存部分的功能及具体组成部分,总结如下:

需要说明的是,堆内存是GC重点回收区域,其中分代回收机制将堆内存划分为年轻代、老年代两个区域,默认情况下年轻代占整个堆内存空间的1/3,而老年代则占2/3,可以通过“-XX:NewRatio”设置年轻代与老年代的比值,默认为2,表示比值年轻代与老年代的比值为“1:2”,在JVM调优时可根据应用实际情况进行调整。

而年轻代又分为Eden、Survivor0、Survivor1,这三个区域占整个新生代空间的比值为8:1:1,即Eden区占8/10,其他两个区域分别占1/10,可通过“-XX:SurvivorRatio”参数进行设置,默认值为8。

正确理解并发问题

在了解了JVM结构,特别是内存结构后,我们再说说并发问题产生的原因。在上面的内容中我们分析了Java堆、Java栈,知道Java堆存储的是对象,而Java栈内存是方法执行时所需要的局部变量,其中就包括堆中对象的引用,如果多个线程同时修改堆中同一引用对象的数据,就可能会产生并发问题,导致多个线程中某些线程得到的数据值与实际值不符,造成脏数据。

那么这种问题为什么会发生呢?

实际上,线程操作堆中共享对象数据时并不是直接操作对象所在的那块内存,这里称之为主内存;而是将对象拷贝到线程私有的工作内存中进行更新,完成后再将最新的数据值同步回主内存,而多个线程在同一时刻将一个对象的值改得七七八八,然后再同时同步给对象所在的内存区域,那么以谁更新的为准就成了问题了。

所以,为了防止这种情况出现,Java提供了同步机制,确保各个线程按照一定的机制同一时刻只能运行一个线程更新主内存的值。

具体逻辑示意图如下:

注意,这里所讲的主内存、工作内存与Java内存区域中的Java堆、栈内存、方法区等并不是同一个层次的内存划分。如果勉强类比,从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中对象实例数据部分,而工作内存则对应于虚拟机栈中使用的部分内存区域;从更低层次类比,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

而主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之间的实现细节,Java内存模型中定义了8种操作来完成。

而且还规定在执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

以上8种内存访问操作以及上述规则限定,再加上volatile的一些特殊规定以及final不可变特性,就已经完成确定了JAVA程序中那些内存访问操作在并发下是安全的!

JVM参数总结

为了方便大家对于JVM有关参数有一个参照,如下:

后记

读到这里,小编希望能够对大家温习基础知识起到一定的帮助,特别是从事Java开发工作时间并不长的朋友希望本文能对你们有所促进,因为根据作者的经验,有时候很多从事Java开发工作好几年的同学,都会对这些知识点产生模糊的认识,一方面是目前各类Java开源工具比较完备,另一方面是很多人从事的是业务研发工作,时间久了难免会对基础知识有所遗忘。在上面的部分中还有一块垃圾回收的知识点没有总结到,基于篇幅的原因后面再单独给大家总结!谢谢你们的关注~

—————END—————

本文分享自微信公众号 - 无敌码农(jiangqiaodege),作者:无敌码农

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-09-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 教你分析9种 OOM 常见原因及解决方案

    专栏地址:https://github.com/StabilityMan/StabilityGuide

    用户5927304
  • 一张图看懂JVM

    在应聘Java程序员时,经常会被问到JVM相关的知识点。而市面上讲解JVM原理及结构的书籍及资料,要么晦涩难懂,要么断章取义。那么有没有一张比较清晰的图能够将J...

    用户5927304
  • 如何彻底理解volatile关键字?

    最近面试,你又被volatile关键字虐了吗?这个问题,是不是问得有点扎心了!的确,有很多朋友反馈面试中在涉及考察Java并发编程知识的时候,经常会被问到vol...

    用户5927304
  • Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模...

    程序新视界
  • 不好意思,我和 Java 内存模型杠上了!

    Java内存模型是在硬件内存模型上的更高层的抽象,它屏蔽了各种硬件和操作系统访问的差异性,保证了Java程序在各种平台下对内存的访问都能达到一致的效果。

    zhisheng
  • JVM学习记录-Java内存模型(一)

    Java虚拟机规范中定义了一种Java的内存模型,即Java Memoory Model(简称JMM),用来实现让Java程序在各个平台下都能达到一致的内存访问...

    纪莫
  • 死磕 java同步系列之JMM(Java Memory Model)

    Java内存模型是在硬件内存模型上的更高层的抽象,它屏蔽了各种硬件和操作系统访问的差异性,保证了Java程序在各种平台下对内存的访问都能达到一致的效果。

    彤哥
  • Java内存模型

    多任务处理在现代计算机操作系统中几乎已经是一项必备的功能了。计算机cpu的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘I/O、网络通信或...

    栋先生
  • 嘿,你要的Java内存模型(JMM)来了!

    在单核计算机中,计算机中的CPU计算速度是非常快的,但是与计算机中的其它硬件(如IO、内存等)同CPU的速度比起来是相差甚远的,所以协调CPU和各个硬件之间的速...

    Simon郎
  • Apache Spark 内存管理详解(上)

    本文旨在梳理出Spark内存管理的脉络,抛砖引玉,引出读者对这个话题的深入探讨。本文中阐述的原理基于Spark 2.1版本,阅读本文需要读者有一定的Spark和...

    大数据技术架构

扫码关注云+社区

领取腾讯云代金券