转载

Android热修复技术原理浅析(二)

他是一种文件格式

简单说,就是能被JVM虚拟机识别、加载、并执行的 文件格式

而且除了java语言,还有很多其他语言也可以编译出class文件,当然还有kotlin

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)
上图摘抄自 【深入Java虚拟机】之二:Class类文件结构

如何手动编译出一个class文件

很简单 javac hello.java

class文件的作用

记录 一个类文件 里的所有信息,记住是 一个类文件 ,而且是 所有信息

Class类文件结构

详细的可参考 【深入Java虚拟机】之二:Class类文件结构

这里简要说一下:

  1. Class文件是一组以8位字节为基础单位的 二进制流 ,各个数据项目 严格按照顺序紧凑地 排列在Class文件中,中间 没有添加任何分隔符 ,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据。

  2. 下表列出了Class文件中各个数据项的具体含义:

Android热修复技术原理浅析(二)

magic

每个Class文件的头4个字节称 为魔数(magic) ,它的唯一作用是 判断该文件是否为一个能被虚拟机接受的Class文件 。它的值固定为0xCAFEBABE。

version

紧接着magic的4个字节存储的是Class文件的次版本号和主版本号,高版本的JDK能向下兼容低版本的Class文件,但不能运行更高版本的Class文件。

constant_pool

Android热修复技术原理浅析(二)

常量池是class文件中非常重要的结构,它描述着整个class文件的字面量信息。 常量池是由一组constant_pool结构体数组组成的,而数组的大小则由常量池计数器指定。 常量池计数器constant_pool_count 的值 =constant_pool表中的成员数+ 1。constant_pool表的索引值只有在大于 0 且小于constant_pool_count时才会被认为是有效的。

access_flag

Android热修复技术原理浅析(二)

this_class、super_class、interfaces

Android热修复技术原理浅析(二)

fields

Android热修复技术原理浅析(二)

methods

Android热修复技术原理浅析(二)

attributes

Android热修复技术原理浅析(二)

来个大图

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

好了,看看二进制文件究竟长什么样

这个是使用一个工具来查看class文件的内容

Android热修复技术原理浅析(二)

为什么Android没使用class文件,而是创造了dex文件呢

  1. class文件内存占用大,不适合移动端,最关键就是一个class文件只能表述一个类文件的所有属性
  2. 堆栈的加载模式,加载速度较慢
  3. 文件IO操作多,类查找慢

dex文件

什么是dex文件

能被DVM虚拟机识别、加载、并执行的文件格式

如何手动编译一个dex文件

在build-tools里面找到dx.bat

要使用dx命令,记得配置环境变量

Android热修复技术原理浅析(二)
  1. javac命令 生成class文件

javac hello.java

  1. dx命令 生成dex文件

dx --dex --output hello.dex hello.class

  1. adb命令把hello.dex文件放到手机内存卡

adb push hello.dex /storage/emulated/0

  1. 进入shell

adb shell

  1. dalvikvm命令 执行dex文件里的hello方法

    注意dex文件必须在Andriod手机执行,因为手机里才有DVM虚拟机

dalvikvm -cp /sdcard/hello.dex hello

Android热修复技术原理浅析(二)

dex文件的作用

一个class文件只是记录一个Java类的所有信息

但是一个dex记录所有类文件的信息,是 整个工程的信息

dex文件结构

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

上图中的文件头部分,记录了dex文件的信息,所有字段大致的一个分部;

索引区部分,主要包含字符串、类型、方法原型、域、方法的索引;

索引区最终又被存储在数据区,其中链接数据区,主要存储动态链接库,so库的信息。

dex文件长什么样子呢

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

来张大图

一张图理解dex

dex与class异同

当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)
class与dex异同之处

编年体与纪传体

纪传体通过记叙人物活动反映历史事件的体裁,通过记叙人物活动,反映历史事件。 如:《秦始皇本记.class》《项羽本纪.class》《高祖本纪.class》

编年体是中国传统史书的一种体裁,它是以年代为线索编排有关历史事件。编年体史书以时间为中心,按年、月、日顺序记述史事。因为它以时间为经,以史事为纬,比较容易反映出同一时期各个历史事件的联系。 例如:《春秋.dex》《左传.dex》《资治通鉴.dex》。

Android热修复技术原理浅析(二)

JVM虚拟机简介

jvm整体结构与组成

Android热修复技术原理浅析(二)

内存里存储class文件的不同部分,对应内存空间里的不同部分

编译流程

Android热修复技术原理浅析(二)

类加载器

