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

一、Java虚拟机概述

知识点

CS寄存器保存段地址,IP保存偏移地址。CS和IP这两个寄存器的值能够唯一确定内存中的一个地址

函数跳转的本质其实便是修改CS和IP这两个寄存器的内容,使其指向到目标函数所在内存的首地址,这样CPU便能执行目标函数了

中间语言由于其本身不能直接被CPU执行,为了能够被CPU执行,中间语言在完成同样一个功能时,需要准备更多便于自我管理的上下文环境,最后才能执行目标机器指令。准备上下文环境最终也是依靠机器码去实现,因此中间语言最终便生成了更多机器码,当然执行效率就降低了

通过编译器将Java语言翻译成中间语言,然后再交给虚拟机,其再将中间语言翻译成对应机器平台上的指令​

JVM 内存分为操作数栈、局部变量表、Java堆、常量池、方法区

常见汇编指令:

-

4dabf9918a70aef0d55d8924cee54870.png

jvm指令:

cc19a1fa6012af90d868f4302047e862.png

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

计算机核心3大功能:

7df41a015fdf6e772a0a5c5c80d882dd.png

知识点

在Linux平台上,栈是向下增长的,从内存的高地址往低地址方向增长,因此每次调用一个新的函数时,需要为新的函数分配栈空间,新函数的栈顶相对于调用者函数的栈顶,内存地址一定是低位方向,因此新函数的栈顶总是通过对调用者函数的栈顶做减法而计算出来

CPU不支持将数据从一个内存位置直接传送到另一个内存位置,若要想实现这个效果,必须使用寄存器进行中转

编译器会将一个方法内的局部变量分配在靠近栈底的位置,而将传递的参数分配在靠近栈顶的位置

add()函数的方法栈是在调用方main()函数的方法栈空间基础上往下增长的,并且add()方法栈与main()方法栈连在一起

物理机器执行call函数调用时,机器会自动将eip入栈。

物理机器执行函数调用时,被调用方需要手动将ebp入栈。

对于压栈的入参,既可以从通过相对于调用者函数的栈顶的偏移量来相对定位,也可以通过相对于被调用者函数的栈底的偏移量来相对定

对于被调用者函数的方法栈内的数据,却不能以调用者函数为基准通过偏移量获取。因为此时被调用函数尚未分配方法栈空间,根本取不到数据,甚至会取到错误的数据。

函数返回的一般逻辑是,如果有返回值,就把返回值放在eax寄存器中,然后执行leave和ret指令。如果没有返回值,则直接执行leave和ret指令

方法栈示意图

a37cb08d40d789c2dec6ca8bd889f6fe.png

物理机器调用函数过程:

d7f49fd4727295f06cf9ec599143ba04.png

小结

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

知识点:

指针函数的返回类型是一个指针,而一般的函数声明所返回的则是普通变量类型。

函数指针声明的是一个指针,只不过这个指针与一般的指针不同,一般的指针指向一个变量的内存地址,而函数指针则指向一个函数的首地址。

函数指针作为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份

7bf834978296b505c940f62969b82bad.png

四、Java字节码实战

常量池数组组成结构

3890ea255c7277003faca1c727ca8009.png

常量池元素一览表

编号

常量池元素名称

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

字段和方法的名称以及类型的符号引用

常量池元素结构

f46ee6763ea3d1c4b42d46d42c47f4e1.png

类的access_flags可选项

3530f51d59f030ffb56fce697218611d.png

3406351e5957dfd8ef778979c608ba5f.png

fields结构组成

getImage?fileId=5dcd565540178d2d04000005

f7ec3fb50fefec2f6a1dc4b338376d02.png

变量的access_flags可选项

9db012b0348b73be58ae844546618f70.png

知识点

根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示。为了压缩字节码文件的体积(字节码文件最终也会占用服务器硬盘资源和内存资源),对于基本数据类型,JVM都仅使用一个大写字母来标识

对于数组类型,每一维将使用一个前置的“[”字符来描述,如“int[]”将被记录为“[I”,“String[][]”将被记录为“[[Ljava/lang/String;”

用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组“()”之内,如方法“StringgetAll(intid,Stringname)”的描述符为“(I,Ljava/lang/String;)Ljava/lang/String;”

在编译期间,编译器会自动为一个类增加void()这样一个方法,其方法名就是“”,返回值为void。该方法的作用主要是执行类的初始化,源程序中的所有static类型的变量都会在这个方法法中完成初始化,全部被static{}所包围的程序都在这个方法中执行

编译器会自动为该类添加一个默认的构造函数

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

6000c9a9471cd0b5c365aeec61d2703f.png

methods结构组成

34720aa796faf2846acd42e7780418e4.png

方法的access_flags

932d0e82ab6a0d4f37513eb5ad47e725.png

知识点2

为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性

这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型。该u2类型的属性名称指向常量池中对应的元素。

对大多数文件,类名和文件名是一致的,少数特殊类除外(如内部类),此时如果不生成SourceFile属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名。

虽然JVM所支持的9大属性,其相互之间格式相差甚远,但是都会以一个u2类型的属性名称开始,JVM根据名称便可知道当前描述的到底是这9大属性中的哪一个属性

attribute_name_index,指向常量池的索引,查找对应的属性表名称

9大属性及其介绍

89e5876b864bbe6ae2d26a4e95ea0ec1.png

51414c9d7102de6c8842477f813505e9.png

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属性表

54d4c600295616bee3ad2b98c72756ca.png

Exceptoins属性表

adeb5598912c3bdea764a8c5244a6de6.png

InnerClasses属性表

ebe42092ce5e8950ce2b7c28db5a7b73.png

inner_classes_info表

075f85ff23f7b605ad73613cbdb8d37c.png

LineNumberTable属性表

d7e5fed70d70adc083c53151b53e26d8.png

line_number_info表

59d631f2e7aa9bbcdbac9a4a99e1bd13.png

LocalVariableTable属性表

0223fe48059d756fed8f2dba68e6ee48.png

local_variable_info表

d351f9e7e4911b682e86b6729db64f7d.png

SourceFile属性表

26e99c744511aada56c6aa4d9140f5f1.png

Synthetic和Deprecated属性表 - attribute_length = 0

41309eafc42425db7bc8c786c2f5b142.png

知识点3

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

整理效果图

c2c2fe94f1bb990f9ef5ea5525651cb7.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值