专栏首页无敌码农一张图看懂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 条评论
登录 后参与评论

相关文章

  • 27款阿里巴巴实用且超神的Java开源项目

    Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring C...

    格姗知识圈
  • SpringBoot2.0 基础案例(10):整合Mybatis框架,集成分页助手插件

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集...

    知了一笑
  • 还在使用SimpleDateFormat?你的项目崩没?

    日常开发中,我们经常需要使用时间相关类,说到时间相关类,想必大家对SimpleDateFormat并不陌生。主要是用它进行时间的格式化输出和解析,挺方便快捷的,...

    格姗知识圈
  • 你真的了解 lambda 吗(纠错篇)?

    本文链接:http://www.cmlanche.com/2018/07/22/lambda用法与源码分析/

    用户1516716
  • Java虚拟机详解(三)------垃圾回收

      如果对C++这门语言熟悉的人,再来看Java,就会发现这两者对垃圾(内存)回收的策略有很大的不同。

    IT可乐
  • 精英程序员跟普通程序员区别在哪里?应该如何针对性的提高自己?

    正常来讲程序员之间的差异,主要还是解决问题的能力,一个好的程序框架不但可以兼容性强而且长时间运行还能非常的稳定,后续即使增加很多的功能也能不出大的问题,如果是普...

    程序员互动联盟
  • Spring Boot配置文件详解

    Spring Boot提供了两种常用的配置文件,分别是properties文件和yml文件。他们的作用都是修改Spring Boot自动配置的默认值。

    格姗知识圈
  • 如何在 IDEA 使用Debug 图文教程

    Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。通常我们也可以启用De...

    用户1516716
  • SpringBoot2.0 基础案例(02):配置Log4j2,实现不同环境日志打印

    日志打印是了解Web项目运行的最直接方式,所以在项目开发中是需要首先搭建好的环境。

    知了一笑
  • java自学出来的怎么找工作?

    一般来讲如果通过自学编程顺利找到工作的话,那么后劲一定都会非常的强劲,为什么通过自学编程找到工作的一般在公司做的还可以,作为一个从事编程行业十几年的老码农,对于...

    程序员互动联盟

扫码关注云+社区

领取腾讯云代金券