不积跬步,无以至千里;不积小流,无以成江海
Java并发是基于JMMJava内存模型构建于内核线程之上的使用共享内存的并发模型。JMM起着承上启下的不可或缺的作用,承上关联内核线程和Java线程,启下利用自身规范,指导实现,解决并发中存在的问题。
Java内存结构VSJava内存模型
Java是一门面向对象的高级语言,有诸多概念。有的概念从名称上看来比较相似,比如Java内存结构和Java内存模型,但实际上表示的意义却大相径庭,在学习过程中也比较容易混淆,有时候搜索的二手资料中也可能对其混为一谈。我们先从整体上概括一下二者的主要作用:
- Java内存结构和JVM运行时数据区域有关
- Java内存模型和Java的并发编程有关
可以看出两者概念上是有很大区别的。下面先来介绍以下Java的内存结构。
Java内存结构
我们都知道,Java 代码是要运行在虚拟机上的,而虚拟机在执行 Java 程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。在《Java 虚拟机规范(Java SE 8)》中描述了 JVM 运行时内存区域结构可分为以下 6 个区。
*堆区(Heap) *:堆是存储类实例和数组的,通常是内存中最大的一块。实例很好理解,比如 new Object() 就会生成一个实例;而数组也是保存在堆上面的,因为在 Java 中,数组也是对象。
*虚拟机栈(Java Virtual Machine Stacks) *:它保存局部变量和部分结果,并在方法调用和返回中起作用。
*方法区(Method Area) *:它存储每个类的结构,例如运行时的常量池、字段和方法数据,以及方法和构造函数的代码,包括用于类初始化以及接口初始化的特殊方法
*本地方法栈(Native method Stacks) *:与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的 Java 方法服务,而本地方法栈则是为 Native 方法服务
*程序计数器(The PC Register) *:是最小的一块内存区域,它的作用通常是保存当前正在执行的 JVM 指令地址
*运行时常量池(Run Time Constant Pool) *:是方法区的一部分,包含多种常量,范围从编译时已知的数字到必须在运行时解析的方法和字段引用。
注意,以上是 Java 虚拟机规范,不同的虚拟机实现会各有不同,一般会遵守规范。
这里总结一下,JVM 内存结构是由 Java 虚拟机规范定义的,描述的是在 Java 程序执行过程中,由 JVM 管理的不同数据区域,各个区域有其特定的功能。官方的规范地址。
为什么需要JMM
要理解为什么需要JMM,我们先从简单的HelloWorld程序的运行开始讲起。
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello world");
}
}
任何源代码,必须转换为CPU指令才能执行。转换方式有两种,一种是解释型语言如python逐行翻译,另一种是编译型如C,C++提前编译为机器指令。Java是编译型语言,但是他不是直接编译为机器指令,为了实现”Write once,run anywhere“的理念,Java采用开发特定平台虚拟机,然后Java源代码编译为针对虚拟机的通用二进制格式。HelloWorld运行大致流程为:
- 最开始,我们编写的 Java 代码,是 *.java 文件;
- 在编译(包含词法分析、语义分析等步骤)后,在刚才的 .java 文件之外,会多出一个新的 Java 字节码文件(.class);
- JVM 会分析刚才生成的字节码文件(*.class),并根据平台等因素,把字节码文件转化为具体平台上的机器指令;
- 机器指令则可以直接在 CPU 上运行,也就是最终的程序执行。
在更早期的语言中,其实是不存在内存模型的概念的。
所以程序最终执行的效果会依赖于具体的处理器,而不同的处理器的规则又不一样,不同的处理器之间可能差异很大,因此同样的一段代码,可能在处理器 A 上运行正常,而在处理器 B 上运行的结果却不一致。同理,在没有 JMM 之前,不同的 JVM 的实现,也会带来不一样的“翻译”结果。
所以 Java 非常需要一个标准,来让 Java 开发者、编译器工程师和 JVM 工程师能够达成一致。达成一致后,我们就可以很清楚的知道什么样的代码最终可以达到什么样的运行效果,让多线程运行结果可以预期,这个标准就是 JMM,这就是需要 JMM 的原因。如果不加以规范,那么同样的 Java 代码,完全可能产生不一样的执行效果,那是不可接受的,这也违背了 Java “书写一次、到处运行”的特点。
JMM及其解决的问题
JMM 是和多线程相关的一组规范,需要各个 JVM 的实现来遵守 JMM 规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。这样一来,即便同一个程序在不同的虚拟机上运行,得到的程序结果也是一致的。
因此,JMM 与处理器、缓存、并发、编译器有关。它解决了 CPU 多级缓存、处理器优化、指令重排等导致的结果不可预期的问题。
我们在并发编程中使用了各种同步工具和关键字,包括 volatile、synchronized、Lock 等,其实它们的原理都涉及 JMM。正是 JMM 的参与和帮忙,才让各个同步工具和关键字能够发挥作用,帮我们开发出并发安全的程序。
JMM 里最重要 3 点内容,分别是:重排序、原子性、内存可见性 , 核心内容包括