jvm的classloader与Android里的classloader区别较大

下图为jvm的类加载器,

Android的类加载器是热修复的核心,接下来会专门说

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

类加载流程

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

jvm内存管理和垃圾回收

Android热修复技术原理浅析(二)

java栈区

java栈帧

每个方法从调用到执行完成,就是对应一个栈帧在虚拟机从入栈到出栈的过程

栈帧里包含局部变量表、栈操作数、动态链接、方法入口

A方法调用B方法,就会在调用B方法代码时,java虚拟就就会创建一个保存B方法的栈帧,然后压入栈区,当B方法执行完后,这个栈帧就会弹出栈区,这就是使我们经常说的,栈内存不需要我们管理,局部变量会在方法调用结束后,自动回收。

另外,从这里可以看出,每个方法对应一个栈帧,如果递归方法嵌套太深,当栈的深度大于jvm所允许的最大深度时候,会引起Stack Overflow,栈溢出,所以递归慎用,

本地方法栈

为native方法服务的,也是通过栈帧实现对本地方法的调用

方法区

存储虚拟机加载的类信息、常量、静态变量、及时编译器编译后的数据 这块区域,永远占据内存,知道退出进程

所以常量、静态变量生命周期很长,只有App退出,才会被回收,所以,很多内存泄漏都是不合理使用静态变量引起的

堆区

所有通过new创建的对象的内存都在堆区分配 是虚拟机中最大的一块内存,是GC要回收的部分

新生代与老生代,简单说,刚刚创建的对象会存在新生代里,当新生代对象越来越多,内存不足时候,jvm会通过自己的一套算法,把对象从新生代移动到老生代,这样新生代就会多出一部分空间了,还能接受新的对象。当新生代和老生代的内存都满了,再来对象就会oom

为什么要分新生代+老生代

这是为了让开发者动态调整新生代和老生代的大小,例如在做即时通讯时,临时的消息对象创建的比较多,就可以把新生代这块区域调整大一些,便于新对象的分配

垃圾回收

引用计数算法

引用计数器:被引用+1,引用销毁-1,为0,则可以被销毁

循环引用的时候,此算法失效

Android热修复技术原理浅析(二)

可达性算法

被GCRoot直接或者间接引用的对象,就不可销毁

Android热修复技术原理浅析(二)

引用类型

强软弱虚

弱引用的创建与使用

Android热修复技术原理浅析(二)

垃圾回收算法

标记-清除算法

好处:不需要让对象进行移动,仅需要对不存活的对象进行处理,在 存活对象较多 时候,执行效率高效,但是内存碎片很多

Android热修复技术原理浅析(二)

复制算法

好处:当 存活的对象比较少 时,较为高效,但是需要另外一块空间,用于管理移动

Android热修复技术原理浅析(二)

标记-整理算法

  1. 先遍历把可回收对象扫描出来,如B
  2. 扫描清除未标记对象
  3. 把存活的对象,进行移动,没有内存碎片
Android热修复技术原理浅析(二)

以上三种算法各有优缺点,虚拟机根据不同情况,采用不同算法,进行垃圾回收

触发回收

  1. jvm无法为新对象创建内存了
  2. 手动调用System.gc()方法(并不会马上执行gc)
  3. 低优先级的gc线程,被运行时就会执行

Dalvik 虚拟机与Jvm异同之处

  1. 执行文件不同,一个是class文件,一个是dex
  2. 类加载系统区别较大
  3. Dalvik 可以同时存在多个,Jvm只能同时存在一个
  4. Dalvik是基于寄存器的,jvm是基于栈的

jvm的方法调用是就栈的,前面说的栈帧 Dalvik是基于寄存器的,寄存器是比内存更快的存储介质

ART虚拟机

虽然Dalvik虚拟机已经不错了,但是google工程师研发了ATR虚拟机,更加高效

  • DVM使用JIT将字节码转换为机器码,效率低

app每次运行都会把字节码转换为机器码,再去执行,退出应用,在进入app,又会再次把字节码转为机器码,效率很低的

  • ART采用的是AOT 预编译 技术,执行速度更快

在app安装时候,就把字节码转为本地机器码,存在本地,因此,只要app启动,直接执行机器码,而不是每次转换。

但是采用ART预编译技术,app安装时间快比较长,而且在手机里占用空间多 空间换时间

Classlodaer

java里的classloder

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

android的classloader

classloader种类

  • BootClassLoader

    加载framework层的字节码文件

  • PathClassLoader

    加载安装到系统里的app的class文件

  • DexClassLoader

    加载指定目录的class文件

  • BaseDexClassloader

    PathClassLoader和DexClassLoader的父类

