1. 引言
在程序执行过程中,链接(Linking)与加载(Loading)是两个至关重要的环节。链接的目的是将多个目标文件组合成一个可执行模块,而加载则是将该模块载入内存中,准备执行。
本文将从开发者的角度出发,深入浅出地讲解动态链接(Dynamic Linking)与动态加载(Dynamic Loading)的区别和应用场景,帮助你理解它们在 Java 程序运行机制中的作用。
2. 链接(Linking)
链接是指将程序的不同模块或函数连接起来,以便程序可以顺利执行的过程。链接器(Linker)会将多个目标文件(Object File)合并成一个完整的可执行文件。简单来说,链接就是把代码和数据“拼”在一起。
链接可以发生在两个阶段:
- 编译时(Compile Time):源代码编译成目标代码时完成链接
- 加载时(Load Time):程序被加载进内存时进行链接
根据链接发生的时间,链接可以分为:
- 静态链接(Static Linking)
- 动态链接(Dynamic Linking)
下图展示了静态链接与动态链接的区别:
✅ 静态链接:在编译阶段就将依赖库直接复制进可执行文件中
✅ 动态链接:依赖库在运行时才被链接,多个程序可以共享一个库
2.1 静态链接
静态链接是在程序编译阶段完成的。链接器将所有目标文件和命令行参数整合,生成一个完整的可执行文件。
优点:程序独立,运行时不依赖外部库
缺点:体积大,更新库需重新编译程序
2.2 动态链接
动态链接旨在解决静态链接带来的资源浪费问题。例如,每个程序都可能使用 printf()
函数,如果都复制一份,不仅浪费磁盘空间,也占用内存。
动态链接的处理方式是:
- 编译时只记录依赖库信息
- 运行时由动态链接器将程序与共享库绑定
例如,Linux 下的 .so
文件、Windows 下的 .dll
文件,就是动态链接库。
3. 加载(Loading)
加载是指将程序从磁盘加载到内存中执行的过程。程序在运行前必须被加载到内存,才能被 CPU 执行。
动态加载(Dynamic Loading)
动态加载是指程序在运行时根据需要加载某些模块或库。加载完成后,程序可以访问这些模块中的函数和变量,执行完毕后还可以卸载它们。
✅ 应用场景:
- 插件系统(如 Apache Web Server 加载
.dso
文件) - 按需加载资源,节省内存开销
⚠️ 动态加载的关键在于“按需”,不是所有模块一开始就加载到内存中。
在 Java 中,我们可以通过 ClassLoader
实现类的动态加载。例如:
ClassLoader classLoader = new MyClassLoader();
Class<?> loadedClass = classLoader.loadClass("com.example.MyClass");
4. 动态链接 vs 动态加载
先来看一下链接和加载的关系:
流程如下:
- 编译器将源码翻译成目标模块
- 链接器将多个目标模块合并成可执行模块
- 加载器将可执行模块加载进内存执行
4.1 动态加载的特点
- 在运行时按需加载模块
- 适用于大型程序或插件系统
- 可以减少内存占用和启动时间
4.2 动态链接的特点
- 在运行时将程序与共享库绑定
- 多个程序共享一个库
- 通过“存根(Stub)”记录函数在哪个库中定义
✅ 类似之处:两者都延迟加载模块
❌ 区别在于:动态链接是程序与库之间的绑定,而动态加载是程序主动加载模块
5. 总结
本文介绍了链接与加载的基本概念,以及它们在程序执行过程中的作用。
我们重点讨论了:
- 链接的两种类型:静态链接 vs 动态链接
- 加载的两种方式:静态加载 vs 动态加载
- 动态链接与动态加载的区别与联系
✅ 动态链接用于共享库,减少冗余代码
✅ 动态加载用于按需加载模块,节省内存资源
作为 Java 开发者,理解这些机制有助于我们更好地掌握类加载器(ClassLoader)、JVM 启动过程、以及大型系统模块化设计中的底层原理。在实际开发中,尤其是在做插件化架构、热部署、微服务模块加载时,这些概念尤为重要。