JVM,DVM,ART

JVM

可以把Java程序设计语言、Java虚拟机和Java API类库这三部分统称为JDK(Java Development Kit),它是Java程序开发的最小环境。另外,Java API中的Java SE API子集和Java虚拟机这两部分统称为JRE(Java Runtime Environment),它是Java程序运行的标准环境。 从上面可以看出Java虚拟机及其重要,它是整个Java平台的基石,是Java语言编译代码的运行平台。你可以把Java虚拟机看做一个抽象的计算机,它有各种指令集和各种运行时数据区域。

Java程序执行流程:

从上图可以看到Java虚拟机与java语言没有什么必然联系,它只与特定的二进制文件:Class文件有关。

数据类型

Java虚拟机与Java语言的数据类型相似,可以分为两类:基本类型和引用类型。Java虚拟机希望编译器在编译期间尽可能的完成类型检查,使得虚拟机在运行期间无需进行类型检查操作。

运行时数据区域

很多人将Java的内存分为堆内存(heap)和栈内存(Stack),这种分发不够准确,Java的内存区域划分实际上远比这复杂。 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为不同的数据区域,些数据区域分别为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区。

  1. 程序计数器:为了保证程序能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器正是起到这种作用。
  2. Java虚拟机栈:每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈。它的生命周期与线程相同,与线程是同时创建的。Java虚拟机栈存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常所说的栈内存(Stack)指的就是Java虚拟机栈
  3. 本地方法栈:Java虚拟机实现可能要用到C Stacks来支持Native语言,这个C Stacks就是本地方法栈。它与Java虚拟机栈类似,只不过本地方法栈是用来支持Native方法服务。
  4. Java堆(Java Heap):是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。
  5. 方法区(Method Area):是被所有线程共享的运行时内存区域。用来存储已经被Java虚拟机加载的类的结构信息,包括: 运行时常量池、字段和方法信息、静态变量等数据。

GC

GC主要做了两个工作,一个是内存的划分和分配,一个是对垃圾进行回收。

垃圾标记算法

关于对垃圾进行回收,被引用的对象是存活的对象,而不被引用的对象是死亡的对象也就是垃圾,GC要区分出存活的对象和死亡的对象,也就是垃圾标记,并对垃圾进行回收。目前有两种垃圾标记算法,分别是引用计数算法和根搜索算法,这两个算法都和引用有些关联。

引用: 在JDK1.2之后,Java将引用分为强引用、软引用、弱引用和虚引用。

强引用: 当我们new一个对象时就是创建了一个具有强引用的对象,如果一个对象具有强引用,垃圾收集器就绝不会回收它。Java虚拟机宁愿抛出OutOfMemoryError异常,使程序异常终止,也不会回收具有强引用的对象来解决内存不足的问题。

软引用: 如果一个对象只具有软引用,当内存不够时,会回收这些对象的内存,回收后如果还是没有足够的内存,就会抛出OutOfMemoryError异常。Java提供了SoftReference类来实现软引用。

弱引用: 弱引用比起软引用具有更短的生命周期,垃圾收集器一旦发现了只具有弱引用的对象,不管当前内存是否足够,都会回收它的内存。Java提供了WeakReference类来实现弱引用。

虚引用: 虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,这就和没有任何引用一样,在任何时候都可能被垃圾收集器回收。一个只具有虚引用的对象,被垃圾收集器回收时会收到一个系统通知,这也是虚引用的主要作用。Java提供了PhantomReference类来实现虚引用。

引用计数算法

引用计数算法的基本思想就是每个对象都有一个引用计数器,当对象在某处被引用的时候,它的引用计数器就加1,引用失效时就减1。当引用计数器中的值变为0,则该对象就不能被使用成了垃圾。 目前主流的Java虚拟机没有选择引用计数算法来为垃圾标记,主要原因是引用计数算法没有解决对象之间相互循环引用的问题。

根搜索算法

这个算法的基本思想就是选定一些对象作为GC Roots,并组成根对象集合,然后从这些作为GC Roots的对象作为起始点,向下进行搜索,如果目标对象到GC Roots是连接着的,我们则称该目标对象是可达的,如果目标对象不可达则说明目标对象是可以被回收的对象。 可以作为GC Roots的对象主要有以下几种:

Java栈中的引用的对象。

本地方法栈中JNI引用的对象。

方法区中运行时常量池引用的对象。

方法区中静态属性引用的对象。

运行中的线程

由引导类加载器加载的对象

GC控制的对象

垃圾收集算法

Android类加载机制

DVM

Dalvik虚拟机,简称DVM。DVM是Google专门为Android平台开发的虚拟机,它运行在Android运行时库中。需要注意的是DVM并不是一个Java虚拟机。

DVM和JVM的区别 DVM之所以不是一个JVM ,主要原因是DVM并没有遵循JVM规范来实现。DVM与JVM主要有以下区别。

  1. 基于的架构不同:JAVA虚拟机基于栈结构,程序在运行时虚拟机需要频繁的从栈上读取写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费很多CPU时间。Dalvik虚拟机基于寄存器架构,数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。
  1. 执行的字节码不同:Java运行的是Java字节码,DVM运行的是Dalvik字节码。Java类会被编译成一个或多个.class文件,打包成jar文件,而后JVM会通过相应的.class文件和jar文件获取相应的字节码。而DVM会用dx工具将所有的.class文件转换为一个.dex文件,然后DVM会从该.dex文件读取指令和数据。当JVM加载该.jar文件的时候,会加载里面的所有的.class文件,JVM的这种加载方式很慢,对于内存有限的移动设备并不合适。 而在.apk文件中只包含了一个.dex文件,这个.dex文件里面将所有的.class里面所包含的信息全部整合在一起了,这样再加载就提高了速度。.class文件存在很多的冗余信息,dex工具会去除冗余信息,并把所有的.class文件整合到.dex文件中,减少了I/O操作,提高了类的查找速度。
  1. Dalvik可执行文件体积更小(原因同第二点)
  1. DVM允许在有限的内存中同时运行多个进程:DVM经过优化,允许在有限的内存中同时运行多个进程。在Android中的每一个应用都运行在一个DVM实例中,每一个DVM实例都运行在一个独立的进程空间。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

ART虚拟机

ART(Android Runtime)是Android 4.4发布的,用来替换Dalvik虚拟,Android 4.4默认采用的还是DVM,系统会提供一个选项来开启ART。在Android 5.0时,默认采用ART,DVM从此退出历史舞台。

DVM中的应用每次运行时,字节码都需要通过即时编译器(JIT,just in time)转换为机器码,这会使得应用的运行效率降低。而在ART中,系统在安装应用时会进行一次预编译(AOT,ahead of time),将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译了,运行效率也大大提升。

ART优点

应用运行更快,因为 DEX 字节码的翻译在应用安装是就已经完成。

减少应用的启动时间,因为直接执行的是 native 代码。

提高设备的续航能力,因为节约了用于一行一行解释字节码所需要的电池。

支持更低的硬件

ART缺点

由于在安装时时生成的 native 机器码是存储在内部存储器上,所以需要更多的内部存储空间。(大概多个10%~20%)

应用安装需要更长的时间,因为 DEX 字节码需要在安装时就翻译成机器码。

你可能感兴趣的:(JVM,DVM,ART)