JVM(四):Java内存模型(JMM)

硬件层的并发优化基础知识

  • 存储器的层次结构

  • 从CPU到各层所需要的时间

硬件层数据一致性协议

  • 多线程一致性的硬件层支持(老的CPU使用的)
  • Intel中的MESI-Cache一致性协议
  • 协议很多,Intel采用MESI(https://www.cnblogs.com/z00377750/p/9180644.html)
  • 现代CPU数据一致性实现 = 缓存锁(MESI…) + 总线锁
  • 读取缓存以cache line为基本单位,目前64bytes;
  • 伪共享:位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享问题;
    • 使用缓存行的对齐能够提高效率

    • 伪共享问题:JUC/c_028_FalseSharing

    • cache line的概念-缓存行对齐-伪共享

      • 验证示例代码
      package com.lele.juc.c_028_FalseSharing;
      import java.util.Random;public class T01_CacheLinePadding {private static class T {public volatile long x = 0L;}public static T[] arr = new T[2];static {arr[0] = new T();arr[1] = new T();}public static void main(String[] args) throws Exception {Thread t1 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[0].x = i;}});Thread t2 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[1].x = i;}});final long start = System.nanoTime();t1.start();t2.start();t1.join();t2.join();System.out.println((System.nanoTime() - start)/100_0000);}
      }
      
      package com.lele.juc.c_028_FalseSharing;public class T02_CacheLinePadding {private static class Padding {public volatile long p1, p2, p3, p4, p5, p6, p7;}private static class T extends Padding {public volatile long x = 0L;}public static T[] arr = new T[2];static {arr[0] = new T();arr[1] = new T();}public static void main(String[] args) throws Exception {Thread t1 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[0].x = i;}});Thread t2 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[1].x = i;}});final long start = System.nanoTime();t1.start();t2.start();t1.join();t2.join();System.out.println((System.nanoTime() - start)/100_0000);}
      }
      

