以下所有内容来自于 《深入理解 Java 虚拟机》这本书的理解和回顾,非照抄原文,肯定有错误,也有故意致错,一切皆为方便理解

Java 内存区域 奠定了Java 的基础模型,GC 在这其中周而复始,

那么在这之前,在 Java 程序运行之前,jvm 又做了那些事?

# 课前准备 - 类文件结构

类加载的是 class 文件,这里先大概了解以下 class 文件结构

概念

class 文件是专门给 jvm 使用的,对比机器码和操作系统,class 之于 jvm

各个平台上的 虚拟机都统一支持 class 文件

[五]虚拟机执行子系统

只要是合乎规范的calss文件都能被虚拟机加载

结构

将class 文件编译成二进制,前四个字节是“魔数”,用来做身份校验,判断是否是合法的 class 文件,类比猪肉盖章

5.6字节是次版本号,7.8是主版本号,jdk1.0主版本 45 ,新版本 + 1,判断是那个 JDK 编译的,以保证高低版本的兼容性.检测

之后就是常量池入口,访问标志等等,由字段表,属性表,方法表等组成

class 文件包含了一个程序代码的所有内容以及添加的一些额外东西,就像厨师炒菜前把菜品分门别类的准备好,放在一起

字节码指令

类似于 x86 指令集,Java 也有自己的指令集 ,指令参数都存放在操作数栈中

Java 虚拟机采用面向操作数栈的架构(桶状),windows 是面向寄存器(蜂窝状),前者效率低,不占空间,后者以空间换时间,

# 类加载

概念:Java 虚拟机把描述类的数据从 class 文件中加载到内存,并对数据进行校验,解析,初始化,最终形成可以直接被虚拟机使用的 Java 类型的过程

Java 的类加载是在程序运行期间完成,比之提前编译好,效率低,扩展性好(运行时可以动态链接)

过程

[五]虚拟机执行子系统

加载:

  • classlode 根据 classpath 提供的全限定名获取该类的二进制字节流加载到内存(按照一定规则存放在方法区)
  • 由于最终只需要一个二进制字节流,所以可以从任何地方获取,网络,磁盘,压缩包等等
  • 加载过程也可以使用用户自定义类加载器

验证:如果有变态通过手敲 0 1 二进制代码,并放入内存,就可能存在安全性

  • 文件格式验证:class 文件格式规范
  • 元数据验证;对字节码描述的信息进行语义分析
  • 字节码验证:验证分析出来的语义是合法的,安全的,由于该过程较为复杂且耗时,官方将尽可能多的验证提前到编译期
  • 符号引用:在虚拟机将符号引用转化成直接引用时发生 – 解析式中端发生,检查引用是否存在,合法

准备:

  • 静态变量分配内存,并设置初始值(零值,不是程序员代码的初始值)

解析:

  • 符号引用替换为直接引用

在此之前的动作都有虚拟机主导完成,之后的步骤则有用户程序接管

初始化:

  • 正式根据程序初始化类变量和其他资源
  • 初始化阶段就是执行 () 方法的过程

# 双亲委派

在类加载过程中,加载阶段可以自定义类加载器,否则默认使用虚拟机的类加载器

那么虚拟机的类加载器是什么样子呢?

Java 的三层加载器

  • 启动类加载器 Bootstrap Class Loader:加载 JDK 下 lib 文件中的类
  • 扩展类加载器 Extension Class Loader:加载官方提供的扩展类库的类
  • 应用程序类加载器:Application Class Loader:系统类加载器,加载用户类路径上的类库

[五]虚拟机执行子系统

双亲委派

加载类时,首现由级别高的加载,没有时,再由下一级加载 ,遇到类加载请求时,先交给父类加载,父类找不到时,再有子类加载

如果一个人写了一个 String 类,里面存在恶意代码,Application Class Loader 直接就把这个类加载到内存中执行了,所有依赖于 String 的类就会有问题,等于是把官方的 String 替换掉了

反之,如果交到 启动类加载器手里,它在核心类库中找到了 官方的 String 类,此时就不会加载用户的恶意 String 的代码了

# 字节码执行

类加载完毕后,就开始执行,由于 Java指令集是基于 栈的,所有操作都在栈上进行

  • Java 虚拟机以 “栈帧” 作为最基本的执行单元

  • 栈帧需要多大的局部变量表和操作数栈,在编译器已经确定

  • 只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是有效的

#分派

Java 中对方法的调用,确定目标方法的过程,多态性的体现,

静态分派:

在编译器确定,在类加载是解析

静态分派只涉及 重载 ,本质是根据方法的参数,确定实际的方法

动态分派

概念:运行期根据方法接受者的实际类型选择方法版本

方法的重写就是使用动态分派,父子类同名方法是怎么识别的?

静态分派只涉及 重载 ,本质是根据方法的参数,确定实际的方法

动态分派

概念:运行期根据方法接受者的实际类型选择方法版本

方法的重写就是使用动态分派,父子类同名方法是怎么识别的?

假设 A a = new B(子类),在执行过程中的 invokevirtual 指令,会根据实际类型,也就是 B 找到复合的方法,否则按照继承关系从上到下寻找最合适的方法,如果找不到,就抛出异常,这就是 重写 的本质

相关文章:

  • 2021-11-20
  • 2021-04-11
  • 2021-11-13
  • 2021-07-17
  • 2021-09-26
  • 2021-10-16
  • 2022-12-23
  • 2021-08-22
猜你喜欢
  • 2021-12-05
  • 2021-12-13
  • 2021-06-21
  • 2021-04-05
  • 2021-11-13
  • 2021-08-25
相关资源
相似解决方案