JAVA虚拟机简介

来源:互联网 发布:java 复制代码 编辑:程序博客网 时间:2024/05/16 09:45

Java虚拟机

这里写图片描述

注意:我们这里说的虚拟机是所谓的高级语言虚拟机, 并不是像Vmware那样, 完全虚拟一个硬件和操作系统出来。

此外java虚拟机上还可以运行clojure, scala , Jruby, Jptyon等语言

Java虚拟机列表:
- https://www.zhihu.com/question/29265430?sort=created
- https://my.oschina.net/yygh/blog/598187

CPU处理器与操作系统的整体叫平台,每种CPU都有其特定的指令集,不同的操作系统支持不同CPU的指令集。语言跨平台是编译后的文件跨平台 即.class文件,而不是源程序(.java文件)跨平台。由于cpu指令集的差异所以Java虚拟机在不同平台的实现是不一样的。这样就不会像汇编语言对平台的依赖性那么大。

虚拟机并不是Java的专利

这里写图片描述

Ruby, PHP, Python都有自己的虚拟机

为什么要用虚拟机?

  • 跨平台
    • CPU指令集不用
    • 操作系统接口不同
  • 效率更高
    • 相对于解释型语言
  • 抽象层次高,更容易编程
    • 消除指针
    • 不用管理内存
    • ……

这里写图片描述

Class 二进制文件一览

这里写图片描述

先来直观的看一下一个class 文件的格式,使用16进制的方式来展示二进制的数据。

这里写图片描述

看起来很乱的一个个字节其实是有严格次序的, 这些次序就是java 虚拟机所规定的U4, U2中的 U 指的是无符号数 U4 就是4个字节, U2 就是2个字节。先简单介绍下部分区域的作用,以后会继续写专题博客。常量池:的作用是存放 类名,方法名,超类,字段名等。你可能会有疑问类名为什么保存? C++编译地址,而java是动态链接,每次都是通过名称来找类。属性:jvm非常重要的一部分描述方法中或字段的信息 metadata(元数据),元数据就是描述数据的数据。

魔术与版本号,常量池个数

这里写图片描述

  • Magic Number

    • 每个class文件的头4个字节称为魔术(Magic Number),它的唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件。
  • Major/Minor Version : 版本号

  • 16进制
    Major Version (0x34) = 52
  • 常量池个数 (0x36) = 54
    • 大端模式(Big-Endian):高位在前
    • 00 36 vs 36 00

注意:小端模式: 低位在前。JVM用的是大段模式.物理的CPU用的可能是小端模式, 所以JVM与底层交互时地址值需要做转换。

常量池

这里写图片描述

想象一下, 我们解析这个class的时候,该怎么处理? 遇到了”07”,我们知道这是一个class info , 就去取后面的两个字节当成index , 遇到01 就知道这是一个UTF8Info …,这些数值在JAVA虚拟机规范中都有定义。

注意:与JAVA中语言习惯不同的是,这个容量计数器是从1而不是0开始的,如上图,常量池容量为0x0036,即十进制的54,这就代表常量池中有53项常量,索引值的范围为1~53。

常量池例子

这里写图片描述

