热修复原理浅析(二)

了解热修复,需要有点预热的知识,先从class文件和dex文件说起

class文件和dex文件

class文件

什么是class文件

他是一种文件格式

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

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


上图摘抄自【深入Java虚拟机】之二:Class类文件结构

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

很简单
javac hello.java

class文件的作用

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

Class类文件结构

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

这里简要说一下:

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

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

magic

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

version

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

constant_pool

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

access_flag

this_class、super_class、interfaces

fields

methods

attributes

来个大图

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

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

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

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

dex文件

什么是dex文件

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

如何手动编译一个dex文件

在build-tools里面找到dx.bat

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

  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

dex文件的作用

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

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

dex文件结构

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

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

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

dex文件长什么样子呢

来张大图

一张图理解dex

dex与class异同

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



class与dex异同之处

编年体与纪传体

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

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

JVM虚拟机简介

jvm整体结构与组成

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

编译流程

类加载器

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

下图为jvm的类加载器,

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

类加载流程

jvm内存管理和垃圾回收

java栈区

java栈帧

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

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

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

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

本地方法栈

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

方法区

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

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

堆区

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

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

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

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

垃圾回收

引用计数算法

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

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

可达性算法

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

引用类型

强软弱虚

弱引用的创建与使用

垃圾回收算法
标记-清除算法

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

复制算法

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

标记-整理算法
  1. 先遍历把可回收对象扫描出来,如B
  2. 扫描清除未标记对象
  3. 把存活的对象,进行移动,没有内存碎片

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

触发回收
  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的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")
    }
}

ClassLoader的特点

双亲代理模式

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

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

类加载共享功能

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

类加载隔离功能

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

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

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

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

ClassLoader的源码

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

那么ClassLoader有哪些子类呢

源码目录

间接子类:DexClassloader

DexClassloader源码查看

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

间接子类:PathClassLoader

PathClassLoader源码查看

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

直接子类:BaseDexClassloader

BaseDexClassloader源码查看

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

DexPathList源码

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

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

来到makePathElement方法

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

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

DexFile源码查看

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

  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实现的,就这样

类加载热修复原理

