堆是被所有线程共享的一块内存区域。在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例
Java中几乎所有的对象实例都在这里分配内存。方法区与堆一样,也是各个线程共享的一块内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
光看文字,会让我们觉得很抽象。如下图:
CPU切换
),可以发生在任何一条CPU指令执行完成后;例如:n++
)。n++
编译后,被编译CPU
执行的指令。CPU
的缓存导致的,多核CPU均有各自的缓存,这些缓存均要与内存进行同步。重排序
;并发编程需要处理两个关键问题,即线程之间如何通信和同步。
共有两种并发编程模型:共享内存模型、消息传递模型,Java采用的是前者。
JMM
JMM 是
Java Memory Model
的缩写,Java线程之间的通信由 JMM 控制,即JMM决定一个线程对共享变量的写入何时对另一个线程可见
。JMM定义了线程和主内存之间的抽象关系,通过控制主内存与每个本地内存(抽象概念)之间的交互,JMM为Java程序员提供了内存可见性
的保证。
为了
提高性能
,编译器和处理器常常会对指令做重排序。重排序有3种类型,其中后2种都是处理器重排序。这些重排序可能会导致多线程程序出现内存可见性问题。
参考下图,虽然处理器执行的顺序是A1->A2,但是从内存角度来看,实际发生的顺序是A2->A1。这里的关键是,由于写缓冲区仅对自己的处理器可见,它会导致处理器执行内存操作的顺序可能会与实际的操作执行顺序不一致。由于现代的处理器都会使用写缓冲区,因此它们都会允许对写 - 读操作执行重排序。
对于编译器,
JMM
的编译器重排序规则会禁止特定类型的编译器重排序(比如volatile
)。对于处理器重排序,
JMM
的处理器重排序规则会要求编译器在生成指令序列时,插入特定类型的内存屏障(Memcry Barries /Memory Fence)指令
,通过内存屏障指令来禁止特定类型的处理器重排序。
由于常见的处理器内存模型比JMM
要弱, Java编译器在生成字节码时,会在执行指令序列的适当位置
插入内存屏障来限制处理器的重排序
。同时,由于各种处理器内存模型的强弱不同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM
在不同的处理器中需要插入的内存屏障的数量和种类也不同。
CPU内存屏障:
Java内存屏障
public final class Unsafe {
public native void loadFerice();// LoadLoad + LoadStore
public native void storeFence();// StoreStore + LoadStore
public native void fullFence(); // loadFence() + storeFence() + StoreLoad
}
JMM使用
happens-before
规则来阐述操作之间的内存可见性
,以及什么时候不能重排序。在JMM中。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before
关系。换个角度来说.如果A happens-before B,则意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性。其中,前4条规则与程序员密切相关。
volatile
变量的读,总是能看到对这个volatile
变量最后的写入;volatile
变量的读/写具有原子性,但类似vclatile++
这种复合操作不具有原子性。volatile
变量时,JMM
会把该线程本地内存中的共享变量的值刷新到主内存;volatile
变量时,JMM
会把该线程本地内存置为无效,使其从主内存中读取共享变量。为了实现
volatile
的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障
来禁止特定类型的处理器重排序。内存屏障插入策略非常保守,但它可以保证在任意处理器平台,任意的程序中都能得到正确的volatile
内存语义。
volatile
仅仅保证对单个volatile
变量的读/写具有原子性
,而锁的互斥执行的特性可以确保对整个临界区代码
的执行具有原子性。在功能上锁比volatile更强大,在可伸缩性和执行性能上volatile
更有优势。
CAS + Mark Word
实现。存在锁升级的情况;CAS + volatile
实现。存在锁降级的情况核心是AQS 。