Java虚拟机原理

一 概述

我们知道的虚拟机有很多,运行 Java 的 JVM 虚拟机,运行 Android 程序的 Davlik 和 Art 虚拟机,运行 C# 的 CLR 虚拟机,那么什么是虚拟机呢,虚拟机的作用又是什么呢?运行 JavaScript 的 v8 引擎或者运行 Python 的引擎是否也是虚拟机呢?带着这几个问题,我们开始对虚拟机的学习。

虽然现在很多人都认为运行 JavaScript 的 V8 或运行 Python 的 VirtualEnv,都不是虚拟机,而是解释器,主要原因是因为 V8 或者 VirtualEnv 不仅仅能执行字节码文件,还能将源文件编译成字节码文件,而传统上定义的虚拟机只是用来运行字节码文件的,如果将源文件编译成字节码,则需要编译器来帮忙,比如在 JVM 虚拟机上运行的文件都是已经编译成字节码的 class 文件,但是 V8 或者 Python,都能一边编译源代码,一边执行编译后的字节码文件。但是现在这个规范已经越来越宽松了,也有不少大神认为 V8 或者 VirtualEnv 也是虚拟机。

那么一个虚拟机具备什么样的能力呢?我们下面就来具体看看吧。

1.将源码编译成字节码(编译器能力)

2.装载字节码文件(加载,链接,初始化)

3.内存管理

  • 运行时内存区域
  • 垃圾回收

4.指令解析和执行

接下来主要以 JVM,Davlik 和 Art 三款虚拟机为例,分别介绍上述的能力。

二 将源码编译成字节码

2.1 class字节码

java 的字节码文件是通过 java 编译器来生成的,我们下载 jdk 后,通过 javac 命令,就可以将一个 java 源文件生成 java 字节码文件,这个字节码文件就可以直接在 JVM 上面运行了。

编译器通过对源代码进行词法,语法,语义分析,生成抽象语法树(AST),然后根据 AST,生成目标文件。


词法,语法,语义这一流程不是 java 编译器独有的,是所有的编译器都共有的能力,不管是 llvm 编译 c 文件,或者是我们解析如 html,xml 等 dsl 文件,都是这样的步骤。解析完成后的字节码文件如下。
在这里插入图片描述
我简单介绍一下 class 字节码文件的内容结构

  • Header:文件头包含了magic(魔数)——“验证是否是 class 格式文件”;minor_version,major_version——“该 class 文件支持的版本等数据信息”
  • Constant Pool:常量池包含了类中所有的资源信息,如字面量常量——”字符串,被 final 修饰的常量等“;符号引用——”类和接口的全限定(绝对路径)名;字段的名称和描述符;方法的名称和描述符“
  • Access Flag:类访问标志在常量池后面,标识类和接口的访问信息,如该 Class 文件是类还是接口,是否为 public,是否为 abstract 等
  • Class :类索引,包含当前类的索引(this_class)父类索引(super_class),接口索引(interfaces),通过这个索引,我们可以去常量池找这个类的全限定描述符
  • fields:字段表集合,记录了类中每个变量的变量名, 变量类型, 访问标识, 属性等
  • method:方法表集合,方法表和字段表的结构比较类似,包含了访问标识,名称索引,描述符索引,属性表索引等信息
  • attributes:属性表,属性表非常庞大,包含方法的字节码指令,方法表里面的属性表索引就是指向该方法的字节码指令,常量值,方法抛出的异常等数据

2.2 Dex字节码

说完了 class 字节码,接下来对比说一下 Dex 字节码文件,我们知道 class 字节码文件只能在 JVM 上面运行,无法在 Android 虚拟机上运行,只有 dex 文件才能在 Android 虚拟机上运行,那么 dex 文件又是什么呢?它和 class 文件的区别是什么呢?

Android 项目通过 gradle 构建生成 apk 文件,apk 文件就是 Android 的安装包,安装包主要由 dex 文件,so 文件,资源文件,manifest 文件组成,如果有使用 kotlin 的话,apk 包里面还会有 kotlin 的编译产物。

在这里插入图片描述

我这里只讲 dex 文件,Android 的编译器会将 java 文件编译成 dex,编译流程如下:

SourceCode(.java) — javac → Java Bytecode(.class) — Proguard → Optimized Java bytecode(.class) — Dex → Dalvik Optimized Bytecode(.dex)

从上面的流程看到,编译器第一步同样是将 java 文件转换成了 class 字节码文件,之后便是 Android 编译器所特有的部分:

  • Proguard 流程会对字节码文件进行压缩,优化和混淆,我们可以在 gradle 中开启配置 proguradFiles 的规则来开启我们的 Proguard 流程
  • 当 Proguard 优化字节码文件后,dx 编译器(AndroidStudio3.0 之后开始采用 D8 编译器)会将优化后的字节码文件生成 dex 文件

java8 中引入了 lambda 等一些语法糖新特性,所以为了兼容这些语法糖,Android 编译器在编译的途中会经历拖糖的操作,在 Android Gradle Plugin3.1 版本之前是用的第三方的插件进行脱糖操作,将所有的流程串起来,它的步骤如下图:

  • Header:dex文件的头文件同样包含了magic魔数,用来标识是否是dex文件,还包含了checksum和signature等文件校验和签名信息码,file_size,header_size文件和头大小以及其他数据的大小等信息等等
  • String_ids:字符串偏移数组,表示每个字符串在 data 区的偏移量,根据偏移量在Data区拿到数据
  • Type_ids:数据类型索引,表示所有引用的数据类型在字符串中的索引
  • Protos_ids:方法声明索引
  • Fields:记录了所属类,类型以及方法名
  • Methods:方法表
  • Classes:类信息索引,记录了类信息,包括接口,超类,类数据偏移量
  • Data:数据区,保存了dex文件中所有类的数据

dex 的文件和 class 文件存放的数据是一样的,只是结构会有些不一致,而且 dex 文件是多个 class 文件的集合,所有会有数据去重,重排列等优化处理处理。

我们接着来看看虚拟机的第二个能力,如何装载上面的字节码文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值