深入理解Android虚拟机-第一篇
Java虚拟机
体系结构
- 一组指令集
- 一组寄存器
- 一个栈
- 局部变量区
- 运行环境区
- 操作数栈区
- 一个无用的单元收集堆
- 一个方法区域
每个Java程序运行在自己的Java虚拟机实例中。
内存
- 寄存器
- 栈 基本类型数据和对象的引用
- 堆 存放new产生的数据
- 静态域 静态成员
- 常量池 常量
- 非RAM存储
线程结束,栈内存释放。栈中的数据以栈帧格式存在。
栈帧数据:
- 本地变量,输入输出参数,以及方法内的变量
- 栈操作,记录出栈,入栈操作
- 栈帧数据,类文件、方法等
常量池
各种基本类型,对象型的常量值及符号引用。
符号引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
堆内存
-
永久代
-
新生代
- Eden
- Survivor 0
- Survivor 1
-
老年代
程序计数器
程序计数器是一块较小的内存,他可以看做是当前线程所执行的行号指示器。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法(native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。JNI是Java本机接口(Java Native Interface),JNI允许Java代码使用以其他语言编写的代码和代码库),这个计数器则为空。此内存区域是唯一 一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈内存就是虚拟机栈,或者说是虚拟机栈中局部变量表的部分。局部变量表存放了编辑期可知的各种基本数据类型(boolean、byte、char、short、int、long、double、float)、对象引用(reference)类型和returnAddress类型(指向了一条字节码指令的地址)
其中64位长度的long和double类型的数据会占用两个局部变量空间,其余的数据类型只占用1个。
Java虚拟机规范对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈和虚拟机栈发挥的作用是非常类似的,他们的区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
Java堆
堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都在堆上分配。
Java堆是垃圾收集器管理的主要区域。Java堆细分为新生代和老年代。不管怎样,划分的目的都是为了更好的回收内存,或者更快的分配内存。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续是连续的即可。若在堆中没有完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
方法区
用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
除了Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池
它是方法区的一部分。class文件中除了有关的版本、字段、方法、接口等描述信息外、还有一项信息是常量池,用于存放编辑期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
Java语言并不要求常量一定只有编辑期才能产生,也就是可能将新的常量放入池中,这种特性被开发人员利用得比较多是便是String类的intern()方法。
当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
Dalvik & ART
Dalvik 和 Java 运行环境的区别
- Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
- Dalvik虚拟机在android2.2之后使用JIT (Just-In-Time)技术,与传统JVM的JIT并不完全相同,
- Dalvik虚拟机有自己的 bytecode,并非使用 java bytecode。
- Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。
- Dalvik负责进程隔离和线程管理,每一个android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
- 不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式Dex。
- dex文件格式可以减少整体文件尺寸,提高I/O操作的类查找速度。
- odex是为了在运行过程中进一步提高性能,对dex文件的进一步优化。
- 所有的Android应用的线程都对应一个linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制。
- 有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化、库的加载、预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的速度提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域
- 基于寄存器,基于寄存器的虚拟机虽然比基于堆栈的虚拟机在硬件,通用性上要差一些,但是它的代码执行效率去更好
- 每一个Android应用都运行在它自己的DVM实例中,每一个DVM实例都是一个独立的进程空间。所有的Android应用的线程都对应一个Linux线程,DVM因此可以更多地依赖操作系统的线程调度和管理机制。不同的应用在不同的进程空间里运行,不同的应用都是用不同的Linux用户来运行以最大程度地保护应用程序的安全性和独立性
ART虚拟机
Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机
-
即Android Runtime
ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
ART有什么优缺点呢?
- 优点:
- 1、系统性能的显著提升。
- 2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
- 3、更长的电池续航能力。
- 4、支持更低的硬件。
- 缺点:
- 1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
- 2.应用的安装时间会变长。