【Android】Android Dalvik&JVM 深藏阁楼爱情的钟 2022-02-03 04:11 272阅读 0赞 ### 文章目录 ### * 1、JVM * * 1)特点 * 2)字节码 * 3)Java代码运行流程 * 4)类加载器分类 * 5)类加载器特点 * 6)类加载步骤 * 7)运行时数据区 * 8)执行引擎 * 9)GC原理 * 10)引用类型 * 11)JVM结构 * 2、Dalvik * 3、ART # 1、JVM # JVM,Java虚拟机,是Java运行时环境JRE的核心组成部分,这也是Java跨平台的基础,将Java字节码在虚拟机中执行。 ## 1)特点 ## 基于栈而不是基于寄存器;基本数据类型外的其它数据类型是基于符号的引用,而不是基于内存地址的引用;GC,提供了垃圾自动回收机制;基本数据类型的长度明确定义,与平台无关,而不像C的基本数据类型在不同的平台上长度可能不同;字节码采用网络字节序,即大端表示。 ## 2)字节码 ## Java字节码是部署Java程序的最小单元,是Java和机器语言的中间语言。 例如下面的Hello.java文件,通过javac命令编译后生成Hello.class文件,这是个二进制文件,可通过javap命令进行反编译。 // Hello.java public class Hello { public void foo() { } public void bar(int i) { } public int hello(String s) { return 0; } } javac Hello.java javap Hello.class Compiled from "Hello.java" public class Hello { public Hello(); public void foo(); public void bar(int); public int hello(java.lang.String); } javap还可以添加一些参数,输出详细的反编译信息,这里就不列举了。 ## 3)Java代码运行流程 ## 第一步,使用javac命令编译java文件生成对应的class文件。 第二步,运行时ClassLoader加载class到内存,转为Class对象。 第三步,执行引擎根据字节码运行程序。 ## 4)类加载器分类 ## 类加载器分为以下四类。 启动类加载器Bootstrap ClassLoader:这个类加载器负责放在<JAVA\_HOME>/lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库,用户无法直接使用。 扩展类加载器Extension ClassLoader:这个类加载器由`sun.misc.Launcher$AppClassLoader`实现,它负责<JAVA\_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,用户可以直接使用。 应用程序类加载器Application ClassLoader:这个类加载器由`sun.misc.Launcher$AppClassLoader`实现,是ClassLoader中getSystemClassLoader方法的返回值,它负责用户路径ClassPath所指定的类库,用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。 自定义加载器User ClassLoader:用户自己定义的类加载器。 ## 5)类加载器特点 ## Java提供了动态加载的特性,只有在运行时第一次遇到类时才会去加载和链接。每个类加载器都有自己的空间,用于存储其加载的类信息。Java的类加载器按是父子关系的层次结构组织的。当一个类需要被加载,会先去请求父加载器判断该类是否已经被加载,如果父类加器已加载了该类,那它就可以直接使用而无需再次加载,如果尚未加载,直到Bootstrap都未加载,才需要当前类加载器来加载此类。子类加载器可以从父类加载器中获取类,反之则不行。类加载器可以载入类却不能卸载它,但是可以通过删除类加载器的方式卸载类。 ## 6)类加载步骤 ## 类加载包括如下五个步骤。 加载(Loading): 从文件中获取类并载入到JVM内存空间。 验证(Verifying): 验证载入的类是否符合Java语言规范和JVM规范。在类加载流程的测试过程中,这一步是最为复杂且耗时最长的部分。大部分JVM TCK的测试用例都用于检测对于给定的错误的类文件是否能得到相应的验证错误信息。 准备(Preparing): 根据内存需求准备相应的数据结构,并分别描述出类中定义的字段、方法以及实现的接口信息。 解析(Resolving): 把类常量池中所有的符号引用转为直接引用。 初始化(Initializing): 为类的变量初始化合适的值。执行静态初始化域,并为静态字段初始化相应的值。 ## 7)运行时数据区 ## 运行时数据包括如下五个组件。 方法区(Method Area):所有的类级数据将存储在这里,包括静态变量。每个JVM只有一个方法区,它是一个共享资源。 堆区域(Heap Area):所有对象及其对应的实例变量和数组将存储在这里。每个JVM也只有一个堆区域。由于方法和堆区域共享多个线程的内存,所存储的数据不是线程安全的。 堆栈区(Stack Area):对于每个线程,将创建单独的运行时堆栈。对于每个方法调用,将在堆栈存储器中产生一个条目,称为堆栈帧。所有局部变量将在堆栈内存中创建。堆栈区域是线程安全的,因为它不共享资源。堆栈框架分为三个子元素,局部变量数组(Local Variable Array),与方法相关,涉及局部变量,并在此存储相应的值操作数堆栈(Operand stack),如果需要执行任何中间操作,操作数堆栈将充当运行时工作空间来执行操作;帧数据(Frame Data),对应于方法的所有符号存储在此处,在任何异常的情况下,捕获的区块信息将被保持在帧数据中。 PC寄存器(PC Registers):每个线程都有单独的PC寄存器,用于保存当前执行指令的地址。一旦执行指令,PC寄存器将被下一条指令更新。 本地方法堆栈(Native Method stacks):本地方法堆栈保存本地方法信息。对于每个线程,将创建一个单独的本地方法堆栈。 ## 8)执行引擎 ## 分配给运行时数据区的字节码将由执行引擎执行,执行引擎读取字节码并逐个执行。执行引擎包括解释器、JIT编译器和GC。 解释器:解释器解释字节码的速度更快,但执行速度较慢。解释器的缺点是,当多次调用一种方法时,每次都需要新的解释。 JIT编译器 :JIT编译器中和了解释器的缺点。执行引擎将在转换字节码时使用解释器的帮助,但是当它发现重复的代码时,它将使用JIT编译器,它编译整个字节码并将其更改为本地代码。该本机代码将直接用于重复的方法调用,这可以提高系统的性能。JIT编译器包括中间代码生成器、代码优化器、目标代码生成器、Profiler。中间代码生成器用于生成中间代码;代码优化器负责优化上面生成的中间代码;目标代码生成器负责生成机器代码或本地代码;Profiler是一个特殊的组件,负责查找热点,即该方法是否被多次调用。 GC:收集并删除未引用的对象。垃圾回收可以通过调用“System.gc()”触发,但执行不能保证。JVM的垃圾回收所创建的对象。 ## 9)GC原理 ## 内存主要被分为三块:新生代(Youn Generation)、旧生代(Old Generation)、持久代(Permanent Generation)。三代的特点不同,造就了他们使用的GC算法不同,新生代适合生命周期较短,快速创建和销毁的对象,旧生代适合生命周期较长的对象,持久代在Sun Hotpot虚拟机中就是指方法区(有些JVM根本就没有持久代这一说法)。 新生代(Youn Generation):大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:From Space和To Space。新建的对象都是从新生代分配内存,Eden区不足的时候,会把存活的对象转移到Survivor区。当新生代进行垃圾回收时会出发Minor GC(也称作Youn GC)。 旧生代(Old Generation):旧生代用于存放新生代多次回收依然存活的对象,如缓存对象。当旧生代满了的时候就需要对旧生代进行回收,旧生代的垃圾回收称作Major GC(也称作Full GC)。 目前为止,JVM已经发展处四种比较成熟的垃圾收集算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法。 标记-清除算法:这种垃圾回收一次回收分为两个阶段:标记、清除。首先标记所有需要回收的对象,在标记完成后回收所有被标记的对象。这种回收算法会产生大量不连续的内存碎片,当要频繁分配一个大对象时,JVM在新生代中找不到足够大的连续的内存块,会导致JVM频繁进行内存回收(目前有机制,对大对象,直接分配到老年代中)。 复制算法:这种算法会将内存划分为两个相等的块,每次只使用其中一块。当这块内存不够使用时,就将还存活的对象复制到另一块内存中,然后把这块内存一次清理掉。这样做的效率比较高,也避免了内存碎片。但是这样内存的可使用空间减半,是个不小的损失。 标记-整理算法:这是标记-清除算法的升级版。在完成标记阶段后,不是直接对可回收对象进行清理,而是让存活对象向着一端移动,然后清理掉边界以外的内存 分代收集算法:当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将内存分为几块即新生代、老年代,然后根据不同年代的特点,采用不同的收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,所以选择了复制算法。而老年代中因为对象存活率比较高,所以采用标记-整理算法(或者标记-清除算法) ## 10)引用类型 ## 强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。 软引用:只有当JVM内存不足时才会被回收。 弱引用:只要GC,就会立马回收,不管内存是否充足。 虚引用:虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。虚引用主要用于检测对象是否已经从内存中删除。 ## 11)JVM结构 ## ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lFZWFydGg_size_16_color_FFFFFF_t_70] # 2、Dalvik # Dalvik是一种JVM实现,但并未完全遵循JVM规范,基于寄存器,Java字节码被转换为Dalvik用的寄存器指令集即dex文件,占用的内存空间更小,dex还会进一步被优化成odex。 # 3、ART # ART是Dalvik的替代品,ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用,这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。 可以通过调用 System.getProperty(“java.vm.version”)来检测当前使用的是哪个虚拟机,如果使用的是ART虚拟机的话,属性值会大于等于2.0.0。 通过反射调用SystemProperty的get方法来查看persist.sys.dalvik.vm.lib属性值,Android系统提供了一个系统属性persist.sys.dalvik.vm.lib,[它的值要么等于libdvm.so][libdvm.so],[要么等于libart.so][libart.so]。当等于libdvm.so时,就表示当前用的是Dalvik虚拟机,而当等于libart.so时,就表示当前用的是ART虚拟机。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lFZWFydGg_size_16_color_FFFFFF_t_70]: /images/20220203/0972f12753fe4c94a17ed19c9c006242.png [libdvm.so]: http://xn--libdvm-o17ius24j360bx19dy7l758b.so [libart.so]: http://xn--libart-o17iuss433a7y3a.so
还没有评论,来说两句吧...