《揭秘Java虚拟机:JVM设计原理与实现》学习笔记-第一章到第四章

一、Java虚拟机概述

知识点

  1. CS寄存器保存段地址,IP保存偏移地址。CSIP这两个寄存器的值能够唯一确定内存中的一个地址
  2. 函数跳转的本质其实便是修改CSIP这两个寄存器的内容,使其指向到目标函数所在内存的首地址,这样CPU便能执行目标函数了
  3. 中间语言由于其本身不能直接被CPU执行,为了能够被CPU执行,中间语言在完成同样一个功能时,需要准备更多便于自我管理的上下文环境,最后才能执行目标机器指令。准备上下文环境最终也是依靠机器码去实现,因此中间语言最终便生成了更多机器码,当然执行效率就降低了
  4. 通过编译器将Java语言翻译成中间语言,然后再交给虚拟机,其再将中间语言翻译成对应机器平台上的指令​
  5. JVM 内存分为操作数栈、局部变量表、Java 堆、常量池、方法区

常见汇编指令:

 

 

 

jvm指令:

 

 

二、Java执行引擎工作原理:方法调用

 

计算机核心3大功能:

 

 

知识点

  1. Linux平台上,栈是向下增长的,从内存的高地址往低地址方向增长,因此每次调用一个新的函数时,需要为新的函数分配栈空间,新函数的栈顶相对于调用者函数的栈顶,内存地址一定是低位方向,因此新函数的栈顶总是通过对调用者函数的栈顶做减法而计算出来
  2. CPU不支持将数据从一个内存位置直接传送到另一个内存位置,若要想实现这个效果,必须使用寄存器进行中转
  3. 编译器会将一个方法内的局部变量分配在靠近栈底的位置,而将传递的参数分配在靠近栈顶的位置
  4. add()函数的方法栈是在调用方main()函数的方法栈空间基础上往下增长的,并且add()方法栈与main()方法栈连在一起
  5. 物理机器执行call函数调用时,机器会自动将eip入栈。
  6. 物理机器执行函数调用时,被调用方需要手动将ebp入栈。
  7. 对于压栈的入参,既可以从通过相对于调用者函数的栈顶的偏移量来相对定位,也可以通过相对于被调用者函数的栈底的偏移量来相对定 
  8. 对于被调用者函数的方法栈内的数据,却不能以调用者函数为基准通过偏移量获取。因为此时被调用函数尚未分配方法栈空间,根本取不到数据,甚至会取到错误的数据。
  9. 函数返回的一般逻辑是,如果有返回值,就把返回值放在eax寄存器中,然后执行leaveret指令。如果没有返回值,则直接执行leaveret指令

方法栈示意图

 

 

物理机器调用函数过程:

 

 

小结

  1. 物理机器在执行程序时,将程序划分成若干函数,每个函数都对应有一段机器码。一段程序的机器码都放在一块连续的内存中,这块内存叫做代码段。物理机器为每一个函数分配一个方法栈,方法栈与代码段在地址上没有任何关系,并且只有当物理机器执行到某个函数时,才会为其分配方法栈,否则就不会分配。函数通过自身的机器指令遥控其对应的方法栈,可以往里面放入数值,也可以将数值移动到其他地方,也可以从里面读取数据,也可以从调用者的方法栈里取值。通过一条条指令和一个个栈,物理机器得以运行完一整个程序

知识点:

  1. 指针函数的返回类型是一个指针,而一般的函数声明所返回的则是普通变量类型。
  2. 函数指针声明的是一个指针,只不过这个指针与一般的指针不同,一般的指针指向一个变量的内存地址,而函数指针则指向一个函数的首地址。
  3. 函数指针作为C语言中的高级应用,是实现C语言动态扩展能力的关键技术之一,如同Java中的反射与类动态加载技术

三、Java数据结构与面向对象

 

知识点

  1. 一个class字节码文件主要由以下10部分组成:◎ MagicNumber Version Constant_pool Access_flag This_class Super_class Interfaces Fields Methods Attributes
  2. 目前已发布的Version包括:1.1(45)、1.2(46)、1.3(47)、 1.4(48)、1.5(49)、 1.6(50)、1.7(51)
  3. 常量池里放的是字面常量和符号引用
  4. 字面常量主要包含文本串以及被声明为final的常量。符号引用包含类和接口的全局限定名、字段的名称和描述符、方法的名称和描述符
  5. 常量池是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可选项

 

 

知识点

  1. 根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示。为了压缩字节码文件的体积(字节码文件最终也会占用服务器硬盘资源和内存资源),对于基本数据类型,JVM都仅使用一个大写字母来标识
  2. 对于数组类型,每一维将使用一个前置的“[”字符来描述,如“int[]”将被记录为“[I”, String[][]”将被记录为“[[Ljava/lang/String;”
  3. 用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组“( )”之内,如方法“String getAll(int id,String name)”的描述符为“(I,Ljava/lang/String;)Ljava/lang/String;”
  4. 在编译期间,编译器会自动为一个类增加void <clinit>()这样一个方法,其方法名就是“<clinit>”,返回值为void。该方法的作用主要是执行类的初始化,源程序中的所有static类型的变量都会在这个方法法中完成初始化,全部被static{}所包围的程序都在这个方法中执行
  5. 编译器会自动为该类添加一个默认的构造函数

 标识字符与基本数据类型对应表

 

 

methods结构组成

 

 方法的access_flags

 

 

知识点2

  1. 为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性
  2. 9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型。该u2类型的属性名称指向常量池中对应的元素。
  3. 对大多数文件,类名和文件名是一致的,少数特殊类除外(如内部类),此时如果不生成SourceFile属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名。
  4. 虽然JVM所支持的9大属性,其相互之间格式相差甚远,但是都会以一个u2类型的属性名称开始,JVM根据名称便可知道当前描述的到底是这9大属性中的哪一个属性
  5. 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

  1. 字节码文件中名字为<init>的方法表示的是类的构造函数,上文所描述的第一个方法名是<clinit>,这是类型的初始化方法。这两者的区别是:当JVM决定加载某个类型时,会调用<clinit>()方法,而当JVM决定实例化某个类型时,会调用<init>方法。一个是类型初始化,一个是类实例的初始化,两者有本质上的区别。并且<clinit>()一定先于<init>()方法被调用。

 

整理效果图

posted @ 2019-12-16 21:05  三思言  阅读(413)  评论(0编辑  收藏  举报