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

2020-06-18  本文已影响0人  Parallel_Lines

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]

上一篇下一篇

猜你喜欢

热点阅读