OSGi类加载模型是受控有向依赖图,BundleClassLoader采用“先本地导出、再导入依赖、慎委托父加载器”策略实现隔离;卸载需满足状态终止、服务解绑、无强引用三条件,模块通信须通过API Bundle定义接口并动态服务注册发现。
osgi 的类加载模型不是简单的网状结构,而是一个受控的、有向的依赖图;动态卸载也不是“删掉就完事”,而是依赖状态收敛与资源释放的协同过程。关键在于理解 bundleclassloader 的隔离逻辑和服务解耦机制。
BundleClassLoader 的隔离边界与委托规则
每个 Bundle 拥有独立的 BundleClassLoader,它不遵循传统双亲委派模型,而是采用“先本地、再导入、最后导出”的查找顺序:
- 优先在本 Bundle 的 已导出包(Export-Package) 中查找类;
- 若未命中,再查本 Bundle 显式声明的 导入包(Import-Package) —— 这些包来自其他已解析 Bundle;
- 不会自动回退到父加载器(如 AppClassLoader),除非显式配置
DynamicImport-Package(应避免滥用); - 同名类被不同 Bundle 加载时,生成的是互不兼容的
Class对象,哪怕字节码完全一致。
动态卸载的前提:服务解绑与状态终止
一个 Bundle 能被成功卸载,必须满足三个条件同时成立:
- 该 Bundle 处于 INSTALLED 或 UNINSTALLED 状态(不能是 ACTIVE 或 STARTING);
- 所有通过
BundleContext.registerService()注册的服务,已被显式unregister()或随 Bundle 停止自动注销; - 没有其他 Bundle 正在持有对该 Bundle 导出类的强引用(例如缓存了其实例、静态字段引用、线程局部变量等)。
常见失败场景:A Bundle 启动时将自身 Service 实例存入全局静态 Map,卸载 A 后 Map 仍持引用 → 类无法回收 → 卸载后残留 ClassLoader 泄漏。
模块间通信必须绕过类加载耦合
直接 new 其他 Bundle 的类或调用其静态方法,会隐式绑定类加载路径,破坏动态性。正确做法是:
- 定义统一接口(如
PaymentService),放在独立的 API Bundle 中; - 实现方 Bundle 在启动时通过
BundleContext.registerService(PaymentService.class, impl, null)发布; - 消费方 Bundle 使用
context.getServiceReference(PaymentService.class)动态获取,且应在stop()中调用ungetService(); - 接口 Bundle 必须被双方
Import-Package,但不包含实现逻辑,确保类由同一加载器提供。
诊断类加载冲突与卸载卡顿的实用手段
运行时排查需结合框架工具和 JVM 底层信息:
- 使用
ss(Equinox)或lb -s(Felix)命令查看 Bundle 状态,重点关注 RESOLVED 但无法 START 的原因(常因 Import-Package 版本不匹配); - 调用
packages命令检查某包被哪些 Bundle 导出/导入,识别重复导出或缺失导出; - 启用
-Dorg.osgi.framework.debug=true输出详细解析日志; - 用
jcmd <pid> VM.native_memory summary观察类加载器数量增长趋势,判断是否存在卸载泄漏。

















