一、Java虚拟机概述
知识点
CS寄存器保存段地址,IP保存偏移地址。CS和IP这两个寄存器的值能够唯一确定内存中的一个地址
函数跳转的本质其实便是修改CS和IP这两个寄存器的内容,使其指向到目标函数所在内存的首地址,这样CPU便能执行目标函数了
中间语言由于其本身不能直接被CPU执行,为了能够被CPU执行,中间语言在完成同样一个功能时,需要准备更多便于自我管理的上下文环境,最后才能执行目标机器指令。准备上下文环境最终也是依靠机器码去实现,因此中间语言最终便生成了更多机器码,当然执行效率就降低了
通过编译器将Java语言翻译成中间语言,然后再交给虚拟机,其再将中间语言翻译成对应机器平台上的指令
JVM 内存分为操作数栈、局部变量表、Java堆、常量池、方法区
常见汇编指令:
jvm指令:
二、Java执行引擎工作原理:方法调用
计算机核心3大功能:
知识点
在Linux平台上,栈是向下增长的,从内存的高地址往低地址方向增长,因此每次调用一个新的函数时,需要为新的函数分配栈空间,新函数的栈顶相对于调用者函数的栈顶,内存地址一定是低位方向,因此新函数的栈顶总是通过对调用者函数的栈顶做减法而计算出来
CPU不支持将数据从一个内存位置直接传送到另一个内存位置,若要想实现这个效果,必须使用寄存器进行中转
编译器会将一个方法内的局部变量分配在靠近栈底的位置,而将传递的参数分配在靠近栈顶的位置
add()函数的方法栈是在调用方main()函数的方法栈空间基础上往下增长的,并且add()方法栈与main()方法栈连在一起
物理机器执行call函数调用时,机器会自动将eip入栈。
物理机器执行函数调用时,被调用方需要手动将ebp入栈。
对于压栈的入参,既可以从通过相对于调用者函数的栈顶的偏移量来相对定位,也可以通过相对于被调用者函数的栈底的偏移量来相对定
对于被调用者函数的方法栈内的数据,却不能以调用者函数为基准通过偏移量获取。因为此时被调用函数尚未分配方法栈空间,根本取不到数据,甚至会取到错误的数据。
函数返回的一般逻辑是,如果有返回值,就把返回值放在eax寄存器中,然后执行leave和ret指令。如果没有返回值,则直接执行leave和ret指令
方法栈示意图
物理机器调用函数过程:
小结
物理机器在执行程序时,将程序划分成若干函数,每个函数都对应有一段机器码。一段程序的机器码都放在一块连续的内存中,这块内存叫做代码段。物理机器为每一个函数分配一个方法栈,方法栈与代码段在地址上没有任何关系,并且只有当物理机器执行到某个函数时,才会为其分配方法栈,否则就不会分配。函数通过自身的机器指令遥控其对应的方法栈,可以往里面放入数值,也可以将数值移动到其他地方,也可以从里面读取数据,也可以从调用者的方法栈里取值。通过一条条指令和一个个栈,物理机器得以运行完一整个程序
知识点:
指针函数的返回类型是一个指针,而一般的函数声明所返回的则是普通变量类型。
函数指针声明的是一个指针,只不过这个指针与一般的指针不同,一般的指针指向一个变量的内存地址,而函数指针则指向一个函数的首地址。
函数指针作为C语言中的高级应用,是实现C语言动态扩展能力的关键技术之一,如同Java中的反射与类动态加载技术
三、Java数据结构与面向对象
知识点
一个class字节码文件主要由以下10部分组成:◎MagicNumber◎Version◎Constant_pool◎Access_flag◎This_class◎Super_class◎Interfaces◎Fields◎Methods◎Attributes
目前已发布的Version包括:1.1(45)、1.2(46)、1.3(47)、1.4(48)、1.5(49)、1.6(50)、1.7(51)
常量池里放的是字面常量和符号引用
字面常量主要包含文本串以及被声明为final的常量。符号引用包含类和接口的全局限定名、字段的名称和描述符、方法的名称和描述符
常量池是Java字节码文件中比较重要的概念,是整个Java类的核心所在,因为常量池中记录了一个Java类的所有成员变量、成员方法和静态变量与静态方法、构造函数等全部信息,包括变量名、方法名、访问标识、类型信息等
class文件组成成份-10份
四、Java字节码实战
常量池数组组成结构
常量池元素一览表
编号
常量池元素名称
tag位标识
含义
1
CONSTRANT_Utf8_info
1
UTF-8编码的字符串
2
CONSTRANT_Integer_info
3
整型字面量
3
CONSTRANT_Float_info
4
浮点型字面量
4
CONSTRANT_Long_info
5
长整型字面量
5
CONSTRANT_Double_info
6
双精度字面量
6
CONSTRANT_Class_info
7
类或接口的符号引用
7
CONSTRANT_String_info
8
字符串类型的字面量
8
CONSTRANT_Fieldref_info
9
字段的符号引用
9
CONSTRANT_Methodref_info
10
类中方法的符号引用
10
CONSTRANT_InterfaceMathodref_info
11
接口中方法的符号引用
11
CONSTRANT_NameAndType_info
12
字段和方法的名称以及类型的符号引用
常量池元素结构
类的access_flags可选项
fields结构组成
变量的access_flags可选项
知识点
根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示。为了压缩字节码文件的体积(字节码文件最终也会占用服务器硬盘资源和内存资源),对于基本数据类型,JVM都仅使用一个大写字母来标识
对于数组类型,每一维将使用一个前置的“[”字符来描述,如“int[]”将被记录为“[I”,“String[][]”将被记录为“[[Ljava/lang/String;”
用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组“()”之内,如方法“StringgetAll(intid,Stringname)”的描述符为“(I,Ljava/lang/String;)Ljava/lang/String;”
在编译期间,编译器会自动为一个类增加void()这样一个方法,其方法名就是“”,返回值为void。该方法的作用主要是执行类的初始化,源程序中的所有static类型的变量都会在这个方法法中完成初始化,全部被static{}所包围的程序都在这个方法中执行
编译器会自动为该类添加一个默认的构造函数
标识字符与基本数据类型对应表
methods结构组成
方法的access_flags
知识点2
为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性
这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型。该u2类型的属性名称指向常量池中对应的元素。
对大多数文件,类名和文件名是一致的,少数特殊类除外(如内部类),此时如果不生成SourceFile属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名。
虽然JVM所支持的9大属性,其相互之间格式相差甚远,但是都会以一个u2类型的属性名称开始,JVM根据名称便可知道当前描述的到底是这9大属性中的哪一个属性
attribute_name_index,指向常量池的索引,查找对应的属性表名称
9大属性及其介绍
Code属性结构表
类型
名称
数量
说明
u2
attribute_name_index
1
u4
attribute_length
1
u2
max_stack
1
操作数栈深度最大值
u2
max_locals
1
局部变量表所需存储空间,Slot
u4
code_length
1
字节码长度,jvm限制一个方法不能超过65535条字节码指令,否则拒绝编译
u1
code
code_length
存放java源码编译后生成的字节码指令,字节流,每个指令都是u1单字节,一个字节能表示256种指令,jvm定义了约200个
u2
exception_table_length
1
exception_info
exception_table
exception_table_length
u2
attributes_count
1
attribute_info
attributes
attributes_count
ConstantValue属性表
Exceptoins属性表
InnerClasses属性表
inner_classes_info表
LineNumberTable属性表
line_number_info表
LocalVariableTable属性表
local_variable_info表
SourceFile属性表
Synthetic和Deprecated属性表 - attribute_length = 0
知识点3
字节码文件中名字为的方法表示的是类的构造函数,上文所描述的第一个方法名是,这是类型的初始化方法。这两者的区别是:当JVM决定加载某个类型时,会调用()方法,而当JVM决定实例化某个类型时,会调用方法。一个是类型初始化,一个是类实例的初始化,两者有本质上的区别。并且()一定先于()方法被调用。
整理效果图