美文网首页
android 多线程 — java 内存模型

android 多线程 — java 内存模型

作者: 前行的乌龟 | 来源:发表于2018-05-31 03:42 被阅读309次

    JAVA 的内存模型是很复杂的,其中不仅包括操作系统底层的设计,更是和硬件设备(CPU 结构)密切相关,了解清楚 JAVA 的内存模型对于我们后面的学习,尤其是同步有非常大的帮助,要不有些地方会理解不通的。

    JVM,JMM 概念


    在开头我们先来了解下2个概念:

    • JVM
      JVM (Java Virtual Machine) Java虚拟机模型。主要描述的是Java虚拟机内部的结构以及各个结构之间的关系,负责将 java 文件编译成 class 文件并运行 class 文件
    • JMM
      JMM (Java Memory Model) Java内存模型。定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,线程之间内存刷新的关系,是隶属于JVM的

    简单来说,JVM可以理解为 java 执行的一个操作系统,而操作系统的内存模型就是 JVM

    JAVA 内存模型


    历史:

    最初的 JAVA 内存模型不够好,存在很多的不足,所以在 JAVA 1.5 中,JAVA 内存模型的版本的进行了一次重大的更新与改进,并且在 JAVA 8 中仍然被使用。

    JMM 的内存模型图很复杂,咱来个简单点的,便于理解,对于多线程来说,咱们了解这么多也就差不多了,要是研究 GC 的话,还需要更详细的研究,比如老年区啥的


    JMM 内存模型图

    JAVA 的内存模型中主要分3块:

    • 堆内存(Java Heap)
      内存中最大的一块,是被所有线程共享的一块内存区域。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

    • 栈(Native Method Stacks)
      它是线程私有的,它的生命周期与线程相同。存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型),它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

    • 方法区(Method Area)
      被所有的线程共享,所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量都存在这 ,存储的都是在整个程序中永远唯一的元素

    • 常量池(方法区的一部分)
      JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中

    或者看下面的文章也可以,描述的更加清楚

    数据存储必须要明确的点:

    • 对象本身存储在 堆内存,但是对象引用存在 栈内存中
    • JAVA 的8种基本数据类型是存在 栈内存里面的,线程间传递基本数据类型传递的都不是数据本身,而是基本数据类型的副本,另一个线程对同一个基本数据类型的修改不会影响其他线程中的值,但是对象不同,对象传递的引用,因为堆内存是共享的,一个线程修改了堆内存中对象的数据,那么必然会影响其他线程中的值
    • 方法中生成的局部变量全部存储在 栈内存中,即便是方法中 new 了一个对象出来,这个对象本身也是存在栈内存中的,不会跑到堆内存中,方法结束,既会销毁相关数据
    • 基本数据类型的包装对象不被视为基本数据类型了,而是对象了,会存在堆内存中。

    一切如下面这2张图


    数据存储示意图_1 数据存储示意图_2

    这样看着应该好理解一些

    JAVA 内部的数据传递


    再来说下栈,栈是个统称,栈的内部是还可以再去细分的,看这个简单的图大家都能发现里面分好几块,要是看更详细的资料,大家会发现分的块还要多,不过这无助于我们理解多线程,我们只要记住栈是单个线程私有的,有多个线程在工作,那么内存中就会存在多个栈内存区域

    如下图:


    1234352-0a8474641ef704d5.png

    线程处理数据的顺序:

    • 这里指引用对象
    • 习惯上我们把 堆内存 称为主内存
    1. Thread 对象把要处理的对象从主内存中 copy 一份到当前线程所在的栈内存中
    2. 假如这个处理耗时2秒,那么2秒后更新栈内存中这个对象的数据副本
    3. 最后把栈内存中对象的数据副本再写回到主内存中,我们称之为:刷新到主内存

    看图逻辑会清晰一些


    线程处理数据

    线程处理数据的过程可能跟我们之前的想法有些出入,并不是直接操作堆内存中所在的数据,然后把数据自己 copy 了一份去处理,拿到结果再刷新堆内存中的数据。

    这就造成一个问题,有个时间差,单线程没什么,一切按顺序执行,那么多线程呢,多线程怎么办呢,要是 Thread1 在处理数据时,Thread2 也在同时处理数据,2者同时往主内存中刷新数据,那么主线程会刷新成哪个线程的数据啊。再者就算 Thread1 和 Thread2 前后往主内存中刷新数据,那么结果也是不多啊,Thread1 先执行,那么 Thread2 在操作同一个数据时应该是在 Thread1 的结果基础上的,现在变成了 2个线程都在同一个数上操作,即便是前后写入结果到主内存中,结果也不是我们预期的。

    举个例子,主内存有个对象,有个参数 money =1 ,Thread1 ++,Thread2 +2,那么 Thread1 和 Thread2 同时执行,那么 Thread1 和 Thread2 中 这个对象的 copy 副本中的 money 都是1 ,然后 Thread1 先写入数据到主内存,Thread2 紧随其后,此时 money 是 3,那么这个结果我们想要的吗,不是啊,我们想要的结果是 4 啊,我们想让 Thread1 先把 money 变成 2 ,然后Thread2 在 money =2 的基础上再加2 ,变成4

    多线程同时刷新数据

    这个例子搞懂了,多线程中我们面临的问题也就是很清晰了,怎么保持主内存数据和栈内存中数据副本的相同,那么这个就得靠 JAVA 多线程的同步机制了

    硬件层面内存模型


    上文书说到 java 的内存模型不光收到 JAVA 底层影响,更是受到硬件影响

    下图是一个简化的现代计算机硬件结构图:


    计算机硬件结构图

    现在的 CPU 都有多个核心,每个核心都有自己的寄存器和高速缓存器

    • 寄存器 register
      每个cpu都会有一系列的寄存器registers在cpu的内存中,而且这些寄存器是很重要的。cpu在寄存器上进行计算操作比在主内存中进行计算要快的多。这是因为cpu访问寄存器的速度比访问内存要快得多。

    • 高速缓存器 cache
      每个cpu也会有一个cpu的cache内存。这是因为cpu访问cache比访问内存的速度要快得多,但是却比访问的寄存器要慢一些,所以cache的速度是介于寄存器和内存的。一些cpu还有多级cache,比如(Level 1 and Level 2),但是这对于我们理解java内存模型关系不大,我们只需要cpu有三层内存结构,寄存器-cache-内存(RAM)

    CPU 在处理对象时,和线程一样会把对象 Copy 一份放到自己的 cache 中,然后先修改自己 cache 中的对象数据副本,然后再刷新回主内存中。

    我们可以把 CPU 的寄存器,高速缓存看成另一个线程中的栈内存,都是会产生数据同步问题的,这样就好理解了。

    Java内存模型和硬件内存模型的联系

    最后


    好了内存模型了解到这里就差不多了,我们知道了不管是线程中的栈内存空间还是 CPU 的寄存器和高速缓存都会操作从主内存的copy 的数据副本,因时间差和多线程,多 CPU 核心同时操作同一个对象数据,会产生数据错乱的数据同步问题。大家了解了多线程数据不同步产生的根本原因,那么本篇的内容就算是达到目的了,知道了为什么我们才好有的放矢不是。

    吐槽下本篇文章真是不好写,反复找了不好资料,尤其是找图很麻烦啊,反复看了很多资料,书也看了3本:《开发艺术探索》《进阶之光》《从小工到专家》,就怕说错了误导大家。这个知识点我也是了解的不是非常深入,有错误请大家指正,我第一时间修改。

    写的不好......想砸电脑......

    参考资料:


    相关文章

      网友评论

          本文标题:android 多线程 — java 内存模型

          本文链接:https://www.haomeiwen.com/subject/vtefsftx.html