`
BBLLMYD
  • 浏览: 17879 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

3)JVM执行子系统

阅读更多
概述 : 了解虚拟机如何执行程序, 虚拟机怎样运行一个Class文件的概念模型, 可以更好的理解怎样写出优秀的代码
 
一 : 类文件结构
 
  • 无关性基石: 《Java虚拟机规范》对class文件的定制的规范, 成为了JVM语言无关性的基石, 即只要满足规范, 不限制语言种类, 编译器能够将语言转换为满足虚拟机规范的的字节码文件即可, 实现语言无关性的基础是虚拟机和字节码的存储格式


 
JVM语言无关性
 
class文件结构 : 
类型
名称
数量
含义
u4(代表4个字节的无符号数)
magic
1
魔数(文件的头4个字节, 固定0XCAFFBABE)
u2
minor_version
1
次版本号
u2
major_version
1
主版本号
u2
constant__pool_count
1
常量池容量计数值
cp_info
constant__pool
constant__pool_count-1
常量池(主要是两类: 字面量和符号引用)
u2
access_flags
1
访问标志
u2
this_class
1
类索引
u2
super_class
1
父类索引
u2
interface_count
1
接口数量
u2
interfaces
interface_count
接口集合
u2
fields_count
1
字段个数
field_info
fields
fields_count
字段表集合
u2
method_count
1
方法个数
method_info
methods
method_count
方法表集合
u2
attribute_count
1
属性个数
attribute_info
attributes
attributes_count
属性表集合
    1). 魔数与Class文件版本 : 魔数可简单理解为一个符合JVM规范的class文件约定的常量, 版本号为随着JDK更新的版本号标识
    2). 常量池 : 可理解为class文件的资源仓库, 与其他项目关联最多的类型, 占用空间较大, 主要包含两类字面量和符号引用
    3). 访问标识 : 用于识别一些类或者接口层次的访问信息
    4). 类索引, 父类索引和接口索引集合 : 类索引, 父类索引和接口索引集合与常量池关联
    5). 字段表集合 : 描述接口或类中声名的变量, 包括类级别和实例级别, 不包括方法中的变量
    6). 方法表集合 : 描述方法的一些摘要信息包括访问标识, 名称索引, 描述符索引, 属性表集合等, 方法具体内容存在属性表集合中的Code属性里
    7). 属性表集合 : 在class文件, 字段表, 方法表都可以携带自己的属性集合, 用于描述默写场景的专有信息
 
字节码指令简介 :  
    由于限制了JVM操作码的长度为一个字节(即0-255), 这意味着操作吗总数不能超过256, 又由于class文件格式放弃了编译后代码的操作数长度对齐, 意味着虚拟机在处理超过一个字节的数据时候, 不得不在运行时从字节中重建出具体的数据结构, 这种操作会在执行字节码时损失些性能, 但同时也可以省略很多填充和间隔符号, 也是为了尽可能的获得短小精干的编译代码, 追求尽可能小的数据量, 高效率传输
    1). 字节码与数据类型 : 对于大部分与数据类型相关的字节码指令, 它们的操作码助记符中都有特殊的字符来表名专门为哪种数据类型提供服务, 大多数对于boolean, buyte, short和char类型数据的操作, 实际上都是用相应的int类型作为运算类型, 这样可尽量节省操作码数量成本
    2). 加载和存储指令 : 大致可分为四类 : 将一个局部变量加载到操作数栈 eg: dload ; 将一个数值从操作数栈存储到局部变量表 eg: istore; 将一个常量加载到操作数栈 eg: dconst_<d>; 扩充局部变量表的访问索引的指令 : wide 
    3). 运算指令 : 算术指令包括如下类别 : 加法, 减法, 乘法, 除法, 求余, 取反, 位移, 按位或, 按位与, 按位异或, 局部变量自增指令, 比较指令
    4). 类型转换指令 : JVM直接支持宽化转换, 处理窄化类型转换时, 必须显示使用转换指令来完成, 尽管窄化发生上限溢出,下限溢出和精度丢失等情况, 但JVM规范中明确规定窄化转换过程中永远不会抛出运行时异常
    5). 对象创建与访问指令 : 分为: 创建类实例的指令, 创建数组的指令, 访问类字段和实例变量的指令, 把一个数组元素加载到操作数栈的指令, 讲一个操作数栈的值存储到数组元素中的指令, 取数组长度的指令, 检查类实例类型的指令
    6). 操作数栈管理指令 : 将操作数栈的栈顶元素出栈, 将栈顶的两个元素值互换, 复制栈顶的数值并将复制的值重新压入栈顶
    7). 控制转移指令 : 分为: 条件分支, 复合条件分支, 无条件分支
    8). 方法调用和返回指令 : invokevirtual, invekeinterface, invokespecial, invokesatic, invekedynamic, ireturn...
    9). 异常处理指令 : 现在是使用异常表完成的
    10). 同步指令 : 使用管程来支持的, 正确使用synchronized需要javac编译器和JVM两者协作支持, synchronized为内置锁会保证执行monitorexit指令释放锁
小结 : 
    对所有class文件格式的改进, 都集中在访问标识, 属性表这些设计上就可以扩展的数据结构中添加内容
 
二 : 虚拟机类加载机制
  •     JVM把描述类的数据从class文件加载到内存, 并对数据进行校验, 转换解析和初始化最终形成可被虚拟机使用的java类型, 这就是虚拟机的类加载机制, 在java里面, 类型的      加载, 连接和初始化过程都是在程序运行期间完成的, 这种策略虽然会令类加载时候增加一些性能开销, 但是会为java程序提供高度的l灵活性, java里天生可以动态扩展的语      言特性就是依赖运行期动态加载和动态连接实现的
 
类加载的时机 : 
    类从加载到虚拟机到被卸载出内存为止, 整个生命周期包括: 加载, 验证, 准备, 解析, 初始化, 使用, 卸载, 其中 加载,验证,准备,初始化和卸载这5个阶段的顺序是确定的, 类在加载过程必须按照这种顺序开始, 而解析阶段不一定: 它在某些情况下可以在初始化后再开始, 之所以强调按顺序开始, 而不是按顺序进行或完成, 是因为这些阶段通常是互相交叉的混合式进行的, 通常会在一个阶段执行的过程中调用另外一个阶段, 而解析阶段则例外, 可能在初始化之后再开始(支持动态绑定)
类加载的过程 : 
    1). 加载 : JVM规范没有明确规定时机, 各JVM实现自己决定加载时机
            类加载是整个类装载的第一个阶段, 此阶段需完成三件事情
            ①: 通过一个类的全限定名来获取此类的二进制字节流
            ②: 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
            ③: 在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据访问入口
             * 数组类本身并不通过类加载器创建,而是通过Java虚拟机直接创建 
    2). 验证 : 
             ①: 文件格式验证
             ②: 元数据验证(语义分析)
             ③: 字节码验证
             ④: 符号引用验证
    3). 准备 : 为类变量分配内存并设置类变量初始值(零值)的阶段
    4). 解析 : 将符号引用转会为直接引用
    5). 初始化 : 有且只有几种情况下当虚拟机没有对类进行初始化时必须进行初始化动作, 
             ①: 遇到new, getstatic, putstatic, invokestatic这四个字节码指令时, 落到代码的场景通常是: new关键字对对象进行实例化时, 读取或者设置一个类的静态字段时, 以及调用一个类的静态方法时;
             ②: 当对类进行反射时
             ③: 当初始化一个类时, 如果发现其父类还没初始化, 先触发其父类的初始化
             ④: 虚拟机启动时, 用户需要制定一个执行的主类
             ⑤: 当Method实例最后解析结果REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄, 并且这个方法句柄所对应的类没有进行初始化
 
类加载器 : 
    1). 类与类加载器 : 比较两个类是否相等, 只有这两个类是由同一个类加载器加载的前提下才有意义, 即当两个类来源一致且由相同的类加载器加载才'相等'
    2). 双亲委派模型 : 
             ①: 启动类加载器 : BootstrapClassLoader,负责加载存放在 JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被 BootstrapClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
             ②: 扩展类加载器 : ExtensionClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader实现,它负责加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。
             ③: 应用类加载器 : ApplicationClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
    
双亲委派机制 : 
    1).当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成
    2).当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成
    3).如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
    4).若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException


 
   双亲委派模型
 
 
 
三 : 虚拟机字节码执行引擎
 
  • 运行时栈桢结构 : 
    1). 局部变量表 : 用于存放方法参数和方法内部定义的局部变量.在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量
    2). 操作数栈 : 是一个后入先出(Last In First Out,LIFO)栈.同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中.操作数栈的每一个元素可以是任意的Java数据类型,包括long和double.32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2.在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值.
    3). 动态链接 : 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking).我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数.这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析.另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接.(静态分派,动态分派)
    4). 方法返回地址 : 方法退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等
    5). 附加信息 : 虚拟机自定义实现


 
运行时栈桢结构
 
方法调用 : 
    1). 解析 : 解析调用时一个静态的过程, 在编译期间就完全确定, 在类装载的解析阶段就会把涉及的符号引用全部转换为直接引用(invokestatic, invokespecial都可在解析阶段确定调用版本)
    2). 分派 : 分为静态分派和动态分派, 以及单分派和多分派(依据宗量数)invokevirtual指令大部分场景
                  所有依赖静态类型来定位方法执行版本的分派动作称为静态分派.其典型应用是方法重载(根据参数的静态类型来定位目标方法).
                  静态分派发生在编译阶段.因此确定静态分派的动作实际上不是由虚拟机执行的.
                  在运行期根据实际类型确定方法执行版本
    3). 动态类型语言支持 : invokedynamic指令
 
基于栈的字节码解释执行引擎 : 
    1). 解释执行 :


 
解释执行
    2). 基于栈的指令集和基于寄存器的指令集 : 
        java编译器输出的指令流, 基本上都是基于栈的指令集(ISA), 不同于基于寄存器的指令集, 基于栈的指令集的一个优点是可移植, 因为寄存器由硬件提供,程序依赖于寄存器不能避免会受到硬件的约束, 主要缺点是相对来说慢一些, 因为要进行很多入栈出栈等操作
    3). 基于栈的解释器的执行过程 :    
        主要是由栈桢中的程序计数器, 局部变量表, 操作数栈协同工作, 实际的虚拟机实现会做出一些优化来提升性能, 实际的运作过程可能不是很符合模型概念的描述, 主要的原因是虚拟机中的解释器和即时编译器都会对输入的字节码进行优化
 
四 : 类加载及执行子系统的案例
 
  • class文件和执行引擎部分中, 能通过程序进行操作的, 主要是字节码生成与类加载器这两部分功能
 
Tomcat: 正统的类加载器架构 : 


 
OSGI: 灵活的类加载器架构 : 


 
字节码生成技术与动态代理的实现 :  
        ASM生成字节码以及CGlib和JDK动态代理的实现
 
  • last : 真实的虚拟机运作过程由于会做出各种优化, 和概念模型可能会有很大的差异, 但从最终执行结果来看应该是一致的
 
 
  • 大小: 19 KB
  • 大小: 28.5 KB
  • 大小: 107.8 KB
  • 大小: 12.5 KB
  • 大小: 62.3 KB
  • 大小: 49.3 KB
分享到:
评论

相关推荐

    JVM执行子系统原理

    ### JVM执行子系统原理 #### 一、类文件结构与字节码指令 **Class类文件结构** Class文件是Java程序编译后产生的文件,它采用了一种类似于C语言结构体的形式来组织数据,其中各个数据项目紧密排列,没有任何额外...

    JVM执行子系统.pdf

    在深入理解JVM执行子系统之前,我们需要了解Java类文件结构,以及JVM如何与这些类文件交互。 ### Java跨平台基础 Java语言之所以能够跨平台运行,是因为它实现了平台无关性。这种无关性是通过Java程序编译后的字节...

    JVM执行子系统.docx

    JVM 执行子系统 Java 虚拟机(JVM)是 Java 程序语言的核心组件之一,它提供了一个平台无关性的执行环境,使得 Java 程序可以在不同的操作系统上运行。JVM 执行子系统是 Java 程序的核心组件,它负责将 Java 代码...

    JVM执行子系统.zip

    JVM执行子系统是JVM中的一个重要部分,负责解释和执行Java字节码,确保程序的运行。在面试中,对于Java开发者来说,深入理解JVM执行子系统是必不可少的技能。以下是对JVM执行子系统的详细解析: 1. 类加载器(Class...

    【面试资料】-(机构内训资料)JVM执行子系统.zip

    JVM执行子系统是JVM中的一个重要模块,它涉及到程序的启动、执行、优化等多个层面。以下是对JVM执行子系统的详细讲解: 1. 类加载机制: JVM执行子系统首先涉及到的是类加载过程,包括加载、验证、准备、解析和...

    JVM执行子系统-JVM进阶

    Java 虚拟机(JVM)是Java编程语言的核心组成部分,它负责解析和执行Class文件,这是一种跨平台的程序存储格式,确保了Java的“一次编写,到处运行”的特性。Class文件包含了Java虚拟机指令集、符号表和其他辅助信息...

    笔记,3、JVM的执行子系统2

    【标题】: JVM执行子系统解析 - Class文件结构详解 【描述】: 本文深入探讨JVM执行子系统的核心组成部分,特别是围绕Java Class文件结构展开,解析其在跨平台执行中的关键角色。 【正文】: Java虚拟机(JVM)是...

    JVM执行子系统Java系列2021.pdf

    JVM的关键在于它的字节码(ByteCode)机制,这是一种独立于具体硬件和操作系统的中间语言。字节码确保了Java程序能够在不同的平台上被解释和执行,这也是Java跨平台特性的重要基础。 `Class`文件是Java程序的基本...

    【JVM和性能优化】3.JVM的执行子系统

    字节码文件 .class文件的产生是最关键的,是Java语言跨平台的基础,.class文件跟不同的操作系统之间对接的差异性由JVM后台自动帮我们解决,我们只需要将代码编译成.class 字节码文件, Class类的本质 任何一个Class...

    jdk,jvm源码

    1. 类加载子系统:负责加载、验证、解析和初始化.class文件。加载阶段会找到类的二进制数据;验证阶段确保类数据符合Java语义和安全规定;解析阶段将符号引用转换为直接引用;初始化则执行类的静态初始化块。 2. ...

    浅谈jvm原理

    JVM 的运行机制可以分为三个主要的子系统:类加载器子系统、运行时数据区和执行引擎。类加载器子系统负责加载 Java 类文件,并将其转换为 JVM 可以识别的字节码。运行时数据区是 JVM 运行时的内存模型,它包括方法...

    深入JVM内核—原理、诊断与优化视频教程-3.常用JVM配置参数

    JVM主要由类加载子系统、运行时数据区、执行引擎、本地方法接口和本地方法库五个部分组成。其中,类加载子系统负责加载、验证、准备和初始化类文件;运行时数据区包括堆、栈、方法区、程序计数器和本地方法栈,它们...

    JVM:类加载器子系统.pdf

    Java虚拟机(JVM)的类加载器子系统是Java运行时环境的一个重要组成部分,它负责将.class文件加载到内存中,并生成对应的Java类对象。这一过程涵盖了从文件系统或网络获取.class文件、验证类文件的正确性、准备类...

    JVM必知必会

    JVM由多个子系统和组件构成: - **类加载子系统**:负责加载类文件到JVM。 - **执行引擎**:执行字节码。 - **运行时数据区**:存储类的实例、方法数据、程序计数器等信息。 - **本地接口**:与操作系统交互,为JVM...

    JVM详解与学习

    JVM的主要组成部分包括类加载器子系统、执行引擎子系统、运行时数据区域组件和本地接口组件。 ##### 2.2 Sun JVM Sun Microsystems 开发的JVM是最常见的JVM实现之一。它支持Java标准版(Java SE)、企业版(Java EE...

Global site tag (gtag.js) - Google Analytics