您的位置:首页 > 编程语言 > Java开发

JAVA虚拟机简介

2017-06-04 11:30 190 查看

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