您的位置:首页 > 其它

深入理解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!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: