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的博客
相关文章推荐
- Java虚拟机(一):JVM简介
- Java虚拟机性能管理神器 - VisualVM(1) 简介 - JVM轻量级监控分析神器
- [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式
- 《Java虚拟机规范》阅读(一):简介和Java虚拟机结构
- [深入理解Java虚拟机]第六章 字节码指令简介
- [转]Java虚拟机(JVM)简介
- Java虚拟机规范阅读(二)IEEE754简介以及Java虚拟机中的浮点算法
- Java 虚拟机学习(1)---java虚拟机简介
- [Java学习] Java虚拟机(JVM)参数简介
- Android开发之JAVA虚拟机、Dalvik虚拟机和ART虚拟机简介
- p-unit简介 -- 一款开源的支持多线程,不同java虚拟机,性能测试工具
- Java虚拟机性能管理神器 - VisualVM(1) 简介 - JVM轻量级监控分析神器
- Java虚拟机学习之G1收集器简介
- Java虚拟机1:Java简介
- 【JVM】Java虚拟机简介
- JVM(java虚拟机)内存简介及常见内存溢出解决办法
- Java虚拟机系列之Java内存结构简介
- java虚拟机探索(一):jvm架构简介
- Java虚拟机简介
- Java虚拟机内存模型简介