刚才咱们看到了常量池中的两项: Class Info , UTF8 Info , 这里列出了更多的常量项, 注意我们最最常用的几个 MethodRef, NameAndType, FieldRef注意他们之间的关系。详细信息请参考JAVA虚拟机规范。你可能会注意到以下这些特殊字符 I : int , L : 代表类, [ : 一维数组, ()V:没有返回值的方法。

几种最常见的结构

CONSTANT_Fieldref_info {           u1 tag;       //值为9    u2 class_index;           u2 name_and_type_index; } CONSTANT_Methodref_info {        u1 tag;         //值为10    u2 class_index;           u2 name_and_type_index; }CONSTANT_NameAndType_info {      u1 tag;     //值为12    u2 name_index;      u2 descriptor_index; }

注意:详情请参见《java虚拟机规范》

访问标志(Access Flags) : U2

这里写图片描述
这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。

类索引(U2) 、父类索引(U2)

这里写图片描述

思考问题:为什么需要记录this class?

字段(Field)

字段表(field_info)用于描述接口或类中声明的变量

这里写图片描述

u2 fields_count;   // 有多少个字段field_info {      u2 access_flags;   // 例如是public , private 等等    u2 name_index;   // 指向常量池的入口    u2 descriptor_index;   //指向常量池的入口    u2 attributes_count;   // 该字段的属性有多少个    attribute_info attributes[attributes_count];  //属性信息}

标志字符含义

这里写图片描述

方法(method)

这里写图片描述
例子:左边是JVM中的信息 右边是源代码(参数名省略用”xx”)
(Ljava/lang/String;)V —> void( String xx)
(Ljava/lang/String;IF)V —> void ( String xx, int xx, float xx )

属性(class 文件中最复杂的部分)

  • 截至Java SE7, 已经有21个属性

  • 方法和字段都可能有属性

    • 例如:方法中有Code属性, 字段有Constant Value属性
  • 属性中可能有嵌套属性

    • Code属性中还有Line Number Table, Local Variable Table,Stack Map Table 等属性
  • 可以自定义属性

Constant Value

如果某字段为静态类型( access_flags 中包含 ACC_STATIC 标志),
将会被分配 ConstantValue 属性

ConstantValue_attribute {      //必须是一个对常量池的有效索引。常量池在该索引处的项必须是UTF8Info,表示字符串“ConstantValue”。    u2 attribute_name_index;       //固定为2    u4 attribute_length;      //必须是一个对常量池的有效索引。常量池在该索引处的项给出该属性表示的常量值, 可能的值有Constant_String, Constant_Long…….    u2 constantvalue_index; }

Code属性

Code_attribute {    u2 attribute_name_index;  //指向常量池,应该是UTF8Info ,值为”Code”  u4 attribute_length;             //属性长度, 不包括开始的6个字节  u2 max_stack;                       // 操作数栈的最大深度(注:编译时已经确定)  u2 max_locals;                      // 最大局部变量表个数  u4 code_length;                    // 该方法的代码长度  u1 code[code_length];         //真正的字节码  u2 exception_table_length;      {         u2 start_pc;           u2 end_pc;           u2 handler_pc;           u2 catch_type;  } exception_table[exception_table_length];    u2 attributes_count;    attribute_info attributes[attributes_count]; } 

Code属性中的字节码

这里写图片描述

真正的字节码就是一个个字节而已。但是,在执行的过程中需要把他们拆解成操作码和操作数两个部分, 特别需要注意的是, 每个操作码所需要的操作数不一定一样! , 有的没有操作数(2A , aload_0), 有的有一个(10 , 1E -> bipush 30),有的有两个,例如 A2 00 0E (if_icmp_ge 20)对于操作数而言, 有的就是立即数,例如goto 28 , bipush 30 , 有的是指向常量池的索引。每个操作码的具体含义可以在《Java虚拟机规范》中找到

LineNumberTable属性

LineNumberTable_attribute {  u2 attribute_name_index;  u4 attribute_length;  u2 line_number_table_length;  {     u2 start_pc;    // 字节码偏移量    u2 line_number;   //行号  } line_number_table[line_number_table_length];}

可选的变长属性, 描述Java 源码行号与字节码行号(字节码偏移量)之间的对应关系, 调试器可以使用, 属于Code属性

LocalVariableTable属性

LocalVariableTable_attribute {  u2 attribute_name_index;  u4 attribute_length;  u2 local_variable_table_length;  {    u2 start_pc;    //局部变量位于 [start_pc, start_pc+length) 之间    u2 length;       u2 name_index;    //局部变量的名称索引    u2 descriptor_index;  //局部变量的描述符索引    u2 index;                      //局部变量在栈帧中的索引  } local_variable_table[local_variable_table_length]; }

是可选变长属性,描述栈帧中局部变量表中的变量和java 源码中定义的变量之间的关系, 属于Code属性

运行时结构图

这里写图片描述
每个程序运行起来至少都有一个线程,每个线程都有一个函数帧。JVM细分了函数栈。操作数栈的作用:java是基于栈的虚拟机,例如想把两个数进行相加的话必须把操作数A压入栈,与操作数B压入栈,然后把两个数相加再压入栈,然后再弹出栈。操作数栈与局部变量都会引用堆上的对象。常量池的引用在方法区,方法区存放了这个类的数据。

一个例子

编译前与编译后

这里写图片描述

执行时函数栈帧

这里写图片描述

0:aload_0 说明把局部变量表第0哥元素压入栈

形成新的函数栈帧

这里写图片描述

先把demo栈帧的三个数copy到新栈帧的局部变量表中,然后开始执行add操作数栈。
什么是动态链接?名称与对象对应起来,方便以后的操作。
有三个概念需要清楚:

常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量。

字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。

运行时常量池(Runtime Constant Pool): 方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。

部分内容参考自:
《深入理解java虚拟机》
《JAVA虚拟机规范》
 Wanna的博客