JVM结构
一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。
JVM 在执行 Java 程序的时候会把对应的物理内 存划分成不同的内存区域,每一个区域都存放着不 同的数据,也有不同的创建与销毁时机,
程序计数器
程序计数器在 JVM 中所起的作用就是用于存放当前线程接下来将要执行的字节码指令、分支、循环、跳转、异常处理等信息 。在任何时候,一个处理器只执行其中一个线程中的指令,为了能够在CPU时间片轮转切换上下文之后顺利回到正确的执行位置,每条线程都需要具有一个独立的程序计数器,各个线程之间互相不影响,因此JVM将此块内存区域设计成了线程私有的
虚拟机栈/线程栈
Java 虚拟机栈也是线程私有的,它的生命周期与线程相同,是在 NM 运行时所创建的,在线程 中,方法在执行的时候都会创建一个名为战帧( stack frame)的数据结构,主要用于存放局 部变量表、操作枝、动态链接、方法出口等信息
每一个线程在创建的时候, JVM 都会为其创建对应的虚拟机栈,虚拟机栈的大小可以通过-xss来配置,方法的调用是枝帧被压人和弹出的过程,通过图 2-4可以看出,同等的 虚拟机械如果局部 变量 表等占用 内存越小则可被压入的战帧就会越多,反之则可被压人的 技帧就会越少,一般将钱帧内存的大小称为宽度,而枝帧的数量则称为虚拟机栈的深度 。
本地方法栈
Java 中提供了调用本地方法的接口( Java Native Interface),也就是 CIC++ 程序,在线程的执行过程中,经常会碰到调用 JNI方法的情况,比如网络通信、文件操作的底层,甚 至是String的intern等都是JNJ方法, JVM为本地方法所划分的内存区域便是本地方法拢, 这块内存区域其自由度非常高,完全靠不同的 JVM 厂商来实现, Java 虚拟机规范并未给出 强制的规定,同样它也是线程私有的内存区域 。
堆内存
堆内存是 JVM 中最大的一块内存区域,被所有的线程所共享, Java 在运行期间创建的所有对象几乎都存放在该内存区域,该内存区域也是垃圾回收器重点照顾的区域,因此有 些时候堆内存被称为“ GC堆” 。
方法区
方法区也是被多个线程所共享 的内存区域,他主要用于存储已经被虚拟 机加 载的类信息、常量、静态、变量、即时编译器( JIT)编译后的代码等数据,虽然在 Java 虚拟机规范中,将堆内存划分为堆内存 的一个逻辑分区,但是它还是经常被称为“非堆”,有时候也被称为“持久代”,主要是站 在垃圾回收器的角度进行划分,但是这种叫法比较欠妥,在 Hotspot J\巾f 中,方法区还会 被细划分为持久代和代码缓存区,代码缓存区主要用于存储编译后的本地代码(和硬件相 关)以及 JIT (Just In Time)编译器生成的代码,当然不同的 JVM 会有不同的实现 。
可见性
有序性
导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化。
volatile
声明一个 volatile 变量 ,告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。
volatile int x = 0
Happens-Before 规则
表达的函数是前面一个操作的结果对后续操作是可见的。
Happens-Before 的语义是一种因果关系。在现实世界里,如果 A 事件是导致 B 事件的起因,那么 A 事件一定是先于(Happens-Before)B 事件发生的,这个就是 Happens-Before 语义的现实理解。
Happens-Before 约束了编译器的优化行为,尽管允许编译器优化,但是要求编译器优化后一定遵守 Happens-Before 规则。
1. 程序的顺序性规则
指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。
2. volatile 变量规则
指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
3. 传递性
A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
4. 管程中锁的规则
对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
5. 线程 start() 规则
线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
6.线程 join() 规则
指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。