JVM 学习(二)Java 内存模型、方法内联、逃逸 --- 2019年4月
1、Java 的内存模型
定义了 happens-before,如果同一个线程中,字节码的先后顺序,后者观测了前者的运行结果,那么就会按顺序执行。
Java 线程之间的通信由 Java 内存模型控制。
Java 内存模型通过定义了一系列的 happens-before 操作,不同线程的操作之间的内存可见性。
happens-before 关系还具备传递性。
解决数据竞争问题的关键在于构造一个跨线程的 happens-before 关系,操作A happens-before 操作B,是的操作A 之前的字节码结果对操作B 之后的字节码可见。
Java 内存模型的底层实现
Java 内存模型是通过内存屏障来禁止重排序的。写读操作才会用具体的指令来代替,可以理解为强制刷新处理器的写缓存,让当前线程所修改的内存对其他线程可见。写缓存是处理器用来加速内存存储效率的一项技术。
例如 volatile 字段,内存写操作会无效其他处理器所持有、指向同一地址的缓存行,所以其他处理器可以立即见到该 volatile 字段的最新值。
锁、volatile字段、final字段与安全发布
锁操作也具备 happens-before 关系。解锁之后才能对同一把锁进行加锁。但是如果证明某把锁仅被同一线程持有,那么它可以移除响应的加锁解锁操作。例如加了 synchornized 关键字就不会强制刷新内存。
volatile 可以看做是一个轻量级、不保证原子性的同步,性能优于锁操作。但是频繁的访问 volatile 字段也会因为不短强制刷新缓存而严重影响程序的性能。当然是写操作才会影响,所以实际情况中使用 volatiel 字段尽量多读少写。并且即时编译器无法将其分配到寄存器,只能存在内存中。
final 字段的写操作后插入一个写写屏障,防止某些优化将新建对象的发布重排序至 final 字段的写操作之前。
新建对象的安全发布,不仅仅包括 final 实例字段的可见性,还包括其他实例字段的可见性。
扩展知识
java 内存模型,围绕 顺序一致性内存模型。
Java 的内存模型主要是为了方便线程之间的通信,抽象出来了线程的本地内存和共享的主内存。JMM 通过控制主内存与每个线程的本地内存之间的交互,来为 Java 开发者提供内存可见性的保证。
执行程序时为了提高性能,编译器和处理器尝尝会对指令做重排序。Java 内存模型就用内存屏障来禁止重排序。(禁止屏障,针对特定类型的处理器重排序)
as-if-serial 语义把单线程保护了起来,重排序不会改变线程执行的结果。
重量级锁到自旋锁,自旋锁是空着等待锁,而不是阻塞等待。
偏向锁 --- 轻量级锁 --- 重量级锁:
2、Java 语法糖与 Java 编译器
泛型与类型擦除
Java 泛型的类型擦除,指的是 Java 程序的泛型信息,在 JVM 里全部都丢失了。这样做是为了兼容引入泛型之前的代码。一般泛型擦除后类型会变为 Object 类,但是也可能是当前继承的类的顶级类。
泛型主要是为了判断程序中语法是否正确。
Java 语义与 Java 字节码中关于重写的定义不一致,因为 Java 编译器会成圣桥接方法作为适配器。
其他语法糖
变长参数、try-with-resources 以及 catch 代码块中捕获多种异常等语法糖。
3、即时编译
通常而言,代码会先被 JVM 解释执行,之后反复执行的热点代码,则会被即时编译成机器码,直接运行在底层硬件之上。
1)、分层编译模式
结合了C1的启动性能优势和C2的峰值性能优势。通常情况下,C2机器码比C1机器码的执行效率高出30%以上。如果C1、C2编译了方法,并且未失效,那么 JVM 是不会再次发出该方法的编译请求。
Java 8 默认开启了分层编译。
2)、即时编译的触发
JVM 会将编译线程按照1:2的比例分配给C1和C2,启动分层编译的阈值是动态调整的。
3)、OSR 编译
决定一个方法是否为热点代码的因素有两个,方法的调用次数、循环回边的执行次数(例如 for 循环的次数),即时编译就是根绝这两个计数器的和来触发。
JVM 还存在一种以循环为单位的即时编辑,即OSR编译,循环回边计数器就是用来出发这种类型的编译的。
ORS:程序执行过程中,动态替换掉 Java 方法栈帧,从而使得程序能够在非方法入口处进行解释执行和编译后的代码之间的切换。默认情况C1的 OSR 编译的阈值为13500,C2为10700.
4)、Profiling
profiling 收集反应程序执行状态的数据,用于编译器优化。
分支 profile 和 类型 profile 的收集将会给应用程序带来不小的性能开销。一般情况下我们不会再解释执行过程中收集分支 profile 以及类型 profile。
C2 可以根据收集得到的数据进行猜测,假设接下来的执行同样会安装收集的 profile 进行,从而做出比较激进的优化。
4、字节码
1)、操作数栈
解释执行时,为 Java 方法分配栈帧时,Java 虚拟机需要开辟一块额外的空间作为操作数栈,存放计算的操作数以及返回结果。
执行每一条指令之前,Java 虚拟机要求该指令的操作数已被压入操作数栈中。
执行指令时,JVM 会将该指令所需的操作数栈弹出,并且将指令的结果重新压入栈中。
有几条指令直接作用在操作数栈上的。最常见的 dup:复制栈顶元素,pop:舍弃栈顶元素。
dup 常用于复制 new 指令生成的 未经初始化的引用,例如 Object o = new Object();
正常情况下,操作数栈的压入弹出都是一条条指令完成,除了抛异常,JVM 直接清除操作数栈上的所有内容,然后将异常实例压入操作数栈上。
2)、局部变量区
Java 方法栈帧的另一个重要组成部分就是局部变量区,字节码程序可以将计算结果缓存在局部变量区之中。
3)、字节码的各种指令
包括了常数指令(iconst、bipush、sipush、Iconst等)、加载指令和存储指令(iload、istore int类型)
数组加载、存储指令(iaload、laload和iastore、lastore等)、返回指令(ireturn、lreturn等)。
5、方法内联
方法内联:在编译过程中遇到方法调用时,将目标方法的方法纳入编译范围之中,并取代原方法调用的优化手段。(类似株连)
方法内联可以消除调用本身带来的开销,还可以进一步出发更多的优化。
内联越多,生成代码的执行效率越高。 然而对于即时编译器来说,内联越多,编译时间也就越场,而程序达到峰值的时刻也将被推迟。也会导致生成的机器码越长。
即时编译器可以在解析过程中替换方法调用字节码,也可以在 IR 图中替换方法调用 IR 节点。
内联规则:
由@Forcelnline 注解的方法(仅限于 JDK 内部方法)会被强制内联。
调用字节码对应的符号引用未被解析、目标方法所在的类未被初始化,或者目标方法是 native 方法,都将导致方法调用无法内联。
C2 不支持内联超过9层的调用,以及1层的直接递归调用。
总体来说,即时编译器中的内联算法,更青睐于小方法。
动态绑定
即时编译器需要先对虚方法调用进行去虚化,即转换位一个或多个直接调用,然后进入方法内联。
去虚化分为完全去虚化(基于类型推导、基于类型层次分析)、条件去虚化。
6、逃逸分析
逃逸:新建的对象,被存入堆中(静态字段或者堆中对象的实例字段),如果对象存入堆中,其他线程就能获得该对象的引用。(局部对象实例,逃出方法)
或者对象被传入未知代码中,即时编译器无法确认该方法调用,会不会将调用者所传入的参数转存储至堆中。可以认为方法调用的调用者以及参数是逃逸的。
基于逃逸分析的优化
锁消除:即时编译器能够证明对象不逃逸,对该对象的加锁、解锁操作没有意义。因为其他线程对该对象无法操作,因此也不能够对其加锁、解锁。这种情况,即时编译器就可以消除对该不逃逸锁对象的加锁、解锁操作。
逃逸分析的结果,多被用于新建对象操作转换成栈上分配或者标量替换。
栈上分配:当逃逸分析能证明新建对象不逃逸,JVM 就可以将该对象分配到栈上,在 new 语句所在的方法退出时,通过弹出当前方法的栈帧来自动回收所分配的内存空间。(这样就无须借助垃圾回收器来处理不再被引用的对象)
但是 HotSpot 虚拟机并没有采用栈上分配的技术,而是采用了标量替换技术。
标量替换:将原本对对象的字段的访问,替换成一个个局部变量的访问。这些字段没有分配实际内存,和栈上分配一样,甚至可以直接存在寄存器中,不需要内存空间。
JVM 学习(二)Java 内存模型、方法内联、逃逸 --- 2019年4月的更多相关文章
- JVM学习记录-Java内存模型(二)
对于volatile型变量的特殊规则 关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制. 在处理多线程数据竞争问题时,不仅仅是可以使用synchronized关键字来实现,使用vo ...
- JVM学习记录-Java内存模型(一)
前言 Java虚拟机规范中定义了一种Java的内存模型,即Java Memoory Model(简称JMM),用来实现让Java程序在各个平台下都能达到一致的内存访问效果.JVM是整个虚拟机,JMM模 ...
- Java中的方法内联
Java中的方法内联 1. 什么是方法内联 例如有下面的原始代码: static class B { int value; final int get() { return value; } } pu ...
- jvm系列五-java内存模型初览(1)
本文转载自:再有人问你Java内存模型是什么,就把这篇文章发给他. 网上有很多关于Java内存模型的文章,在<深入理解Java虚拟机>和<Java并发编程的艺术>等书中也都有关 ...
- 【JVM.11】Java内存模型与线程
鲁迅曾经说过“并发处理的广泛应用是使得Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因,也是人类‘压榨‘ 计算机运行能力的最有力武器.” 一.概述 多任务处理在现代计算机操作系统中几乎已 ...
- 【深入理解JVM】:Java内存模型JMM
多任务和高并发的内存交互 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标 ...
- JVM的艺术—JAVA内存模型
*喜欢文章,动动手指点个赞 * 引言 亲爱读者你们好,关于jvm篇章的连载,前面三章讲了类加载器,本篇文章将进入jvm领域的另一个知识点,java内存模型.彻底的了解java内存模型,是有必要的.只要 ...
- jvm系列五-java内存模型(2)
原作者系列文章链接:并发编程系列博客传送门 前言# 在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念.网上很多文章中关于JMM的介绍要么是照搬了一些 ...
- 轻松学JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
- 深入理解JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
随机推荐
- tkinter调取签名网而设计签名页面
# --*-- coding:utf-8 --*-- import tkinter as tk import re import requests from tkinter import messag ...
- 最强AngularJS资源合集
AngularJS是Google开源的一款JavaScript MVC框架,弥补了HTML在构建应用方面的不足,其通过使用指令(directives)结构来扩展HTML词汇,使开发者可以使用HTML来 ...
- Android FoldingLayout 折叠布局 原理及实现(二)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/44283093,本文出自:[张鸿洋的博客] 1.概述 在上一篇Android Fo ...
- 嘿嘿嘿,开始自学mysql
开始学习mysql了,作为非计算机专业学生,必须需要一个地方来给自己的知识进行一些记录和总结. 一SQL语句 数据库是不认识java语言的,但是我们同样要与数据库交互,这时需要使用到数据库认识的语言S ...
- bzoj 2829 计算几何
将每张卡四个角的圆心跑graham出正常凸包,再加上一个圆就好了. 要注意先输入的是x,找点时三角函数瞎换就过了.. #include<cstdio> #include<cstrin ...
- STL--multiset用法
multiset: multiset<int>s; 定义正向迭代器与正向遍历: multiset<int>::iterator it; for(it=s.begin();it! ...
- Windows上安装配置SSH教程(7)——几种方式对比
服务端:Windows XP 客户端:Windows 10 由于Cygwin也可以安装OpenSSH,所以客户端其实可以直接使用Cygwin安装OpenSSH,那么在Windows下使用SCP(安全拷 ...
- ServletContextListener
在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期. 当Serv ...
- GT工具中用到的英文词解释
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 ...
- 本周新学的 GUI绘图技术
作者语录:"终于学到绘图了 看到这种有图案的心情美丽多了 希望自己可以越学越多 越学越好" 本次就不用图片展示效果了,纯文字. 1.Graphics类概述 画图时我们都需要拥有一 ...