深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)
2017-09-23 16:24
603 查看
为了 接下去 更好理解 JAVA 并发,多线程 JUC 包的原理 特此写下前置学习文章 深入学习 java 虚拟机
本文目录
JVM启动流程
JVM基本结构
内存模型
编译和解释运行的概念
一、java 程序 启动流程
启动流程
java 命令开始
寻找 配置文件 定位需要的 .dll
.ddl 初始化 JVM 虚拟机
获得 native 接口
找到main 方法运行
二、JVM结构(运行时数据区)
JVM结构(运行时数据区)
二.一、线程私有的区域
1.程序计数器(PC寄存器)
记录正在执行的字节码地址,可辨别当前字节码解析到了什么位置,引导字节码解析顺序,并控制程序的流程。(当前程序执行到哪然后下一步该执行什么操作。)
执行java 方法的时候世纪路字节码的地址,执行Native方法这个计数器为空(Undefined)
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。
2.栈(VM Stack)
栈由一系列帧组成(因此Java栈也叫做帧栈)
线程私有------->方法执行时候的内存模型
每个方法执行都会创建一个栈帧 用于存储局部变量表(基本数据类型和对象引用),操作数栈,动态链表,方法出口等信息,值得一提的是long,double长度为64为会占据两个局部变量空间其余为一个。
局部变量表所需内存实在编译器完成分配的,方法在帧中所需的分配多大的局部变量空间是完全确定的,方法运行不会改变局部变量表大小。
当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值。
栈上分配
小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
直接分配在栈上,可以自动回收,减轻GC压力
大对象或者逃逸对象无法栈上分配
每个线程包含一个栈区,栈中只保存基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
3.本地方法栈(Native Method Stack)
和虚拟机栈所发挥的效果非常相似, 区别在于 是为执行Native 方法 所服务的,HotSpot 直接把本地方法栈和虚拟机栈合二为一
-二.二、线程共享区域
Method Area(方法区) 方法区是堆的逻辑部分。
(这个只是JVM 中的 一个规范设计 每个厂商可能实现不同 本文会尽可能的 使用JDK1.8 的HotSpot 作为讲解)
在 HotSpot中 我们有了新的东西 就是 Metaspace(元空间) 是对 JVM规范中方法区的实现
元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过设置参数来指定元空间的大小。所有线程共享的。
保存装置的类信息
类的常量池()
类的字段,方法信息
方法字节码
永久区---> (java 8 HotSpot 中已经废除)
因为容易出现永久代的内存溢出
JAVA 堆 (Heap )全局共享
程序开发程序最为密切
目的就是存放对象实例
根据垃圾回收算法 分为(Minor GC、Full GC)
新生代(Eden ,Survivor from ,Survivor To )
老年代
GC的主要工作区间
所有线程共享Java堆
直接内存
1.4 中加入NIO 后 基于通道(channel) 与缓存 (Buffer) 使用Native 函数 操作内存 通过一个存储在java堆中的DirectByteBuffer对象作为这块内存引用这样能显著提升性能 避免java堆 与Native 堆 来回复制数据
三、java 的内存模型
每一个线程有一个工作内存和主存独立
工作内存存放主存中变量的值的拷贝
对于普通变量,一个线程中更新的值,不能马上反应在其他变量中 如果需要在其他线程中立即可见,需要使用 volatile 关键字
3.1、多线程操作变量模型
3.2、关键字volatile
保证内存可见性
防止指令重拍(有序性)
volatile保证操作原子性
volatile 不能代替锁
一般认为volatile 比锁性能好(不绝对)
选择使用volatile的条件是:
语义是否满足应用
因为多线程操作和volatile两个意思
关键字volatile 效果
synchronized (unlock之前,写变量值回主存)
final(一旦初始化完成,其他线程就可见)
3.3 指令重拍
指令重拍就是编译器按照理解的优化代码
可能会不按照代码顺序来 不会进行对象依赖的重拍
会重拍对象之间不依赖的进行重拍
最后 保证 整个线程的语义不发生改变
线程内串行语义
编译器不考虑多线程间的语义
可重排: a=1;b=2;
例子 -----> 正确方法多线程 指令重拍的错 synchronized 锁住对象
3.3.1 指令重排的基本原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法
编译运行(JIT)
将字节码编译成机器码
直接执行机器码
运行时编译
编译后性能有数量级的提升
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群346942462,我们一起学Java!
本文目录
JVM启动流程
JVM基本结构
内存模型
编译和解释运行的概念
一、java 程序 启动流程
启动流程
java 命令开始
寻找 配置文件 定位需要的 .dll
.ddl 初始化 JVM 虚拟机
获得 native 接口
找到main 方法运行
二、JVM结构(运行时数据区)
JVM结构(运行时数据区)
二.一、线程私有的区域
1.程序计数器(PC寄存器)
记录正在执行的字节码地址,可辨别当前字节码解析到了什么位置,引导字节码解析顺序,并控制程序的流程。(当前程序执行到哪然后下一步该执行什么操作。)
执行java 方法的时候世纪路字节码的地址,执行Native方法这个计数器为空(Undefined)
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。
2.栈(VM Stack)
栈由一系列帧组成(因此Java栈也叫做帧栈)
线程私有------->方法执行时候的内存模型
每个方法执行都会创建一个栈帧 用于存储局部变量表(基本数据类型和对象引用),操作数栈,动态链表,方法出口等信息,值得一提的是long,double长度为64为会占据两个局部变量空间其余为一个。
局部变量表所需内存实在编译器完成分配的,方法在帧中所需的分配多大的局部变量空间是完全确定的,方法运行不会改变局部变量表大小。
当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值。
栈上分配
小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
直接分配在栈上,可以自动回收,减轻GC压力
大对象或者逃逸对象无法栈上分配
每个线程包含一个栈区,栈中只保存基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
3.本地方法栈(Native Method Stack)
和虚拟机栈所发挥的效果非常相似, 区别在于 是为执行Native 方法 所服务的,HotSpot 直接把本地方法栈和虚拟机栈合二为一
-二.二、线程共享区域
Method Area(方法区) 方法区是堆的逻辑部分。
(这个只是JVM 中的 一个规范设计 每个厂商可能实现不同 本文会尽可能的 使用JDK1.8 的HotSpot 作为讲解)
在 HotSpot中 我们有了新的东西 就是 Metaspace(元空间) 是对 JVM规范中方法区的实现
元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过设置参数来指定元空间的大小。所有线程共享的。
保存装置的类信息
类的常量池()
类的字段,方法信息
方法字节码
永久区---> (java 8 HotSpot 中已经废除)
因为容易出现永久代的内存溢出
JAVA 堆 (Heap )全局共享
程序开发程序最为密切
目的就是存放对象实例
根据垃圾回收算法 分为(Minor GC、Full GC)
新生代(Eden ,Survivor from ,Survivor To )
老年代
GC的主要工作区间
所有线程共享Java堆
直接内存
1.4 中加入NIO 后 基于通道(channel) 与缓存 (Buffer) 使用Native 函数 操作内存 通过一个存储在java堆中的DirectByteBuffer对象作为这块内存引用这样能显著提升性能 避免java堆 与Native 堆 来回复制数据
三、java 的内存模型
每一个线程有一个工作内存和主存独立
工作内存存放主存中变量的值的拷贝
对于普通变量,一个线程中更新的值,不能马上反应在其他变量中 如果需要在其他线程中立即可见,需要使用 volatile 关键字
3.1、多线程操作变量模型
3.2、关键字volatile
保证内存可见性
防止指令重拍(有序性)
volatile保证操作原子性
volatile 不能代替锁
一般认为volatile 比锁性能好(不绝对)
选择使用volatile的条件是:
语义是否满足应用
因为多线程操作和volatile两个意思
关键字volatile 效果
synchronized (unlock之前,写变量值回主存)
final(一旦初始化完成,其他线程就可见)
3.3 指令重拍
指令重拍就是编译器按照理解的优化代码
可能会不按照代码顺序来 不会进行对象依赖的重拍
会重拍对象之间不依赖的进行重拍
最后 保证 整个线程的语义不发生改变
线程内串行语义
编译器不考虑多线程间的语义
可重排: a=1;b=2;
例子 -----> 正确方法多线程 指令重拍的错 synchronized 锁住对象
3.3.1 指令重排的基本原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法
编译运行(JIT)
将字节码编译成机器码
直接执行机器码
运行时编译
编译后性能有数量级的提升
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群346942462,我们一起学Java!
相关文章推荐
- 深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)
- 深入理解 JVM 系列:运行时数据区域
- .Net Discovery系列之十二-深入理解平台机制与性能影响(下)
- JS核心系列:理解 new 的运行机制
- 深入理解ASP.NET的内部运行机制
- 深入JVM系列(三)之类加载、类加载器、双亲委派机制与常见问题
- 深入解析java虚拟机-jvm运行机制
- 深入理解JVM(二)------Java代码执行机制
- 深入理解JVM--JVM垃圾回收机制
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
- 深入理解 JVM 系列:判断对象的存活
- 深入JVM系列(二)之GC机制、收集器与GC调优
- .Net Discovery系列之四 深入理解.Net垃圾收集机制(下)
- 深入理解JVM(二)------Java代码执行机制
- Java的多线程机制系列:不得不提的volatile及指令重排序
- 深入理解ASP.NET的内部运行机制
- 深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来
- (20)深入理解JVM--JVM垃圾回收机制
- 深入解析java虚拟机-jvm运行机制