其实一个app最少需要BootClassLoader和PathClassLoader才能正常运行

我们打印下app里的classlodaer

//  打印所有的ClassLoader
var classLoader = classLoader
if (classLoader != null) {
    Log.e("cjx", "ClassLoader---$classLoader")
    while (classLoader.parent != null) {
        classLoader = classLoader.parent
        Log.e("cjx", "ClassLoader---$classLoader")
    }
}
复制代码
Android热修复技术原理浅析(二)

ClassLoader的特点

双亲代理模式

  1. classloader加载字节码时, 先询问当前classloader 是不是加载过此类,如果加载过,直接返回(不会重复加载字节码)
  2. 如果没有加载过, 询问父classloader是不是加载过 ,如果加载过返回parent加载过的字节码文件
  3. 如果整个继承线路都没加载过这个字节码, 才会由子classloader完成加载

由此可见,一个字节码文件被任意一个classLoader加载过,就不会被其他classLoader加载了,提高了加载效率,也带来了另外特性

类加载共享功能

一个字节码文件一旦被顶层classLoader加载过,就会被整个继承体系所共有

类加载隔离功能

不同继承路线的classLoader加载的类,肯定不是同一个类,防止被冒充

例如String这个类,肯定在顶层的classLoader里会把它加载,这样就避免,你自己写个classLoader来篡改string这个类的加载过程

什么样的类才能叫做是同一个类呢

同一个包名+同一个类名+同一个类加载器加载的类,才叫同一个类

ClassLoader的源码

Android热修复技术原理浅析(二)

如果都找不到,会走findClass方法,看一下这个方法

Android热修复技术原理浅析(二)

那么ClassLoader有哪些子类呢

Android热修复技术原理浅析(二)

源码目录

间接子类:DexClassloader

DexClassloader源码查看

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

上面的第二个参数很重要,这个路径是系统内部的路径,就是因为这个参数,才能去把未安装到app里的dex文件,加载进来

间接子类:PathClassLoader

PathClassLoader源码查看

Android热修复技术原理浅析(二)

其实这两个间接地子类,什么也没做,只是一个能加载外部的dex文件,一个只能加载apk内部的文件,主要逻辑还是他们的父类BaseDexClassloader实现的,我们接着看BaseDexClassloader的findClass方法,看看是如何加载dex文件的

直接子类:BaseDexClassloader

BaseDexClassloader源码查看

Android热修复技术原理浅析(二)

发现直接调用的是DexpathList的findclass方法

DexPathList源码

Android热修复技术原理浅析(二)

Element是类DexPathList的一个内部类,它其中重要的一个变量就是DexFile,就是dex文件。

Android热修复技术原理浅析(二)

看看这个Element[]是怎么实现的

Android热修复技术原理浅析(二)

来到makePathElement方法

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

makePathElements方法核心作用就是 将指定路径 中的 所有文件转化成DexFile 同时 存储到到Element[]这个数组 中。nativeLibraryDirectories 就是lib库了。 最终在findclass方法中实现。

Android热修复技术原理浅析(二)

接着看看dexFile的loadClassBinaryName方法,我们进入DexFile这个类

DexFile源码查看

Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)
Android热修复技术原理浅析(二)

回顾一下,我们的源码解析经历了些什么

  1. 首先看了ClassLoder 的双亲委托模式的实现,发现最终指向了findClass()这个方法
  2. 然后发现他是一个空实现,他等着子类去实现
  3. ClassLoader有一个直接子类BaseDexClassloader和两个间接子类DexClassloader 、PathClassLoader其实这两个间接地子类,什么也没做,只是DexClassloader能加载外部的dex文件,PathClassLoader只能加载apk内部的文件,主要逻辑还是他们的父类BaseDexClassloader实现的
  4. 在BaseDexClassloader里发现findClass,调用的是DexPathList的findClass方法
  5. 在DexPathList里,先看到一个Element这个内部类,他里面有个重要的变量叫DexFile,Element[]是通过makePathElement实现的
  6. makePathElement遍历所有文件,把所有dex加载为dexFile,并且存到Element[]里
  7. Ok终于来到BaseDexClassloader的findClass方法,他会遍历所有dexElement,通过clss的名字,加载这个类为class对象
  8. dexFile加载class的实现是通过native实现的,就这样
Android热修复技术原理浅析(二)
原文  https://juejin.im/post/5d492717f265da03d316a985
正文到此结束
Loading...