关于JVM

自己能说多少?

用于执行Java字节码指令的虚拟机,相对操作系统来说,它是一个进程,一般的虚拟机都是用C/C++ 来写的,在不同的系统中,它的实现是不同的,作为Java程序和操作系统的中间层,通过它实现了 同一套Java程序可以在不同程序中运行。

尽管被称之为Java虚拟机,但是它可以运行Kotlin、Scala语言编写的程序,是因为对于JVM来说, 它能运行的是Class字节码,通过不同语言的编译器,这些语言代码都会编译成Class文件。

对比Dalvik虚拟机、ART

首先,需要了解的是,在Android系统中,并不是直接用JVM虚拟机,早期用的Dalvik虚拟机,后面用的 是ART,不过,后两者都是执行是dex文件,而非JVM的Class文件。

而class到dex的转换是在apk打包过程中进行的,dex相比于class,其重要的一个区别是,使得字节码占用的存储变小了,主要原因是在dex中常量 池是复用的,而在class中,每个class文件拥有一个常量池,导致很多相同常量在class文件之间不能复用,而在字节码中,常量池占比一般是最大的。

而无论是什么字节码,都会有两部分构成,数据、指令,而常量池属于数据,常量池包括类、方法、的全限名。比如Lang\java\Object,还包括一些 字面量,初略 的看,代码中所有的字符都是存在常量池中的,比如类名、类方法,类成员,故这些信息占的存储空间是很大的。

对于Class字节码,其构成大致包括
魔数,用来标志该文件是什么文件的,Class文件的魔术是对应的16进制的CAFEBABE,其实只是一个标志而已
支持的版本信息,表示该Class是Java什么版本编译出来的,当前JVM会检验能不能支持运行该版本的Class文件。
常量池
方法表

凭什么dex能共用常量池,而class一个文件一个常量池?

当看到经过dex化后可以减少存储空间的占有,咋一看感觉dex字节码更胜一筹,难道真的如此吗?

个人认为,诸如类似的优化,其思想都是依据使用场景变化后调整了策略,并无高低之分。

class设计成单个文件自带一个常量池,是因为出于class可以单个文件独立加载到JVM来运行的需要,场景是可以网络下载了一个class便可以直接运行。 而dex将原本多个class的常量池复用,那么就不再适用于网络加载一个class就能运行场景,得加载原来多个class得信息量,也就是加载一个dex文件才能运行。

可以说,若是都是网络加载字节码来运行,那么一个dex包大小比class大得多。

另外,既然可以打包class,Java体系中使用了jar来打包,不知道是否相关得压缩信息量得策略。

而,Android中网络加载dex来运行得案例也是有的,比如热修复技术中thinker,就是下载一个补丁dex文件。

栈式虚拟机、寄存器式虚拟机对比?

JVM是栈式的,而Dalvik是寄存器式。

在于指令不同,首先,指令由操作码和操作数组成,操作码表示要干什么,操作数表述对什么数值进行操作;
而两者的最大区别应该是操作数的寻址方式不同,栈式的指令的寻址地址永远是栈顶的数,而寄存器式指令,指令本身包括了操作数所在的 位置,比如在某个寄存器,前者寻址是隐式的,而后者是显式的。

比如一个加法指令,栈式指令的执行过程是,先后弹出栈顶两数相加,后把结果推入栈顶。
而寄存器式指令,则可以是由三地址组成,即 iadd dest src1 src2,这样,要分配三个寄存器,首先要把操作数读取到两个源寄存器中, 经过CPU计算后,将结果存放在目标寄存器中。

这样,利用的栈的数据结构,操作地址都是隐式的,可以让指令更简练,即指令集更小。

而表达更简练的后果是,同一个操作,需要指令更多,而CPU执行指令的过程是不断地取指令执行指令,而当时间都耗费在取指令上时, 效率就降低了。

关于Java字节码指令,所知多少?

除了上面所提及的栈式指令整体以外,从具体的一些指令举例。
比如一个加载指令,为什么要区分 sload, iload, dload, lload, 即区分操作数类型。
指令本身的信息就得包括类型,因为这告知了CPU加载一个数,该从内存起点偏移多长,比如short类型只包括两个字节,int包括4个字节等等。

关于JIT、AOT

虚拟机中的指令,最终指令还是要转换成本地机器码,即CPU能够识别的指令。

JIT是 just in time, 即执行过程中,虚拟机在运行时将字节码指令转换成本地机器指令。
AOT是 ahead of time,在运行之前就将字节码转换成机器码。

两者的区别可以理解为懒汉式和饿汉式,也可以用我们考量空间和时间区别,或时间消耗的不同。JIT可以在加载部分Class文件后便执行,可以快速的 看到执行效果,AOT则是选择在APK安装中,将字节码全部转换成机器码,导致安装比较久,而且安装后占用存储空间大,这在android 7.0中体现。

而在7.0之前,全部只用JIT,导致运行性能不够高,毕竟运行时还要将时间消耗在转换字节码上。

而在8.0之后,ART,即Android Runtime,在这两者做了平衡,而是在运行过程中,才将字节码转换,并且将转换后的字节码保存下来,而且是计算了常常使用到的字节码才做转换,保存在optimized目录下。