上节回顾:类加载机制

双亲委派机制

parent只是一个成员变量,不是继承关系。

上节课的遗留问题

parent是怎么指定的?
手动指定parent:

双亲委派机制可以被打破吗?

双亲委派机制是在ClassLoader类里的LoadClass()方法已经写死的,你只需重写FingClass()方法就可以了。那怎么打破它呢?

热加载的实现原理

Tomcat把整个ClassLoader全部干掉,再用自己实现的ClassLoader把新修改过的类的Class重新Load一遍。

正确版本:将一个class加载两次

package com.mashibing.jvm.c2_classloader;import com.mashibing.jvm.Hello;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class T012_ClassReloading2 {private static class MyLoader extends ClassLoader {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {File f = new File("C:/work/ijprojects/JVM/out/production/JVM/" + name.replace(".", "/").concat(".class"));if(!f.exists()) return super.loadClass(name);try {InputStream is = new FileInputStream(f);byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {e.printStackTrace();}return super.loadClass(name);}}public static void main(String[] args) throws Exception {MyLoader m = new MyLoader();Class clazz = m.loadClass("com.mashibing.jvm.Hello");m = new MyLoader();Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");System.out.println(clazz == clazzNew);}
}

无效版本:使用的仍然是加载过的类

package com.mashibing.jvm.c2_classloader;public class T011_ClassReloading1 {public static void main(String[] args) throws Exception {T006_MSBClassLoader msbClassLoader = new T006_MSBClassLoader();Class clazz = msbClassLoader.loadClass("com.mashibing.jvm.Hello");msbClassLoader = null;System.out.println(clazz.hashCode());msbClassLoader = null;msbClassLoader = new T006_MSBClassLoader();Class clazz1 = msbClassLoader.loadClass("com.mashibing.jvm.Hello");System.out.println(clazz1.hashCode());System.out.println(clazz == clazz1);}
}

Linking

证明默认值的存在
输出结果为2. 如果交换10 11行,输出结果为3

package com.mashibing.jvm.c2_classloader;public class T001_ClassLoadingProcedure {public static void main(String[] args) {System.out.println(T.count);}
}class T {public static T t = new T(); // 执行到这一步时,下一行的count仍然是默认值0,调用构造方法后,count变为1,public static int count = 2; // 执行到这一步时,上一行的count=1被这一行的count=2覆盖private T() {count ++;}
}

New 一个对象的过程

  • 先分配内存
  • 再赋默认值
  • 再赋初始值

面试题:DCL(Double Check Lock)单例要不要加Volitile?
需要。因为有指令重排的问题。
初始化一半的时候,单例已经存在了,处于半初始化的状态。此时另外一个线程来了,直接把半初始化的对象取走了,出现值的问题。

package com.mashibing.jvm.c0_basic;public class C {public static void main(String[] args) {Object o = new Object();}
}

下图4、7可能会发生重排
官网解释:
new: Create new object
dup: 复制操作数堆栈上的顶部值,并将复制的值压入操作数堆栈。
invoke special: 调用实例方法; 对超类、私有和实例初始化方法调用的特殊处理
astore_<n>: 将引用存储到局部变量中。操作数堆栈顶部的objectref的类型必须是returnAddress或reference类型。它从操作数堆栈中弹出,并将处的局部变量的值设置为objectref。

JMM(Java Memory Model)

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


不同层级的速度差别有多大?


产生数据的不一致问题:两个CPU之间的缓存怎么保持一致?
老的CPU,总线锁:效率偏低

新的CPU:各种各样的一致性协议,例如,intel的MESI协议
4种状态的含义:我改过、只有我在用、别人也在读、失效

现在,CPU的数据一致性实现是通过 缓存一致性协议(MESI…)+总线锁 共同实现的。

缓存行 CacheLine

读取缓存以cacheline为基本单位,多数的实现为64kB(64字节,512位)

伪共享:位于同一缓存行的两个不同数据被两个不同CPU锁定,产生相互影响的问题。

示例1:两个线程修改同一个数组中的值。耗时1255

package com.mashibing.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);}
}

示例2:使用7个long类型的数字(7个*8字节=56字节)将缓存行填起来,加上变量x本身占用的空间,使两个T对象恰巧不在统一缓存行。耗时443
通过缓存行对齐后,效率提升了。
开源的disruptor就考虑到了这个问题。

package com.mashibing.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的乱序执行

CPU为了提高效率,会在一条指令执行过程中(比如去内存读数据,慢100倍),去同时执行另一条指令。前提是两条指令没有依赖关系。

合并写技术

写操作也可以合并

只有4个字节,一次填6个的话,要等下一次把剩下的2个填满。如果一次填4个,就正好填满。

package com.mashibing.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;  // 1BytearrayA[slot] = b;  // 1BytearrayB[slot] = b;  // 1BytearrayC[slot] = b;  // 1BytearrayD[slot] = b;  // 1BytearrayE[slot] = b;  // 1BytearrayF[slot] = b;  // 1Byte}return System.nanoTime() - start;}public static long runCaseTwo() {  // 一次正好写满一个四字节的 Buffer,比上面的循环效率更高long start = System.nanoTime();int i = ITERATIONS;while (--i != 0) {int slot = i & MASK;byte b = (byte) i; // 1BytearrayA[slot] = b;  // 1BytearrayB[slot] = b;  // 1BytearrayC[slot] = b;  // 1Byte}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;}
}

