深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)
为了 接下去 更好理解 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!