经过对PathClassLoader、DexClassLoader、BaseDexClassLoader、DexPathList的分析,我们知道,安卓的类加载器在加载一个类时会先从自身DexPathList对象中的Element数组中获取(Element[] dexElements)到对应的类,之后再加载。采用的是数组遍历的方式,不过注意,遍历出来的是一个个的dex文件。在for循环中,首先遍历出来的是dex文件,然后再是从dex文件中获取class,所以,我们只要让修复好的class打包成一个dex文件,放于Element数组的第一个元素,这样就能保证获取到的class是最新修复好的class了(当然,有bug的class也是存在的,不过是放在了Element数组的最后一个元素中,所以没有机会被拿到而已。

代码转载自:https://pan.quark.cn/s/8ce4326d996e 对于在 CentOS 7 系统中修改网卡配置文件后无法使设置生效的情况,经过实践验证,可以通过使用 nmcli 命令来进行调整。完成修改之后,需要重新启动虚拟机以使更改生效,这样操作流程即告完成。如果设置仍然无法生效,则表明虚拟机在启动过程中所获取的 IP 地址配置并非针对 eth0,此时可以对其它网卡的配置文件进行修改或将其移除。在 CentOS 7 系统中,网络配置的管理机制与早期版本存在差异,主要体现为采用了 Network Manager 服务来负责网络接口的管理。在某些情形下,尽管修改了 `/etc/sysconfig/network-scripts` 目录下的 `ifcfg-eth0` 文件,但网络配置却未能即时生效。此类问题的发生通常源于 CentOS 7 采用了不同于以往的配置读取方法。接下来将具体阐述如何借助 nmcli 命令来处理这一挑战。 以 root 用户身份登录系统并打开终端界面。nmcli 是 Network Manager 提供的命令行界面工具,它支持在命令行环境下执行网络连接的建立、编辑、查询及管理任务。针对修改 eth0 网卡配置的需求,可以遵循以下步骤进行操作: 1. 导航至 `/etc/sysconfig/network-scripts` 目录: ``` cd /etc/sysconfig/network-scripts ``` 2. 检查该目录内是否存在 `ifcfg-eth0.bak` 文件,该备份文件可能是先前调整配置时遗留下来的,若存在可能造成冲突。若发现该文件,可以选择将其删除: ``` [root@localhost netw...
代码转载自:https://pan.quark.cn/s/46fd08fb879c 网管教程 从入门到精通软件篇 ★一。★详尽的xp修复控制台指令及其应用!!! 放入xp(2000)的光盘,安装时选择R,执行修复! Windows XP(涵盖 Windows 2000)的控制台指令是在系统遭遇某些意外状况时的一种极具效用的诊断、检测以及恢复系统功能的工具。笔者确实一直期望能够将这方面的指令进行归纳,此次由老范辛苦整理了这份极具价值的秘籍。 Bootcfg bootcfg 命令用于启动配置与故障恢复(对大多数计算机而言,即 boot.ini 文件)。 带有特定参数的 bootcfg 命令仅在运用故障恢复控制台时方可使用。能够在命令行界面下运用带有不同参数的 bootcfg 命令。 用法: bootcfg /default 设定默认引导选项。 bootcfg /add 向引导清单中增添 Windows 安装。 bootcfg /rebuild 重复整个 Windows 安装流程并让用户选择需添加的项目。 注意:运用 bootcfg /rebuild 之前,应先借助 bootcfg /copy 命令备份 boot.ini 文件。 bootcfg /scan 探查用于 Windows 安装的全部磁盘并展示结果。 注意:这些结果被静态存储,并用于当前会话。若在当前会话期间磁盘配置发生变动,为获取更新的探查结果,必须先重启计算机,然后再次探查磁盘。 bootcfg /list 列示引导清单中已有的项目。 bootcfg /disableredirect 在启动引导程序中禁用重定向。 bootcfg /redirect [ PortBaudRrate] |[ useBio...
代码下载链接: https://pan.quark.cn/s/fc524f791b68 AA制程,即Active Alignment,被理解为主动对准,是一种用于确定零部件装配中相对位置的方法。在摄像头封装阶段,涉及图像传感器、镜座、马达、镜头、线路板等多个部件的重复组装,而传统的封装设备如CSP及COB等,均是依据设备设定的参数进行零部件的移动装配,因而零部件的叠加误差会逐渐增大,最终在摄像头上表现为拍照最清晰的位置可能偏离画面中心、四边清晰度不均等现象。伴随智能手机和其他高端电子产品的普及,摄像头模组的性能正日益受到重视。高分辨率、卓越的低光表现以及稳定视频输出是现代用户所期望的。在摄像头模组的制造环节,各部件的精准定位对成像质量具有决定性作用。因此,一种名为“AA制程”(Active Alignment)的前沿技术被开发出来,成为摄像头精密对准的核心技术。 AA制程,即Active Alignment,是一种在摄像头封装过程中应用的主动对准方法。该方法在多个组件装配阶段发挥作用,涵盖图像传感器、镜座、马达、镜头和线路板等部件。传统的封装方式,例如CSP(Chip Scale Package)和COB(Chip On Board),依赖于设备预设的参数进行组装,但随着组件数量的增加,误差也会累积,最终影响摄像头的表现。例如在成像质量上可能出现中心位置偏移、四角清晰度不一致等问题。 AA制程技术的核心在于实时监测与主动调整。在组装过程中,它借助先进的检测设备持续监控半成品的状态,并根据实时信息对组装部件进行精确修正,从而显著降低装配误差。通过这种技术,能够确保摄像头模组中各组件的相对位置准确无误,从而使得最终的成像效果更加稳定,特别是在中心区域和四角的清晰度上...
内容概要:本文介绍了一套基于Matlab实现的光子晶体90度弯曲波导的维时域有限差分法(2D FDTD)仿真代码,旨在通过数值模拟手段深入研究光子晶体波导中的光传播特性。该资源聚焦于电磁场与光子学领域的仿真技术应用,系统实现了FDTD算法在复杂介质结构中的建模过程,涵盖空间网格剖分、时间步进迭代、完美匹配层(UPML)边界条件处理、总场散射场(TFSF)激励源设置、介电常数分布定义及电磁场演化可视化等核心模块,能够有效分析光在90度弯曲波导中的传输效率、模式分布与反射损耗等关键性能指标。; 适合人群:具备电磁场理论基础和Matlab编程能力的研究生、科研人员以及从事光子晶体器件设计与仿真的工程技术人员。; 使用场景及目标:①用于教学演示FDTD方法的基本原理与算法流程,帮助理解麦克斯韦方程的离散化求解过程;②支撑科研工作中对光子晶体弯曲波导结构的传输特性进行仿真分析与性能优化;③作为开发更复杂光子集成器件(如分束器、滤波器)数值仿真工具的基础框架; 阅读建议:建议使用者结合经典FDTD教材(如Taflove著作)深入理解算法理论,并在Matlab环境中逐模块调试代码,重点关注电场与磁场的交替更新过程、UPML吸收边界的设计实现以及TFSF源的引入方式,从而全面提升对时域电磁仿真机制的掌握与应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值