美文网首页
Android 开发需要了解的虚拟机知识

Android 开发需要了解的虚拟机知识

作者: Parallel_Lines | 来源:发表于2020-06-18 17:53 被阅读0次

    Dalvik 和 ART

    参看 快速理清 .dex、.odex、ART、AOT、OAT 逻辑关系

    Java 内存结构和内存模型

    参考 Java 内存基本知识

    这里对 Java 内存模型做个简单的理解:

    因为 CPU 存在多级缓存(详见 Java 锁机制详解(二)volatile 开头部分),导致多线程情况下存在内存可见性问题。

    计算机内存可见性

    Java 则据此在多线程环境下做了抽象:

    Java内存可见性

    并提供了 volatilesynchronizedfinal 等关键字,concurren 包、以及一系列规则等以解决此类问题。此外,指令重排等计算机内存的潜在隐患也同样做了抽象、处理。

    所以,我们将此处 Java 抽象模型以及关键字等统一称为内存模型。即 Java 内存模型定义了 JVM 在计算机内存的工作方式。

    垃圾回收策略

    GC 主要针对 JVM 堆和方法区进行垃圾回收,因为这部分内存的动态分配和回收的。

    垃圾识别策略

    1、引用计数法

    原理:给对象添加一个引用计数器,当计数器为 0 时说明没有引用,则对象可被回收。
    缺点:无法解决循环引用。

    2、可达性分析法

    原理:从 GC Roots 创建一条到对象的引用链,当一个对象到 GC Roots 没有任何引用链时,视为对象可回收。

    GC 会收集那些不是 GC Roots 且没有被 GC Roots 引用的对象。那么谁可作为 GC Roots 对象呢?总结来说就是:

    当前肯定不会被回收的对象都可以作为 GC Roots。

    如类的静态变量所引用的对象当前肯定不会被回收,那它即可作为它所引用对象的 GC Roots。

    关于 GC Roots 的问题可以参考 知乎大神 的回答(或者直接看下边),很有启发。

    问:为什么它们可以作为 GC roots?
    答:因为这些东西被认为是在被使用。JVM 设计者命令 JVM 将他们作为 GC Roots 。

    问:到底 GC Roots 是什么?
    答:GC Roots 是一个统称,是所有可以用作"根集可达性算法"中的根。

    问:它们存放在哪里?
    答:GC Roots 本身是没有所谓的存储位置,他们都是字节码加载运行过程中加入 JVM 中的一些普通对象,只不过被认为是 GC Roots。

    问:GC Roots 是引用还是对象?
    答:引用就是对象,因为对于 Java 语言(非字节码)来说单独的引用(没有指向对象的引用)没有意义。

    问:GC Roots 是放在堆里的还是方法栈还是哪个地方?
    答:都有,只要他被认为是被使用的,但堆中开辟的对象都是在其他位置有一个引用的。

    问:虚拟机会回收 GC Roots 吗?
    答:不会但不保证绝对不会,因为 JVM 有几十种,无法保证以后会不会加入回收 GC Roots 的机制,但就 HotSpot 而言是不会的。

    内存回收算法

    1、标记清除算法

    原理:在标记阶段给所有的活动对象打上标记,在回收阶段将没有标记的对象(即垃圾对象)回收。
    缺点:内存碎片化严重,也正因为碎片化严重,导致内存再分配效率低。

    2、复制算法

    原理:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当一块的内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存一次清理掉。
    优势:每次都对整个半区进行回收,解决碎片化问题,再分配效率高。
    缺点:内存损失一半,代价太高。

    3、分代回收算法

    原理:将内存去分为新生代和老年代俩个区。新生代因为回收频繁,使用复制算法提高效率。老年代因为存活率高,使用标记清除算法节省内存。
    优势:兼顾时间(效率)和空间(内存)的综合选择。

    类的加载

    加载过程

    虚拟机把 Class 文件加载到内存,并完成链接和初始化,最终形成可被虚拟机直接使用的 Java 类型的过程叫做类的加载。

    JVM 不是一开始就把所有的类都加载进内存中,只有第一次运行类时才会加载,且只加载一次。

    题外话:Android 应用启动时有个 dexElements 数组,包含了所有 .dex 文件地址,ClassLoader 会遍历该数组查找要加载的类。也就是说可以通过修改 dexElements 数组以替换 JVM 加载的类文件,以实现热修复。

    类的加载过程可以分为如下几步:

    1、加载

    通过类加载器(ClassLoader),将 Class 文件从各个来源加载到内存中。

    ClassLoader

    既然说到了 ClassLoader,那这里就简单解析下。

    ClassLoader 类加载原理 -- 双亲委托

    关于 Android 的双亲委托,可以参考 热修复原理与基础范例

    简单来说,就是 ClassLoader 向上委托,向下查找,找到立即返回。

    ClassLoader 实现类

    Java 的 ClassLoader.

    类名 用途
    BootstrapClassLoader 顶层的加载类,主要加载核心类库,%JRE_HOME%\lib 下 的rt.jar、resources.jar、charsets.jar 和 class 等。
    ExtentionClassLoader 扩展的类加载器,加载目录 %JRE_HOME%\lib\ext 目录下的 jar 包和 class 文件。还可以加载 -D java.ext.dirs 选项指定的目录。
    AppclassLoader 加载当前应用的 classpath 的所有类。

    Android 的 ClassLoader.

    类名 用途
    BootClassLoader 通常用来预加载一些 Java 常用的类;
    PathClassLoader 通常用于加载系统类和应用程序的类(就是你的 apk,它可以从你的 apk 中解析出 class 对象),其中 optimizedDirectory 为null, 默认目录 /data/dalvik-cache/。
    DexClassLoader 可以加载外部 dex 文件以及包含 dex 的压缩文件(apk 和 jar)。

    ClassLoader 常用参数

    参数名 用途
    dexPath 包含目标类或资源的 apk/jar 列表,多个路径采用 : 分割
    optimizedDirectory 优化后的 dex 文件输出的目录,可为 null
    libraryPath native 库所在路径列表,多个路径采用 : 分割
    classLoader 父类加载器

    更多 ClassLoader 相关内容可参考此 链接

    2、链接

    链接又可以分为三个过程,分别是 验证、准备、解析。

    2.1、验证

    保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

    2.2、准备

    给静态变量分配内存,并根据数据类型赋默认值。(如 int 类型则赋值 0.)

    2.3、解析

    把常量池内的符号引用替换为直接引用。

    因为我们写代码时自定义的方法名、类名等虚拟机是无法识别的,所以会被虚拟机替换为对应的内存地址,也就是直接引用。

    3、初始化

    执行类的构造器进行初始化。

    类的初始化顺序

    既然说到了,就简单总结下类的初始化顺序。

    父类--静态变量
    父类--静态初始化块
    子类--静态变量
    子类--静态初始化块
    父类--成员变量
    父类--初始化块
    父类--构造器
    子类--成员变量
    子类--初始化块
    子类--构造器
    

    总结

    Android 开发需要了解的虚拟机知识就到这儿,后续有的话会继续补充。

    [TOC]

    相关文章

      网友评论

          本文标题:Android 开发需要了解的虚拟机知识

          本文链接:https://www.haomeiwen.com/subject/ittpxktx.html