Java 内存模型

java 的内存模型

java memory module

高速缓存解决了处理器与内存的速度矛盾,但是引入了一个新的问题:缓存一致性(Cache coherence).

java 内存模型的主要目的是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量。

  • Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(与cache类比)

    线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写内存中的变量。

    Java 线程 <——-> 工作内存 <———> sava和load操作 <——-> 主内存

主内存和工作内存之间的具体交互协议

Java 内存模型定义了一下八种操作:

  1. lock : ——> 主内存变量, 把一个变量标识为一条线程独占状态
  2. Unlock : ——> 主内存变量, 释放
  3. read : ——> 主内存变量, 把一个变量值从内存传送到线程的工作内存 ,便于load 操作
  4. load : ——> 工作内存的变量,把read的变量值放入到工作内存的变量副本中
  5. use : ——> 工作内存的变量,工作内存的变量值—> 执行引擎
  6. assign : ——> 工作内存的变量, 把从执行引擎得到的值——> 赋给工作内存的变量
  7. store : ——> 工作内存的变量, 传给主内存,便于后面的write操作
  8. write : ——> 主内存的变量,把从工作内存的传过来的变量进行write进主内存

Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行

重排序

源代码——> 编译器优化重排序 ——> 指令级并行重排序 ——-> 内存系统重排序 ——> 最终执行的指令序列

为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序

Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种:

同步机制 :

volatile , synchronized, final

  1. 线程内存访问机制

  2. volatile 关键字

    volatile修饰的变量,线程在每次使用变量的时候,都会读取修改后的最新的值.
    对变量的写操作,不依赖于当前值
    不能和其他变量同时出现在一个表达式中
    volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行

  3. synchronized关键字

    线程同步的内部机制,通过加锁的方式保证线程的可见性和互斥性,
    即一个线程的执行结果可以被另一个线程所看到且在同一时间只能有一个线程执行被保护的代码块

  4. final 关键字
    与前面的锁和volatile相比较,对final域的读和写更像是普通的变量访问

    • 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
    • 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

上面的两条规则其实可分为1.写final域的重排序规则 :

  1. 读final域的重排序规则
  1. 写final域的重排序规则

    • JMM禁止编译器把final域的写重排序到构造函数之外
    • 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障(内存屏障)
      这个屏障禁止处理器把final域的写重排序到构造函数之外。
  • 读final域的重排序规则

    • 在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作
      仅针对与处理器(重排序的后两种),
      编译器会在读final域操作的前面插入一个LoadLoad屏障

volatile与synchronized

  1. volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取。
  2. synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
  3. volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
  4. volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性
  5. volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
  6. volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.

Java最大线程数

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

MaxProcessMemory 指的是一个进程的最大内存

JVMMemory JVM内存

ReservedOsMemory 保留的操作系统内存

ThreadStackSize 线程栈的大小