Android 开发需要了解的虚拟机知识
Dalvik 和 ART
参看 快速理清 .dex、.odex、ART、AOT、OAT 逻辑关系
Java 内存结构和内存模型
参考 Java 内存基本知识
这里对 Java 内存模型做个简单的理解:
因为 CPU 存在多级缓存(详见 Java 锁机制详解(二)volatile 开头部分),导致多线程情况下存在内存可见性问题。
计算机内存可见性Java 则据此在多线程环境下做了抽象:
Java内存可见性并提供了 volatile
、synchronized
、final
等关键字,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]