深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

来源:互联网 发布:js基本数据类型有哪些 编辑:程序博客网 时间:2024/05/20 05:57

为了 接下去 更好理解 JAVA 并发,多线程 JUC 包的原理 特此写下前置学习文章 深入学习 java 虚拟机

本文目录

  • JVM启动流程

  • JVM基本结构

  • 内存模型

  • 编译和解释运行的概念

一、java 程序 启动流程

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

启动流程

java 命令开始

寻找 配置文件 定位需要的 .dll

.ddl 初始化 JVM 虚拟机

获得 native 接口

找到main 方法运行

二、JVM结构(运行时数据区)

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

JVM结构(运行时数据区)

二.一、线程私有的区域

1.程序计数器(PC寄存器)

  • 记录正在执行的字节码地址,可辨别当前字节码解析到了什么位置,引导字节码解析顺序,并控制程序的流程。(当前程序执行到哪然后下一步该执行什么操作。)

    • 执行java 方法的时候世纪路字节码的地址,执行Native方法这个计数器为空(Undefined)

    • 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

  • 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。

2.栈(VM Stack)

  • 栈由一系列帧组成(因此Java栈也叫做帧栈)

  • 线程私有------->方法执行时候的内存模型

    • 每个方法执行都会创建一个栈帧 用于存储局部变量表(基本数据类型和对象引用),操作数栈,动态链表,方法出口等信息,值得一提的是long,double长度为64为会占据两个局部变量空间其余为一个。

      局部变量表所需内存实在编译器完成分配的,方法在帧中所需的分配多大的局部变量空间是完全确定的,方法运行不会改变局部变量表大小。

  • 当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值。

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

  • 栈上分配

  • 小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上

  • 直接分配在栈上,可以自动回收,减轻GC压力

  • 大对象或者逃逸对象无法栈上分配

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

每个线程包含一个栈区,栈中只保存基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中

每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

栈分为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 关键字

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

3.1、多线程操作变量模型

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

3.2、关键字volatile

  • 保证内存可见性

  • 防止指令重拍(有序性)

  • volatile保证操作原子性

volatile 不能代替锁

一般认为volatile 比锁性能好(不绝对)

选择使用volatile的条件是:

语义是否满足应用

因为多线程操作和volatile两个意思

关键字volatile 效果

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

synchronized (unlock之前,写变量值回主存)

final(一旦初始化完成,其他线程就可见)

3.3 指令重拍

指令重拍就是编译器按照理解的优化代码

可能会不按照代码顺序来 不会进行对象依赖的重拍

会重拍对象之间不依赖的进行重拍

最后 保证 整个线程的语义不发生改变

  • 线程内串行语义

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

  • 编译器不考虑多线程间的语义

    可重排: a=1;b=2;

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

例子 -----> 正确方法多线程 指令重拍的错 synchronized 锁住对象

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

3.3.1 指令重排的基本原则

  • 程序顺序原则:一个线程内保证语义的串行性

  • volatile规则:volatile变量的写,先发生于读

  • 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

  • 传递性:A先于B,B先于C 那么A必然先于C

  • 线程的start方法先于它的每一个动作

  • 线程的所有操作先于线程的终结(Thread.join())

  • 线程的中断(interrupt())先于被中断线程的代码

  • 对象的构造函数执行结束先于finalize()方法

编译运行(JIT)

  • 将字节码编译成机器码

  • 直接执行机器码

  • 运行时编译

  • 编译后性能有数量级的提升

深入理解JVM 系列JVM运行机制 JVM内存模(volatile,指令重排)

学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群346942462,我们一起学Java!