这两个概念估计有很多人会混淆,它们均可以说是 JVM 规范的一部分,但真不是一回事!它们描述和解决的是不一样问题,简单来讲,程序员
JVM 是什么呢?它屏蔽了底层架构的差别性,是 Java 跨平台的依据,也是每一个 Java 程序员必须了解的一部分。编程
Java Virtual Machine(JVM) 是一种抽象的计算机,基于堆栈架构,它有本身的指令集和内存管理。它加载 class 文件,分析、解释并执行字节码。基本结构以下:数组
如上图所示,JVM 主要分为三个子系统:类加载器、运行时数据区和执行引擎。缓存
它主要功能是处理类的动态加载,还有连接,而且在第一次引用类时进行初始化。数据结构
Loading - 加载,顾名思义,用于加载类,它有三种类加载器,根据双亲委托模型,从不一样路径进行加载:多线程
Linking - 连接,动态连接到运行时所需的资源,分为三步:架构
Initialization - 类初始化,类加载的最后阶段,这里对静态变量进行赋值,并执行静态块。(注意区分对象初始化)并发
它约定了在运行时程序代码的数据好比变量、参数等等的存储位置,主要包含如下几部分:app
运行时数据区存储着要执行的字节码,执行引擎将会读取并逐个执行。函数
Interpreter - 解释器,它对字节码的解释很快,但执行慢,有个缺点是,当方法被屡次调用时,每次都须要从新解释。
JIT Compiler- JIT编译器, 解决了解释器的缺点,仍使用解释器来转换字节代码,但发现有代码重复执行时,会使用 JIT 编译器,将整个字节码编译成本地代码,将本地代码用于重复调用,从而提升系统的性能,有如下几部分组成:
Garbage Collector- 垃圾收集器,收集和删除未引用的对象。
另外,还包括执行引擎所需的本地库*(Native Method Libraries)和与其交互的 JNI 接口(Java Native Interface)*。
如今来看下 Java 内存模型和 JVM 内存结构有何不一样。
常说的 JVM 内存结构指的就是上文提交到运行时数据区,其中堆、方法区被线程共享,程序计数器、栈、运行时常量池被线程独享。
它描述的是,在运行时,字节码和代码数据存储的位置。
先抛开 Java 不说,先来看下内存模型是什么?维基百科中的定义:
In computing, a memory model describes the interactions of threads through memory and their shared use of the data.
意思就是,在计算中,内存模型描述了多线程如何正确的经过内存进行交互和使用共享数据。换句话说,内存模型约束了处理器对内存的读写。
CPU 和内存之间一般会存在一层或多层高速缓存,这对单处理器可能没问题,但在多处理器系统中,可能就会出现缓存一致性问题,也就是当两个处理器(线程)同时读取相同内存位置会发生什么?什么状况下会看到相同的值?
缓存一致性问题,在并发编程中,又被称做可见性问题。内存模型在处理器级别,为处理器彼此之间对内存写入结果的可见性,定义了充分必要条件:
大多数处理器不会限制内存操做的顺序,多线程在执行时可能会出现让人困惑和违背直觉的结果。这是由于 CPU 为了充分利用不一样类型存储器(寄存器、高速缓存、主存)的总线带宽,会将内存操做从新排序,以无序执行,这个动做称为内存排序或指令重排序。
重排序,也被称为编译器优化和处理器优化,由于它既能够发生在编译期间,也能够发生在 CPU 运行时。为了保证多线程的有序性,须要使用内存屏障禁止重排序。
因此说,内存模型就是在硬件层面描述了使用内存屏障(刷新缓存或禁用指令重排序)解决多线程编程中的可见性和有序性的问题。
Java 内存模型(下文简称 JMM)就是在底层处理器内存模型的基础上,定义本身的多线程语义。它明确指定了一组排序规则,来保证线程间的可见性。
这一组规则被称为 Happens-Before, JMM 规定,要想保证 B 操做可以看到 A 操做的结果(不管它们是否在同一个线程),那么 A 和 B 之间必须知足 Happens-Before 关系:
怎么理解 happens-before 呢?若是按字面意思,好比第二个规则,线程(不论是不是同一个)的解锁动做发生在锁定以前?这明显不对。happens-before 也是为了保证可见性,好比那个解锁和加锁的动做,能够这样理解,线程1释放锁退出同步块,线程2加锁进入同步块,那么线程2就能看见线程1对共享对象修改的结果。
Java 提供了几种语言结构,包括 volatile, final 和 synchronized, 它们旨在帮助程序员向编译器描述程序的并发要求,其中:
编译器在遇到这些关键字时,会插入相应的内存屏障,保证语义的正确性。
有一点须要注意的是,synchronized 不保证同步块内的代码禁止重排序,由于它经过锁保证同一时刻只有一个线程访问同步块(或临界区),也就是说同步块的代码只需知足 as-if-serial 语义 - 只要单线程的执行结果不改变,能够进行重排序。
因此说,Java 内存模型描述的是多线程对共享内存修改后彼此之间的可见性,另外,还确保正确同步的 Java 代码能够在不一样体系结构的处理器上正确运行。
它们之间的关系能够这样来个总结,实现一个 JVM 要知足内存结构描述的组成部分,设计如何执行多个线程的时候,要知足Java 内存模型约定的多线程语义。
搜索公众号「顿悟源码」获取更多源码分析和造的轮子。