一张图看懂JVM

2019-07-20  本文已影响0人  洛的俠

在上一版文章发出后,作者收到了很多朋友的反馈,有反馈图片不清晰的,也有反馈说内存部分画的不是太细、缺少必要的文字描述,在这里小码农要向这些朋友表示抱歉,同时也向这些朋友表示感谢,正是因为有了你们的鞭策,小码农才有了持续学习的动力。

所以,这两天小码阅读了更为详细的资料,并对之前的内容进行了更为细化的梳理,希望这次能让大家对JVM相关的知识点有更加深刻的理解,也欢迎大家多多批评指正。

JVM结构示意图

image

JVM总体概述

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

JVM内存概述

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

image

需要说明的是,堆内存是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提供了同步机制,确保各个线程按照一定的机制同一时刻只能运行一个线程更新主内存的值。

具体逻辑示意图如下:

image

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

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

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

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

JVM参数总结

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

image
上一篇 下一篇

猜你喜欢

热点阅读