哈喽,大家好,这次我们来聊聊JVM和Andorid虚拟机的一些知识点。希望大家在看了文章后能对JVM,Dalvik,ART有一个简单的认识,如果想要详细研究的话,这篇文章还没有达到那个高度。好了,废话不多说,下面开始进入正题。
什么是JVM?
JVM是Java Virtual Machine(Java虚拟机)的缩写。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言只需生成在Java虚拟机上运行的目标代码(字节码/.class文件),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
什么是DVM(Dalvik)?
Dalvik是Google公司自己设计用于Android平台的Java虚拟机。它是在JVM的基础上做了进一步的改动,使其更好的适应于移动设备。Dalvik的指令集是基于寄存器来实现的,而JVM是基于堆栈来实现的。这使得前者在速度上比后者更好,同时会减少CPU压力。
JVM和DVM
Java执行的时候最终是将Java代码转换为.class文件,而因为.class文件存在很多的冗余信息,不太适合在移动设备上执行,所以Google在获取.class文件后利用DEX工具将.class文件整合为.dex文件,去除了.class文件的冗余信息,减少I/O操作,提高了查找速度。不过这样也导致了方法数超过65535。Google利用MultiDex技术将.dex文件转换为.odex文件,解决了方法数的问题。
JVM内存结构
在了解完JVM和DVM的关系后,我们来看看JVM的内存结构
jvm内存结构
图中蓝色代表线程共享,黄色代表线程私有。
-
程序计数器:它是线程的一块私有内存,用来储存当前线程执行的字节码指令的内存地址。因为CPU的一个核心一次只能执行一个线程,所以多线程其实是在多个线程之间切换执行来实现的。这样在切换到一个线程后如何从正确的位置开始执行,就要找到储存的内存地址。
-
虚拟机栈:它是一块线程私有内存,它的生命周期与线程相同。 Java虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动作链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
-
本地方法栈:本地方法栈和虚拟机栈基本类似,只是虚拟机栈服务的是Java方法,而本地方法栈服务的是Native方法。
-
方法区:方法区是线程共享的,它是用于存储类结构信息的地方。包括常量、静态变量、构造函数等类型信息。而前面所讲的类型信息是由类加载器在类加载时从类文件中提取出来的。运行时常量池是方法区的一部分。用于存放编译器生成的各种字面量和符号引用。运行期间也可以将新的常量放入常量池中,用得比较多的就是String类的intern()方法。当方法区内存使用满了后也会发生OOM。
-
堆:它是虚拟机管理内存中最大的一块,被所有线程共享,该区域用于存放对象实例,几乎所有的对象(实例变量,数组)都在该区域分配,是GC的主要区域。
Dalvik的堆结构
因为Dalvik和JVM的堆结构有区别,所以我们这里来专门讲一讲Dalvik的堆结构。Android中是把堆结构分成了两部分,Active堆和Zygote堆。
Zygote是Android中的一个虚拟机进程,同时也是一个虚拟机实例的孵化器。Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等操作。在系统需要一个新的虚拟机实例时,Zygote通过复制自身,最快速的提供个进程。另外,对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域,大大节省了内存开销。除了Zygote预加载的Android的核心类等,其余新建的实例,数组等都是放在Active堆中。因为Zygote堆中的类库我们很少 修改,所以Active是GC的重点,这也是把堆分成Zygote和Active的原因。
Zygote在创建一个新的虚拟机实例的时候用的是fork技术,为了尽可能的避免父进程和子进程之间的数据拷贝,fork方法使用写时拷贝技术,简单讲就是fork的时候不立即拷贝父进程的数据到子进程中,而是在子进程或者父进程对内存进行写操作时才对内容进行复制。
什么是ART?
这里先说说JIT和AOT这两个概念
- Just In Time (JIT)
使用 Dalvik JIT 编译器,每次应用在运行时,它实时的将一部分 Dalvik 字节码翻译成机器码。在程序的执行过程中,更多的代码被被编译并缓存。由于 JIT 只翻译一部分代码,它消耗的更少的内存,占用的更少的物理存储空间。 - Ahead Of Time(AOT)
ART 内置了一个 Ahead-of-Time 编译器。在应用的安装期间,它就将 dex 字节码编译成机器码并存储在设备的存储器上。这个过程只在将应用安装到设备上时发生。由于不再需要 JIT 编译,代码的执行速度要快得多。
ART是Android在5.0的时候使用的用来代替Dalvik虚拟机的,其实在4.4的时候ART就已经放入了代码中。Dalvik虚拟机当时用JIT技术,为了更好的提高Android设备性能, ART用了AOT技术,这样去除了运行时的解释执行,效率更高,启动更快。在带来更快的运行速度的同时,ART也有它的缺点。因为安装时要把dex文件编译成机器码存在本地,所以会占用更大的本地空间,同时安装时间也会较长。
ART堆结构
ART的堆结构相比于Dalvik有一定的改动,ART的堆主要组成包括Image Space、Zygote Space、Allocation Space和Large Object Space四个Space。其中Image Space、Zygote Space这两个为所有线程共享内存。Allocation Space和Large Object Space则拿来分配内存。
Large Object Space是用来分配内存给需要占用大内存的实例,数组。它需要满足一定条件才会分配
- Zygote Space已经划分除了Allocation Space
- 分配对象是原子类型数组,如int[] byte[] boolean[]
- 分配的内存大小大于一定的门限值
当满足这三个条件后,就会分配Large Object Space中的内存。
在对ART的堆结构优化后,ART内存分配效率是Dalvik的8-10倍,GC效率是Dalvik的2-3倍。
到这里简单的介绍就够一段落了,希望大家有所收货。如果文章中有错误的地方,大家提出来后我会及时修改。
参考文献:
Android内存管理分析总结
JVM内存结构分析
JVM、DVM(Dalvik VM)和ART虚拟机对比
Android ART
网友评论