掌握Java和Android虚拟机

本文深入探讨了虚拟机的概念,以JVM、Davlik和Art虚拟机为例,详细阐述了从源码编译成字节码、字节码文件的装载、内存管理和指令执行的过程。讲解了Java的class字节码和Android的dex字节码的结构,以及虚拟机如何加载、链接和初始化这些字节码文件。同时,分析了虚拟机的内存布局、垃圾回收机制和指令解析执行,展示了虚拟机在不同平台上的实现差异和优化技术,如AOT和JIT编译。
摘要由CSDN通过智能技术生成

​我们知道的虚拟机有很多,运行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三款虚拟机为例,分别介绍上述的能力。

将源码编译成字节码
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:属性表,属性表非常庞大,包含方法的字节码指令,方法表里面的属性表索引就是指向该方法的字节码指令,常量值,方法抛出的异常等数据
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版本之前是用的第三方的插件进行脱糖操作,将所有的流程串起来,它的步骤如下图:
在这里插入图片描述
我们接着看一下dex文件的文件结构

  • 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文件的集合,所有会有数据去重,重排列等优化处理处理。

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

装载字节码文件
class字节码文件

java编译器将源文件编译成class字节码文件后,jvm就直接可以运行了,但想要运行,首先要将这个字节码文件加载进内存,jvm通过ClassLoader来加载指定路径的字节码文件,字节码的文件可以通过网络下载,也可以通过本地读取。我们看一下ClassLoader类加载class的实现。

//java.lang.ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
   
        synchronized (getClassLoadingLock(name)) {
             
        //查找.class是否被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
   
            long t0 = System.nanoTime();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值