乱序执行的证明


package com.mashibing.jvm.c3_jmm;public class T04_Disorder {private static int x = 0, y = 0;private static int a = 0, b = 0;// private static volatile int x = 0, y = 0;// private static volatile 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);}
}

如何保证特定情况下不乱序?

Volitile
有序性保证:CPU 级别的内存屏障,我们以 intel CPU 为例:

volitile 变量,它的内存的前后都加了屏障,

下节课,我们讲 Volitile 的有序性在硬件级别是如何保证的、在JVM级别是如何规范的。

JVM从入门到精通(三):热加载的实现原理,Java内存模型,缓存行,指令重排,合并写技术等相关推荐

  1. webpack 热加载原理探索

    前言 在使用 dora 作为本地 server 开发一个 React 组件的时候,默认使用了 hmr 插件.每次修改代码后页面直接更新,不需要手动 F5 ,感觉非常惊艳,这体验一旦用上后再也回不去了. ...

  2. 热部署与热加载的区别

    在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载.对于Java应用程序来说,热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载class,从而升级应用. 热加载的实现原理 ...

  3. jvm内存参数配置_JVM内存结构和Java内存模型

    一.JVM 首先看一张JVM结构图(某度找的) 主要看运行时数据区,里边有方法区,堆,java虚拟机栈,本地方法栈,程序计数器.其中方法区和堆是线程共享的,也是JVM进行垃圾收集的区域,java虚拟机 ...

  4. JVM从入门到精通(尚硅谷宋红康)

    不动笔墨不读书,先把书读厚,再把书读薄是我学习方式. 所以等理解了再整理一次笔记,目前笔记和视频一一对应. 笔记连载中 <尚硅谷2020最新版宋红康JVM> 第1章:JVM与Java体系结 ...

  5. C4D致富经典入门到精通(三)

    C4D样条曲线创建 C4D基础界面的介绍与常用快捷键:  C4D致富经典入门到精通(一) C4D父子关系的理解与创建参数几何体与可编辑对象: C4D致富经典入门到精通(二) C4D样条曲线创建 :  ...

  6. spring boot:从零开始搭建一个项目 - day 7 springboot devtools热加载+MybatisPlus配置+kisso从入门到放弃

    spring boot:从零开始搭建一个项目 - day 7 springboot devtools热加载+MybatisPlus配置+kisso从入门到放弃 一.springboot devtool ...

  7. gitbook 入门教程之解决windows热加载失败问题

    破镜如何贴花黄 gitbook 在 Windows 系统无法热加载,总是报错! gitbook 是一款文档编写利器,可以方便地 markdown 输出成美观优雅的 html ,gitbook serv ...

  8. Kali Linux 从入门到精通(三)-入侵系统定制

    Kali Linux 从入门到精通(三)-入侵系统定制 定制 网络配置 临时IP地址 dhclient eth0 ifconfig eth0 192.168.11/24 route add defau ...

  9. Jvm从入门到精通(全)

    目录 前言 1. JVM体系架构 2. 类加载器子系统 2.1 类加载器过程 2.1.1 加载 2.1.2 链接 2.1.3 初始化 2.2 类加载器分类 2.3 ClassLoader类 2.4 双 ...

最新文章

  1. CSMAR database query sample
  2. mysql集合与集合的子集_大集合List分为多个子集合
  3. 操作系统实验报告11:ucore Lab 2
  4. 数值计算算法-多项式插值算法的实现与分析
  5. 初探 vue 插件开发
  6. Centos6.6安装Nginx
  7. 细节问题:ZEROFILL的用法范围。
  8. python-appium手机自动化测试(仅需安装包)前期准备(pydev-eclipse编辑器)
  9. 【手写数字识别】基于matlab GUI BP神经网络手写数字识别系统【含Matlab源码 1639期】
  10. SPC统计及控制图系列标准
  11. 傅里叶变换 ~ 什么是傅里叶变换?
  12. 实验报告(LCS算法和背包算法)
  13. Android上Excel编辑器,Excel表格编辑app
  14. 电梯远程监控系统方案
  15. 游戏引擎设计的技术及详解
  16. 通用计数器的检定方案
  17. myeclipse自定义背景颜色
  18. 秒云获得阿里云首批产品生态集成认证,携手阿里云共建云原生智能运维生态服务
  19. JdbcTemplate报空指针异常 已解决
  20. 计算机重装后不能启动怎么办,电脑重装系统后开不了机怎么处理

热门文章

  1. python 链表推导式_五--python之数据结构(Data Structures)
  2. UVA1374 Power Calcilus快速幂计算
  3. UVA11212Editing aBook 编辑书稿
  4. python高级语法-collections模块下几个新序列
  5. android homme一般多钱,【ANDROID HOMME】ANDROID HOMME官网介绍_ANDROID HOMME口碑_什么值得买...
  6. C++虚继承(五) --- 虚拟继承的概念
  7. Shell变量:Shell变量的定义、删除变量、只读变量、变量类型
  8. Instsrv.exe可以给系统安装和删除服务
  9. 数据结构与算法 | 直接插入排序、希尔排序
  10. Windows下查看端口被占用问题和解决办法