一张图看懂JVM
https://mp.weixin.qq.com/s?__biz=MzAxNjk4ODE4OQ==&mid=2247484432&idx=1&sn=381c98c49ffb813a9b9799e232a5a42c&chksm=9bed2562ac9aac74f6a3c5cc8f8c4a145e5bd9489eadd7ef12bc687239fa5ead497dac00ce6e&scene=0&key=c0940e0b2170699ab63621476b009383645cc10db9ce1ce7685af04b9b20dd771e838e4e1ffaa9ddb1c59193c5d686ba714c73cfd56311f55b001a680eacd79efd173308acf3eae64c7326dc30b06cd9&ascene=1&uin=MjgwMTEwNDQxNg%3D%3D&devicetype=Windows-QQBrowser&version=6103000b&lang=zh_CN&pass_ticket=7VimjYLmQAgyidy1vuYk8UcovgLF%2Fov0yGxcp%2B%2FloNe8nO%2B2veg9hLIgh4PfuU6V
在上一版文章发出后,作者收到了很多朋友的反馈,有反馈图片不清晰的,也有反馈说内存部分画的不是太细、缺少必要的文字描述,在这里小码农要向这些朋友表示抱歉,同时也向这些朋友表示感谢,正是因为有了你们的鞭策,小码农才有了持续学习的动力。
所以,这两天小码阅读了更为详细的资料,并对之前的内容进行了更为细化的梳理,希望这次能让大家对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开源工具比较完备,另一方面是很多人从事的是业务研发工作,时间久了难免会对基础知识有所遗忘。在上面的部分中还有一块垃圾回收的知识点没有总结到,基于篇幅的原因后面再单独给大家总结!谢谢你们的关注~
一张图看懂JVM的更多相关文章
- 一张图看懂JVM之垃圾回收算法详解
导读 ...
- 一张图看懂ANSYS17.0 流体 新功能与改进
一张图看懂ANSYS17.0 流体 新功能与改进 提交 我的留言 加载中 已留言 一张图看懂ANSYS17.0 流体 新功能与改进 原创2016-02-03ANSYS模拟在线模拟在线 模拟在线 ...
- 一张图看懂开源许可协议,开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别
一张图看懂开源许可协议,开源许可证GPL.BSD.MIT.Mozilla.Apache和LGPL的区别 首先借用有心人士的一张相当直观清晰的图来划分各种协议:开源许可证GPL.BSD.MIT.Mozi ...
- FUNMVP:几张图看懂区块链技术到底是什么?(转载)
几张图看懂区块链技术到底是什么? 本文转载自:http://www.cnblogs.com/behindman/p/8873191.html “区块链”的概念可以说是异常火爆,好像互联网金融峰会上没人 ...
- 4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程)
4张图看懂delphi 10生成ipa和在iPhone虚拟器上调试(教程) (2016-02-01 03:21:06) 转载▼ 标签: delphi ios delphi10 教程 编程 分类: 编程 ...
- 一张图看懂css的position里的relative和absolute的区别
position有以下属性:static.inherit.fixed.absolute.relative前三个好理解好区分:static:是默认状态,没有定位,元素出现在正常的流中(忽略 top, b ...
- [转帖]两张图看懂GDT、GDTR、LDT、LDTR的关系
两张图看懂GDT.GDTR.LDT.LDTR的关系 2018-06-09 18:13:53 Six_666A 阅读数 2044更多 分类专栏: 深入理解linux内核 转自:http://ju.o ...
- 一张图看懂Function和Object的关系及简述instanceof运算符
我在写一篇图解prototype和__proto__的区别时,搜资料搜到了一个有意思的现象,下面这两个运算返回的结果是一样的: Function instanceof Object;//true Ob ...
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
前言:一条线,竖着放,如果做不到精进至深,那就旋转90°,至少也图个幅度宽广. 通俗解释上面的胡言乱语:还没学会爬,就学起走了?! 继上篇<Nodejs学习笔记(二)——Eclipse中运行调试 ...
随机推荐
- [转帖]一键获取 所有连接过的wifi 密码
cmd 一键获取 所有连接过的wifi 密码 转帖来源: http://www.cnblogs.com/hookjoy/p/5537623.html for /f "skip=9 token ...
- Laravel技巧:使用load、with预加载 区别
1.使用load $posts = Post::all(); $posts->load('user'); 2.使用with $posts = Post::with('user')->all ...
- 你不知道的JavaScript——第一章:作用域是什么?
编译原理 JavaScript事实上是一门编译语言,但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植. 任何JavaScript代码片段在执行前都要进行编译(通常就在执行 ...
- Django的一些操作与视图函数
一 . Django的安装 pip install django==1.14.6 # 后面的数字是django的版本 二 . 通过命令行(cmd)来创建Django项目 1. 切换到保存项目的文件夹 ...
- linux audit审计(4)--audit的日志切分,以及与rsyslog的切分协同使用
audit的规则配置稍微不当,就会短时间内产生大量日志,所以这个规则配置一定要当心.当audit日志写满后,可以看到如下场景: -r-------- 1 root root 8388609 Mar 3 ...
- case when 空值判断
在对数据库进行查询时,遇到了一个问题:查询结果中的某一列需要判断另一列是否为空的来确定值,自然就想到了case when,于是写出了下面的SQL(其他部分省略): (case date when nu ...
- Web API 2 Entity Framework 使用 Procedure
Recently I worked on a project, which I started as code first and then I forced to switch to Databas ...
- python命令里运行正确但是pycharm里面运行就是报错的问题
这两天在学习爬虫,第一步就是 import scrapy class StackOverflowSpider(scrapy.Spider): 结果一直报错,说是scrapy没有spider这个方法,各 ...
- YUV格式与RGB格式
YUV420介绍: YUV420格式是指,每个像素都保留一个Y(亮度)分量,而在水平方向上,不是每行都取U和V分量,而是一行只取U分量,则其接着一行就只取V分量,以此重复(即4:2:0, 4:0:2, ...
- Zookeeper注册中心底层实现小记
内容摘自微信公众号,程序员小灰.推荐-ing Zookeeper的数据模型 Zookeeper的数据模型是什么样子呢?它很像数据结构当中的树,也很像文件系统的目录. 树是由节点所组成,Zookeepe ...