乱序问题

  • 乱序问题
  • CPU为了提高指令执行效率,会在一条指令执行过程中(比如:去内存读数据(慢100倍)),去同时执行另一条指令,前提是两条指令没有依赖关系(https://www.cnblogs.com/liushaodong/p/4777308.html);
  • CPU的写操作也可以合并(https://www.cnblogs.com/liushaodong/p/4777308.html);
  • WCBuffer只有4个位置,如果合并写的时候大于4个位置,则会分两次(比如:合并写6个,一次写6个比两次(每次写3个)效率低);
  • JUC/029_WriteCombining
    • 示例验证代码

      package com.lele.juc.c_029_WriteCombining;public final class WriteCombining {private static final int ITERATIONS = Integer.MAX_VALUE;private static final int ITEMS = 1 << 24;private static final int MASK = ITEMS - 1;private static final byte[] arrayA = new byte[ITEMS];private static final byte[] arrayB = new byte[ITEMS];private static final byte[] arrayC = new byte[ITEMS];private static final byte[] arrayD = new byte[ITEMS];private static final byte[] arrayE = new byte[ITEMS];private static final byte[] arrayF = new byte[ITEMS];public static void main(final String[] args) {for (int i = 1; i <= 3; i++) {System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());System.out.println(i + " SplitLoop  duration (ns) = " + runCaseTwo());}}public static long runCaseOne() {long start = System.nanoTime();int i = ITERATIONS;while (--i != 0) {int slot = i & MASK;byte b = (byte) i;arrayA[slot] = b;arrayB[slot] = b;arrayC[slot] = b;arrayD[slot] = b;arrayE[slot] = b;arrayF[slot] = b;}return System.nanoTime() - start;}public static long runCaseTwo() {long start = System.nanoTime();int i = ITERATIONS;while (--i != 0) {int slot = i & MASK;byte b = (byte) i;arrayA[slot] = b;arrayB[slot] = b;arrayC[slot] = b;}i = ITERATIONS;while (--i != 0) {int slot = i & MASK;byte b = (byte) i;arrayD[slot] = b;arrayE[slot] = b;arrayF[slot] = b;}return System.nanoTime() - start;}
      }
      
  • 乱序执行的证明:JVM/jmm/Disorder
    • 示例验证代码

      package com.mashibing.jvm.c3_jmm;public class T04_Disorder {private static int x = 0, y = 0;private static int a = 0, b =0;public static void main(String[] args) throws InterruptedException {int i = 0;for(;;) {i++;x = 0; y = 0;a = 0; b = 0;Thread one = new Thread(new Runnable() {public void run() {//由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.//shortWait(100000);a = 1;x = b;}});Thread other = new Thread(new Runnable() {public void run() {b = 1;y = a;}});one.start();other.start();one.join();other.join();String result = "第" + i + "次 (" + x + "," + y + ")";if(x == 0 && y == 0) {System.err.println(result);break;} else {//System.out.println(result);}}}public static void shortWait(long interval){long start = System.nanoTime();long end;do{end = System.nanoTime();}while(start + interval >= end);}
      }
      
如何保证特定情况下不乱序
  • 硬件内存保障(X86)

    • sfence(save fence):在sfence指令前的写操作当必须在sfence指令后的写操作前完成;
    • lfence(load fence):在lfence值指令前的读操作当必须在lfence指令后的读操作前完成;
    • mfence(modify/mix fence):在mfence指令前的读写操作当作必须在mfence指令后的读写操作前完成;

    原子指令,如X86上的“lock…”指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。

  • JVM级别如何规范(JSR133)

    1. LoadLoad屏障:
      对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    2. StoreStore屏障:对于这样的语句Store1;StoreStore;Store2,在Store2及后续写入操作前,保证Store1的写入操作对其他处理器可见。
    3. LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    4. StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
  • volatile的实现细节

    1. 字节码层面 ACC_VOLATILE

    2. JVM层面 volatile内存区的读写 都加屏障

      StoreStoreBarrier

      volatile 写操作

      StoreLoadBarrier

      LoadLoadBarrier

      volatile 读操作

      LoadStoreBarrier

    3. OS和硬件层面

      • https://blog.csdn.net/qq_26222859/article/details/52235930
      • hsdis - HotSpot Dis Assembler
      • windows lock 指令实现 | MESI实现
  • synchronized实现细节

    1. 字节码层面

      • ACC_SYNCHRONIZED
      • monitorenter monitorexit
    2. JVM层面
      • C C++ 调用了操作系统提供的同步机制
    3. OS和硬件层面
      • X86 : lock cmpxchg / xxx
      • https://blog.csdn.net/21aspnet/article/details/88571740

java8大原子操作(虚拟机规范)

已弃用,了解即可

最新的JSR-133已经放弃这种描述,但JMM没有变化。《深入理解Java虚拟机》P364

  • lock:主内存,标识变量为线程独占;
  • unlock:主内存,解锁线程独占变量;
  • read:主内存,读取内容到工作内存;
  • load:工作内存,read后的值放入线程本地变量副本;
  • use:工作内存,传值给执行引擎;
  • assign:工作内存,执行引擎结果赋值给线程本地变量;
  • store:工作内存,存值到主内存给write备用
  • write:主内存,写变量值;

happens-before原则(JVM规定重排序必须遵守的规则)

JLS17.4.5

  • 程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构;
  • 管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作;
  • volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作;
  • 线程启动规则:Thread的start()方法先行发生于这个线程的每一个操作;
  • 线程终止规则:线程的所有操作都先行于此线程的终止检测。可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测线程的终止。
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt()方法检测线程是否中断。
  • 对象终结规则:一个对象的初始化完成先行于发生它的finalize()方法的开始;
  • 传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C;

as if serial

不管如何重排序,单线程执行结果不会改变。

对象的内存布局

对象的创建过程

  1. class loading
  2. class linking(verification, preparation, resolution)
  3. class initializing
  4. 申请对象内存
  5. 成员变量赋默认值
  6. 调用构造方法
    • 成员变量顺序赋初始值
    • 执行构造方法语句

使用JavaAgent测试Object的大小

对象大小(64位机)
观察虚拟机配置
java -XX:+PrintCommandLineFlags -version
普通对象
  1. 对象头:markword 8
  2. ClassPointer指针:-XX:+UseCompressedClassPointers 为4字节 不开启为8字节
  3. 实例数据
    引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节
    Oops Ordinary Object Pointers
  4. Padding对齐,8的倍数
数组对象
  1. 对象头:markword 8
  2. ClassPointer指针同上
  3. 数组长度:4字节
  4. 数组数据
  5. 对齐 8的倍数
实验
  • 新建项目ObjectSize (1.8)

  • 创建文件ObjectSizeAgent

    package com.lele.jvm.agent;import java.lang.instrument.Instrumentation;public class ObjectSizeAgent {private static Instrumentation inst;public static void premain(String agentArgs, Instrumentation _inst) {inst = _inst;}public static long sizeOf(Object o) {return inst.getObjectSize(o);}
    }
    
  • src目录下创建META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Created-By: lele.com
    Premain-Class: com.lele.jvm.agent.ObjectSizeAgent
    

    注意Premain-Class这行必须是新的一行(回车 + 换行),确认idea不能有任何错误提示

  • 打包jar文件

  • 在需要使用该Agent Jar的项目中引入该Jar包
    project structure - project settings - library 添加该jar包

  • 运行时需要该Agent Jar的类,加入参数:

  -javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar
  • 如何使用该类:

    package com.lele.jvm.c3_jmm;import com.mashibing.jvm.agent.ObjectSizeAgent;public class T03_SizeOfAnObject {public static void main(String[] args) {System.out.println(ObjectSizeAgent.sizeOf(new Object()));System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));System.out.println(ObjectSizeAgent.sizeOf(new P()));}private static class P {//8 _markword//4 _oop指针int id;         //4String name;    //4int age;        //4byte b1;        //1byte b2;        //1Object o;       //4byte b3;        //1}
    }
    
  • markword 64位

Hotspot开启内存压缩的规则(64位机)
  • 4G以下,直接砍掉高32位
  • 4G - 32G,默认开启内存压缩 ClassPointers Oops
  • 32G,压缩无效,使用64位
  • 内存并不是越大越好(-
IdentityHashCode的问题

回答白马非马的问题:
当一个对象计算过identityHashCode之后,不能进入偏向锁状态
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500

对象定位

https://blog.csdn.net/clover_lily/article/details/80095580

  • 句柄池(垃圾回收效率高)
  • 直接指针(HotSpot使用)
对象的分配

JVM(四):Java内存模型(JMM)相关推荐

  1. java虚拟机jvm与Java内存模型(JMM)

    Java内存模型(JMM) Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存.Java 内存模型(JMM) 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存 ...

  2. Java虚拟机(JVM)与Java内存模型(JMM)学习笔记

    Java虚拟机[JVM]与Java内存模型[JMM]学习笔记 Java虚拟机(JVM) 三种JVM JVM 位置 JVM的主要组成部分及其作用 类加载器 双亲委派机制 沙箱安全机制 Java本地接口( ...

  3. JVM学习-Java内存模型JMM

    目录 1.Java内存模型概述 2.原子性 2.1.问题提出 2.2.问题分析 2.3.解决办法 3.原子性 3.1.退不出的循环 3.2 解决方法 3.3 可见性 4.有序性 4.1.诡异的结果 4 ...

  4. Java虚拟机jvm和Java内存模型jmm浅尝

    1. 需要了解的知识 1. 并发编程需要处理两个关键问题 线程同步和通信 2. 线程通信机制有两种 共享内存(隐式)和消息传递(显示) 3. Java中是采用共享内存模型来进行通信 2. 了解Java ...

  5. java基础—java内存模型(JMM)CPU架构、缓存一致性、重排序、JMM的实现、JMM保证可见性、有序性问题的详解

    java基础-JMM(CPU架构.JMM保证可见性.有序性) 文章目录 java基础-JMM(CPU架构.JMM保证可见性.有序性) CPU架构 说一说现在计算机存储器的结构层次 使用CPU缓存,会导 ...

  6. JVM——Java内存模型(JMM)

    关注微信公众号:CodingTechWork,一起学习进步. 软硬件发展概述 Amdahl定律和摩尔定律 1)Amdahl定律:通过系统中并行化和串行化的比重来描述多处理器系统能获得的运算加速能力. ...

  7. Java 内存模型 JMM 详解

    转载自 Java 内存模型 JMM 详解 JMM简介 Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性.是否可以重排序等问题的无关具体 ...

  8. java 内存模型JMM解析

    java 内存模型JMM解析 一.CPU多核并发缓存架构解析    1.以往的内存读取    2.后来的内存读取 二.java内存模型实现原理    1.验证上图模型      1)案列代码      ...

  9. Java并发编程-Java内存模型(JMM)

    前言 在上一章 Java并发编程-Android的UI框架为什么是单线程的? 中笔者介绍了并发编程线程安全「三大恶」:「可见性」.「原子性」以及「有序性」 广义上来说,并发编程问题笔者归纳为:是由于后 ...

  10. Java并发编程:Java内存模型JMM

    简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...

最新文章

  1. C语言--static修饰变量
  2. UVA 331 Mapping the Swaps
  3. UA MATH563 概率论的数学基础 中心极限定理6 独立随机变量的和与Kolmogorov扩展定理
  4. php phar包require报错,被遗忘的PHP5.3新特性——Phar
  5. Spring MVC 3模板和Apache Tiles
  6. django ajax form表单,Django学习系列之Form表单和ajax(示例代码)
  7. oracle分组后合并(wm_concat)其中一个字段
  8. PyTorch学习—19.模型的加载与保存(序列化与反序列化)
  9. 现代密码学(七)对称密钥管理和公钥革命
  10. 清华OS前置知识:80386处理器
  11. 3D打印机USB联机打印是如何实现的?(以Cura插件USBPrinting为例)
  12. Backtrader量化平台教程(一):backtrader的整体框架
  13. What‘s next for AlphaFold and the AI protein-folding revolution / 什么是AlphaFold和AI蛋白质折叠革命的下一步?
  14. ​在Windows 10 下安装 Vmware 时遇到的问题和解决​方法
  15. 数据分析思维——数据分析方法
  16. 理科生学计算机数字媒体方向,文科生,理科生,分别可以报哪些专业?5分钟了解文理报考大方向...
  17. (转载)HCIA-GaussDB 华为认证数据库工程师
  18. 双硬盘好还是纯固态好学计算机专业,电脑双硬盘好还是纯固态好?
  19. [普及] NOIP 2012 文化之旅
  20. Android动态广播添加权限

热门文章

  1. win10重装系统,屏幕亮度调节失效修复
  2. [SAP ABAP开发技术总结]增强Enhancement
  3. 火狐 全屏_如何禁用Firefox的全屏警告消息
  4. Pangu Separates Heaven and Earth(签到题)
  5. Redis的前前后后左左右右
  6. Win10怎么关闭开机启动项
  7. 吉林大学软件学院黄庆道《最优化算法》对偶单纯形使用大M法条件
  8. 6ES7513-1AL02-0AB0的技术参数
  9. 遇到【java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter】的解决方案
  10. 如何将计算机的硬盘分割,电脑硬盘如何快速分区