Java内存模型

2021/03/20

前言

因为java程序是运行在jvm上面的,java对计算机内存的使用是需要通过java内存模型来调度和使用,经常可以看到有技术文章在接受,一般我也是当着兴趣去了解一下,没有想过很深入,直到我遇到了多线程…多线程真的是让人又爱又恨,可以解决很多问题的同时经常因为没使用好导致线上各种找都找不到原因的问题。其中很大程度一点就是没有理解java的内存模型,下面我从JVM的运行时数据区来谈谈我对jvm的理解

运行时数据区(JDK8)

8tkds0ol0e

上图是我改的(黄色共享,橙色每个线程独有)

这个元空间和永久代我实在不想吐槽,网上博客用一些网图文章抄来抄去,导致我自己去理解JVM的时候翻阅了很多资料,因为网上的文章太误导了,下面我理清一下jdk678运行时数据区的变化(如果对RDA不了解,先跳过这部分,回来再看这部分)。

  • JDK7以前方法区是永久代实现的,永久代里面存放字面量(interned strings)静态变量(class statics)以及类的加载信息
  • JDK7开始,方法区实现方式不变,但是里面存放的东西发生了变化,为了防止方法区OOM,所以把字面量和静态变量都转移到了java heap中。里面只存放类的加载信息,因为类在编译期就已经确定了,所以大小是固定的不用GC也没有OOM。
  • JDK8开始,方法区实现方式变了,变成元空间实现了,最大特点是元空间并不在java虚拟机内存中,而是使用本地内存。
  • ps:元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

然后我们介绍一下JDK8运行时数据区的一些组成

  • 元空间,刚刚已经介绍了,这个部分存放编译后的一些类字节码信息
  • java栈,里面存放的是每一个线程独有的数据,里面的基本单元是栈帧,每一个栈帧是一个方法,栈嘛,先进后出,把所有的方法都压入这个栈,然后挨个出栈。先进来的方法是在最底下。每一个栈帧里面都会维护一个局部变量表,因为局部变量是属于方法的,所以自己的方法有自己变量表(主要这个表如果是对象的话只存引用地址),操作数栈、方法出口等信息
  • 本地方法栈,和上面的栈很像,只不过是为本地方法服务(一些c\c++写的)
  • 程序计数器,既然有这么多栈,也就是有这么多线程,那我们知道线程之前是通过竞争时间片来执行的,上下文切换的时候那怎么知道现在是执行到这个栈的哪个栈帧,就是通过这个程序计数器来记录,这个也是每个线程独有的,它记录了当前执行的字节码指令地址,当cpu重新切回来它记忆读取然后继续执行
  • 堆,地方是并发噩梦,上面的栈其实每个线程独有一份,所以并发情况下他是安全的,但是堆是所有的线程都可以访问,大家在这里一起存放对象,一般这么理解,除了基本类型是会在栈上分配,其他的类型都是在堆上分配的,只不过每个线程有一个局部变量表来存储它在堆上的地址。堆是垃圾收集器管理的重要区域。

JMM

参照《深入理解Java虚拟机》的解释,主内存、工作内存与Java内存区域中的Java堆、栈、方法区等并不是同一个层次的内存划分。如果两者一定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中对象的实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。所以说首先要明白JMM和运行时数据区的区别。

Java 内存模型主要由以下三部分构成:1 个主内存、n 个线程、n 个工作内存(与线程一一对应),数据就在它们三者之间来回倒腾。那么怎么倒腾呢?靠的是 Java 提供给我们的 8 个原子操作:lockunlockreadloaduseassignstorewrite

一个变量在JMM中流转的顺序是

   【主内存】 -> read -> load -> 【工作内存】 -> use -> 【Java线程】
   【Java线程】-> assign -> 【工作内存】 -> store -> write -> 【主内存】

每一个线程都拥有自己的线程栈,这个栈里面包含了这个线程调用的相关信息和内容。一个线程只能访问自己的栈,并且一个线程创建的本地变量是对其他线程不可见的,只有自己可见,这就意味着两个相同的线程执行同样的代码。它们会在自己的栈中执行代码,会拷贝出两份本地变量。

计算机硬件内存架构

img

  • cpu, cpu是计算大脑,大部分的数据都要到计算中去计算,数据都是从下面读取上来的。
  • 寄存器,CPU在寄存器上执行操作的速度远大于在主存上执行的速度。这是因为CPU访问寄存器的速度远大于主存。
  • 高速缓存cache, 一般都是三级缓存,读取速率一个比一个块,但是空间都比较小,CPU访问缓存层的速度快于访问主存的速度,但通常比访问内部寄存器的速度还要慢一点。
  • 内存, 也叫主存,因为这里的容量是最大的,也是交互最多的,就是大家可以更换的内存条容量。

现在的计算机模式依然用的是冯·诺依曼模型,如上图cpu要读取数据计算,需要从内存拿数据,然后读取到缓存中,然后再读取到自己的寄存区里面。因为只有寄存器速度才能达到cpu的计算频率。通常情况下,当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当CPU需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

后言

值得注意的是,JMM是一个规范,是抽象出来的概念,不是说存在一块区域他就是JMM。可能有人会问这个本地内存它是哪一块区域,它不是固定的一块区域,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

这个其实是一个系列,这篇只说一些java内存模式和计算机内存模式,要深入的讲需要结合实际场景,后面会涉及到很多并发相关的内容,比如共享内存、volatlle、原子类之类的介绍。

引用

  1. http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
  2. http://www.duokan.com/reader/www/app.html?id=478661cfaf9c4c05860806e6ea087962
  3. http://itmyhome.com/java-concurrent-programming/java-concurrent-programming.pdf

(转载本站文章请注明作者和出处 没有气的汽水



┌┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┐
├ 文章已经完啦, 想要第一时间收到文章更新可以关注↓ ┤
└┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┘

Post Directory






下面是评论区,欢迎大家留言探讨或者指出错误哈