马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
在已往近三十年的职业生存里,有几年专注于运行时情况的开发与实现。在runtime中,动态加载技能是此中的基石之一。动态加载技能是指在体系运行过程中,根据必要把步调和数据从外存或网络加载到内存中的过程。此中,lazy loading(懒加载),也被称为延伸加载,是动态加载技能的一种常见实现方式。
1. 什么是动态加载
所谓动态加载,指的是步调在运行期间必要调用某一模块的功能时,由加载器将该模块即时载入内存,举行相应的重定位处置处罚后将控制权交还调用步调。动态加载机制运用动态链
接的原理使得体系具有动态的加载和动态剖析的本领,模块只有在被调用实验时才被链接,进入体系实验。
动态加载一样寻常分为下载、加载和卸载三个操纵,此中下载完成从长途下载目标模块到本地,加载操纵来完成读入模块到内存,然后对模块未剖析的外部引用举行剖析(一样寻常地,也就是符号剖析和重定位)使之可以运行的过程。当模块不再使用时就从内存中卸载。
1.1 动态加载中的根本概念——模块
模块是数听阐明、可实验语句等步调对象的聚集,它单独定名而且可以通过名字来访问,模块筹划者可以通过有选择地在接口上输出其特性以到达控制其特性的目标。没被输出的特性不能被模块外部访问,因此防止了模块被误用而且包管了模块的封装独立性。
在模块化编程头脑中,把步调分割成多少个独立的模块,然后逐块编程和独立编译,形成独立的可加载模块,模块在被加载前保持自己的独立性。一个应用可以由多个独立模块构成,独立的模块构成一个应用有两种方法:静态链接和动态链接。静态链接是独立模块事先链接好,在办理了全部的外部引用之后,编译天生一个可实验文件,随后装入内存就可以实验。历程在实验的过程中,代码段和数据段的位置都不能改变。
可加载模块作为编译单元被独立编译,这就意味着编译器会核实一个引入模块的每个引用同其根模块的同等性。模块链是全部被加载的目标模块依据模块之间的依靠关系被动态添加的一个链表,查询时只必要对该链表举行查找。在动态加载中,模块直到被载入前都保持独立。
一个模块大概与体系中的其他模块无关,也有大概与此中的一些模块举行交互,模块之间的交互就是模块间的通讯。
1.2 动态加载的技能底子——动态链接
动态链接是体系在运行过程中根据必要把外部独立模块的可实验代码链接到体系中使之成为运行体系的一部门的过程。动态链接在实验过程中,答应在它的所在空间中增长、打扫、代替或重定位目标模块。换句话说,答应步调发生变革。在步调实验的生命周期内,步调可以参加新的模块、打扫旧的模块、乃至可以演酿成一个完全差别的步调。
动态链接的一个典范应用就是动态链接库。动态链接库是一个可以被别的应用步调共享的步调模块,此中封装了一些可以被共享的例程和资源。
动态链接库是从C语言函数库和Pascal库单元的概念发展而来的。动态链接库不消重复编译或链接,一旦装入内存, 库中的函数可以被体系中任何正在运行的应用步调所使用,而不必再将动态链接库的另一拷贝装入内存。动态链接所调用的函数代码并没有被拷贝到应用步调的可实验文件中去,而是仅仅在此中参加了所调用函数的形貌信息(通常是一些重定位信息)。只有当应用步调被装入内存开始运行时,在操纵体系的管理下,才在应用步调与相应的动态链接库之间创建链接关系。
接纳这种方法,动态链接库到达了复用代码的极限。另一个方便之处是对动态链接库中函数的修改可以主动流传到全部调用它的步调中,而不必对步调作任何改动或处置处罚。
以windows 为例,动态链接库的实现方法紧张有两种:
① 加载时动态链接(Load-time Dynamic Linking)
这种用法的条件是在编译之前已经明白知道要调用DLL中的哪几个函数,编译时在目标文件中只保存须要的链接信息,而不含DLL函数的代码;当步调实验时,使用链接信息加载DLL函数代码并在内存中将其链接入调用步调的实验空间中,其紧张目标是便于代码共享。
②运行时动态链接(Run-time Dynamic Linking)
这种方式是指在编译之前并不知道将会调用哪些DLL函数,美满是在运行过程中根据必要决定应调用哪个函数,并用LoadLibrary和GetProcAddress动态得到DLL函数的入口所在。
在C/C++中,动态加载的功能可以很容易地使用动态链接库来实现。Win32 API函数LoadLibrary和FreeLibrary提供了在运行时间加载新的功能模块和开释内存空间的功能。必要被更新的功能模块被封装在动态毗连库中,主步调使用LoadLibrary函数装载该动态链接库,然后调用此中的功能模块。必要更新某功能模块的时间,起首制止运行该功能模块,使用FreeLibrary函数卸载现有的动态链接库,通过网络大概是其他通讯端口将新的动态链接库文件发送到指定目次下,然后通过再次调用LoadLibrary函数装载新的动态链接库就可以完成动态加载新模块的功能。
2. 动态加载的一样寻常原理
动态加载机制所涉及到的关键标题包罗加载模块的格式、模块间通讯机制、符号管理等等。
可动态加载的模块有很多种格式范例。好比DOS下的.COM文件和.EXE文件,UNIX 的下a.out文件、ELF文件,以及.OBJ文件等都可以作为可加载模块被链接入体系。对于可重定位的模块还要包罗处置处罚目标代码时所需的扩展符号和重定位信息,好比符号表、重定位信息和字符串表等。模块间的通讯方法可以参照历程间的通讯方法,差别历程之间的通讯有很多种方法:消息队列、管道和共享内存等。符号管理是筹划与实现动态加载机制的条件,也是符号剖析和重定位技能的底子。目标模块按照差别的范例读入内存后,不能立刻就融入体系运行,动态加载机制还必要对其举行处置处罚,也就是办理模块中符号的外部引用(符号剖析)和重定位,这是动态加载过程中最紧张的一个步调。
动态加载是通过把符号的外部参考插入到运行时链接的目标文件中而实现,具有两个特点:
①动态的加载,就是当这个运行的模块在必要的时间才被映射入运行模块的假造内存空间中。如果一个模块在运行中要调用到另一模块中的函数,而在没有调用这个模块中的别的函数之前,不会把该模块加载到体系中(也就是内存映射)。
②动态的剖析,就是存在于另一模块中的函数被调用的时间,才会去把这个函数在假造内存空间的起始所在剖析出来,再写到调用模块中特定的存储所在内。比方,一个模块调用了另一个模块中的函数,那么该函数的所在直到被调用的时间才会被剖析出来。
3. 动态加载的代价
动态加载技能对进步体系性能,提升可扩展性,包管体系的可靠性,延伸体系生命周期,低落体系开发资本都具有非常紧张的意义。
对于步调中不可忽视的错误,大概不能完全满足用户的需求,大概使用过程中必要不绝地修改和升级等等,其对应的修改和升级都会通常导致停机维护。而停机对于如金融处置处罚体系、电信交换机体系、交通控制体系,以及一些关键的军事应用上的卫星体系等等,用户不能忍受体系制止服务。因此,体系的在线扩展如今已经成为体系软件的一个根本需求,在体系运行状态下可以通过动态的添加模块来设置体系,也就是动态加载机制。动态加载也使体系的升级变得更为方便。升级时开发职员不必重新写整个体系,即可将升级限定在体系的一个或更多部门,比方如某种算法或某个数据表格。
动态模块升级还仅取决于底子体系提供的功能API(应用编程接口),而非取决于底子体系的静态所在。这意味着,一个动态模块可支持多个产物版本,只要全部版本提供的API雷同即可。
别的,动态加载也是体系调试和功能美满的紧张本领,具有动态加载功能的体系可以随时更新体系的步调,非常便于体系的调试、维护和功能的美满。
4. 操纵体系内核的动态加载
Linux 内核模块是Linux中内存加载的一个特别部门,它可以在不重启整个体系的情况下动态加载和卸载,具有很高的机动性。在内核模块动态加载时,可以节流相称多的体系资源,而且还能制止不须要的内核模块以及它们的依靠组件被不须要地加载到内存中,从而进步体系的性能。
为了实现动态加载内核模块,Linux提供了体系调用(System call)机制。它是一种专门用于在操纵体系中实验某一操纵的特别函数。当用户或应用步调要求加载内核模块时,体系会调用符合的体系调用。比方,可以使用“insmod”体系调用动态加载内核模块,在不必要的时间用rmmod下令卸载模块。
Linux内核动态加载机制的一样寻常工作流程如下:
- 编写内核模块代码:开发者编写内核模块代码,实现特定的功能。这些代码通常以C语言编写,并使用Linux内核提供的API举行编程。
- 编译内核模块:开发者使用特定的编译工具链,将内核模块代码编译成可加载的模块文件(通常是.ko文件)。
- 加载内核模块:在体系运行期间,用户可以通过实验insmod下令,将编译好的内核模块加载到内核中。加载过程包罗将模块代码映射到内核所在空间、初始化模块等步调。
- 使用内核模块:一旦内核模块被加载,它的功能就可以被内核和其他体系组件使用。比方,如果加载的是一个驱动步调模块,那么内核就可以通过该模块与相应的硬件装备通讯。
- 卸载内核模块:当不再必要某个内核模块时,用户可以实验rmmod下令将其从内核中卸载。卸载过程包罗整理模块资源、打消模块映射等步调。
只管有一些束缚,使用eBPF 也可以实现内容模块中的细粒度动态加载。
5. Android 中的动态加载框架
Java反射通常用于检测和改变应用步调运行在假造机中的表现。使用方法Class.forName得到该类的Class文件,对得到的类用getField方法得到类的成员变量,用getMethod方法得到类的成员方法,也可以通过得到的成员方法的invoke方法实验该成员方法。对于得到的Field范例的成员变量,可以通过它的set方法更换掉该变量。
为完成Android的动态加载,必要修改体系底层源码,用到了大量的Java反射方法。让体系能启动插件组件,使用反射方法更换掉应用层发送给体系的组件名称,从而让体系启动插件组件。对于差别插件的资源加载标题,同样是通过反射实验体系资源管理等方法来办理的。
参考DruidPlugin 的实现, 我们可以得到如下的Android 动态加载框架:
下列扼要先容每个模块:
剖析模块:剖析插件安装包文件,得到插件的全部信息。
Manifest管理模块:剖析效果,管理多个插件的manifest文件的更换和更新。
资源加载模块:管理宿主与多个插件步调编译后天生的资源id索引表,以便正常地通过资源id加载资源。
署理插件模块:实现插件与宿主间的更换工作,让体系以可以或许启动插件组件。
生命周期管理模块:使插件activity组件在宿主步调中能与平常的activity一样,拥有全部的生命周期方法,可以或许正常地运行。
插件管理模块:用于管理诸如插件的添加、删除、更新等操纵。
安全模块:包管框架的安全性,确定通过网络通讯得到的插件文件没有被修
改。
启动插件模块:天生多种插件的启动器,用于加载与启动插件。
该框架位于Framework层与应用层之间,作为应用步调与Android体系的中心桥梁,通过修改体系的方法调用,从而实现插件的加载。
此中, 插件启动器的onCreate方法示例伪代码如下:
1:thread <-Reflector.getActivityThread(app) //获取ActivityThread
2:base <- getInstrumentationByReflect(thread) //得到反射Instrumentation属性
3:wrapper <- InstrumentationReplace(base) //修改
4:setInstrumentationByReflect(wrapper) //重新注入
5:setMessageHandlerByReflect(callBack) //反射设置ActivityThread的m Callback
动态加载的过程如下所示:
此中, activity,bundle,uri 等插件中涉及的组件和参数同样必要做相应的调解。
6. 前端体系的动态加载
对PHP 开发情况下MVC 模式的网站代码筹划来说,分离的构造代码路径的获取是令人头疼,也是代码运行中最容易产生错误的地方。为此,创建一个动态路径的加载应用会极大方便编码,提升开发服从。
用户起首访问入口页面视图,视图哀求控制器,控制器相应特定举动,获取相应模子数据,而后将处置处罚效果反馈到视图中出现给用户。因而,在访问哀求中需明白控制器和控制器实验的举动名称。为实现控制器类中方法能调用差别视图和模子,必要在实例化类对象之前,加载类的界说,即要完成对差别存储位置下类的引用。为优化代码的性能,节流无谓的精力斲丧,应用类主动加载方案。将主动加载类__autoLoad()方法运用pl_auto⁃load_register()重新注册改写,当代码剖析为新引用类时,主动调用改写方法,盘算路由路径所在予以实例化加载,以实现差别文件目次下的类的主动加载。示例代码如下:
- private static function autoLoad($class_name){
- $class_map=array('MySqlDB' => CORE_PATH."MySqlDB.class.php",'Base' => CORE_PATH."Base.class.php" );
- if(isset($class_map[$class_name])) require $class_map[$class_name];
- elseif(substr($class_name, -5) == "Model") require MODEL_PATH.$class_name.".class.php";
- elseif(substr($class_name, -10) == "Controller") require __URL__.$class_name.".class.php";
- }
复制代码 作为是一种网页优化技能,动态加载可以在网页加载时延伸加载不须要的资源,以进步页面的加载速率和性能。比方,在VUE中引入百度开源的echart包用于数据统计的画图。
- <template>
- <div>using echart in vue project</div>
- <div id="c1" style="width: 600px; height: 400px"></div>
- </template>
- <script setup lang="ts">
- import * as echarts from "echarts";
- import { onMounted } from "vue";
- onMounted(() => {
- const dom = document.getElementById("c1");
- var myChart = echarts.init(dom as HTMLElement);
- myChart.setOption({
- title: {
- text: "ECharts Bar usage",
- },
- tooltip: {},
- xAxis: {
- data: ["AA", "BB", "CC", "DD", "EE", "FF"],
- },
- yAxis: {},
- series: [
- {
- name: "产量",
- type: "bar",
- data: [5, 10, 15, 12, 10, 20],
- },
- ],
- });
- });
- </script>
复制代码 接纳平常的加载方式,可以在路由设置中直接导入该页面:
- import VueComponent from "@/pages/vue.vue";
- const router = createRouter({
- history: createWebHistory(),
- routes: [
- { path: "/login", component: () => import("@/pages/login.vue") },
- {
- path: "/dashboard",
- component: Dashboard,
- children: [
- { path: "/", component: () => import("@/pages/dashboard/index.vue") },
- { path: "/vue", component: VueComponent },
- { path: "/react", component: () => import("@/pages/react.vue") },
- ],
- },
- ],
- });
复制代码 会发现下载的包很大有1M多,在一样寻常的网络条件下,至少5秒以上,用户体验交差。如果接纳动态加载,性能则会有较大的提升。
- const router = createRouter({
- history: createWebHistory(),
- routes: [
- { path: "/login", component: () => import("@/pages/login.vue") },
- {
- path: "/statistic",
- component: Statistic,
- children: [
- { path: "/", component: () => import("@/pages/statistic/index.vue") },
- { path: "/vue", component: () => import("@/pages/vue.vue") },
- { path: "/react", component: () => import("@/pages/react.vue") },
- ],
- },
- ],
- });
复制代码 接纳延伸加载之后,加载时间可以回归到1秒以内,用户体验的提升是显着的。
7. 小结
动态加载技能的核心头脑是在步调运行时才加载所需的模块或组件,而不是在编译时静态链接。这种技能带来了很多上风,如代码的模块化、解耦、易于维护和扩展等。
动态加载使得代码更加模块化,低落了体系的复杂度,另有助于进步代码的可重用性,由于雷同的模块可以在多个地方使用。动态加载可以或许实当代码的解耦,有助于进步团队协作服从,并进步体系的可维护性。把握动态加载技能必要对编程语言、操纵体系、网络通讯等方面有深入的相识。因此,学习和实践动态加载技能有助于步调员进步自己的体系架构本领和编程技能。
动态加载技能在软件开发范畴具有广泛的应用场景。公道使用动态加载技能不光可以进步体系的可维护性和可扩展性,还可以提升步调员的体系架构本领和编程技能。因此,对于当代软件开发者来说,把握动态加载技能优劣常须要的。
对体系性能的寻求,意味着对用户体验的恭敬。怎样对软件体系举行全方位的优化是一个体系工程,如果您盼望成为一个对体系性能有寻求的工程师,我们近来出书的《性能之道》一书大概会给您带来现实的资助和较大的启发。
【关联阅读】
- 性能约定:API 限速
- 微秒级高性能网络初探
- 呆板学习模子性能的10个指标
- 操纵体系性能提升之内核锁优化
- 一种编译器视角下的python性能优化
- 导致体系性能失败的10个缘故起因
- 体系性能筹划的10个反模式
- API筹划中性能提升的10个发起
- 性能:关键路径的延伸分析
- API的性能约定
- 服务器性能监控
的温故知新
- 软件体系的多维性能模子
- 性能,10点体系性思考
- 浅谈面向客户端的性能优化
- 智能音箱场景下的性能优化
|