本文深入剖析OSGi框架的核心架构、执行流程、类隔离机制与热部署原理,系统解答OSGi与Spring的区别、Bundle生命周期管理、Tomcat集成方案等六大核心问题,帮助读者从原理到实践全面理解OSGi模块化体系。
作者介绍
大家好,我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者,也是 WWAIC(全周项目AI编程) 范式的提出者和实践者。我曾手写过一个完整的Java Web框架(从IoC容器到嵌入式Tomcat,代码全开源),也喜欢用通俗的语言拆解CPU、JVM、操作系统的运行本质。
我的技术信条:所有高深的技术,最后都能用大白话讲清楚。如果讲不清楚,说明还没真正理解。
本文收获
通过阅读本文,你将获得以下知识:
-
✅ 清晰区分OSGi架构与Spring框架的本质差异
-
✅ 掌握OSGi框架从启动到Bundle激活的完整执行流程
-
✅ 理解OSGi启动Tomcat与Tomcat作为Bundle部署服务的根本区别
-
✅ 深入OSGi多版本JAR隔离的实现原理与JVM底层支持机制
-
✅ 掌握OSGi热部署的实现原理与运行中程序替换的完整过程
-
✅ 理解OSGi Bundle生命周期及卸载时防止资源泄露的最佳实践
📑 目录
OSGi架构和Spring框架有何区别?
OSGi框架完整执行流程是什么?
OSGi启动Tomcat和Tomcat作为Bundle启动服务有何区别?
OSGi是如何实现不同版本JAR隔离的,JVM是如何支持的?
OSGi是如何实现热部署的,运行中的程序是如何实现被替换的?
OSGi框架的生命周期是什么,如何保证Bundle卸载时资源不泄露?
一、OSGi架构和Spring框架有何区别?
1.1 定位不同:规范 vs 框架
OSGi是一个模块化规范,而Spring是一个应用开发框架。
OSGi(Open Services Gateway initiative)是一个工业级标准规范,它关注的是Java应用的打包、部署和版本化管理问题。OSGi不是应用层面的框架,而是设计层面的规范。
Spring则关注的是为Java类提供依赖注入和面向切面编程的基础设施,以完成应用程序的业务逻辑组装。
1.2 粒度不同:模块级 vs 对象级
OSGi的操作粒度是Bundle(模块)——每个Bundle都有自己的ClassLoader,可以独立安装、启动、停止、更新和卸载。模块之间的依赖关系由OSGi框架统一管理。
Spring的操作粒度是Bean(对象)——在单个JVM进程内通过IoC容器管理对象的创建、装配和生命周期。
1.3 动态性:OSGi的天生优势
OSGi的程序天生拥有动态模块的特点。Bundle可以在不重启JVM的情况下动态安装、启动、停止、更新和卸载。当服务提供方发生变化时,OSGi通过代理层包装服务对象,使得热更新成为可能。
Spring本身不具备这种模块级的动态性,但可以通过Spring Dynamic Modules(Spring DM) 桥接OSGi,将Spring的IoC能力与OSGi的模块化能力结合。
1.4 互补关系:各司其职
OSGi与Spring本质上是互补而非竞争的关系:
-
OSGi负责:协调模块与模块之间的调用和依赖关系
-
Spring负责:在一个模块内部进行更细粒度的对象管理
💡 一句话总结:OSGi管“模块间”,Spring管“模块内”。前者是模块化操作系统,后者是对象管理框架。
二、OSGi框架完整执行流程是什么?
2.1 OSGi框架启动流程
OSGi框架的启动是一个多层次的过程:
第一步:框架初始化
-
创建并初始化OSGi框架实例(Framework)
-
启动框架(调用Framework.start()),将框架移入ACTIVE状态
第二步:系统Bundle启动
-
系统Bundle(System Bundle)由框架启动,且只启动一次
-
创建服务注册中心(Service Registry),作为各种事件的分发场所
第三步:安装用户Bundle
-
通过BundleContext.installBundle()安装Bundle JAR
-
框架进行Bundle有效性校验,分配唯一的Bundle ID
-
Bundle进入INSTALLED状态
2.2 Bundle的解析与启动
第四步:依赖解析(Resolution)
-
框架解析器(Resolver)读取Bundle的MANIFEST.MF
-
检查Import-Package、Require-Bundle等依赖声明是否都能被满足
-
若所有依赖满足,Bundle进入RESOLVED状态
第五步:Bundle激活(Activation)
-
调用Bundle.start()方法
-
框架为Bundle创建BundleContext上下文对象
-
框架创建Bundle专属的ClassLoader
-
回调BundleActivator.start(BundleContext)方法
-
若start()正常执行完毕,Bundle进入ACTIVE状态
2.3 完整的生命周期状态机
OSGi规范定义了Bundle的6种生命周期状态:
| UNINSTALLED | 已卸载,Bundle对象仍可操作但资源不可用 | 1 |
| INSTALLED | 已安装,通过校验但依赖未解析 | 2 |
| RESOLVED | 已解析,所有依赖满足,可启动 | 4 |
| STARTING | 启动中,start()方法正在执行 | 8 |
| ACTIVE | 已激活,正常运行 | 32 |
| STOPPING | 停止中,stop()方法正在执行 | 16 |
状态转换规则:INSTALLED → RESOLVED → STARTING → ACTIVE,停止时反向。
💡 关键点:STARTING和STOPPING是瞬时状态,表示Activator方法正在执行但尚未返回。
三、OSGi启动Tomcat和Tomcat作为Bundle启动服务有何区别?
3.1 两种部署模式的本质差异
模式一:在OSGi中嵌入Tomcat(OSGi启动Tomcat)
将Tomcat本身打包成一个OSGi Bundle,在OSGi框架中启动。Tomcat作为OSGi服务(HttpService)向外提供HTTP能力。Web应用本身也以Bundle的形式部署,通过OSGi的HttpService动态注册Servlet和资源。
模式二:在Tomcat中嵌入OSGi(Tomcat作为容器启动OSGi)
将OSGi框架的JAR放入Web应用的WEB-INF/lib目录,在Tomcat的Web容器中启动OSGi框架。但Tomcat本身不具备OSGi Bundle解析、激活、服务导出/导入等能力,需要额外的桥接方案(如Apache Felix HTTP Bridge)。
3.2 核心区别对比
| 主导方 | OSGi框架 | Tomcat容器 |
| 部署粒度 | Bundle(细粒度) | WAR(粗粒度) |
| 模块协作 | Bundle间可共享Package、发布/引用服务 | Web应用间几乎无协作 |
| 热部署 | 支持Bundle级热部署 | 需重启Web应用 |
| 实现复杂度 | 需将Tomcat打包为Bundle | 需桥接OSGi与Servlet容器 |
3.3 实际应用场景
现代OSGi应用服务器(如Eclipse Virgo)采用模式一:其核心是一个OSGi容器,任何OSGi兼容的Bundle都可以直接部署。WAR文件在部署时会被转换(transform) 为OSGi Bundle并安装到Tomcat中。
而Liferay 7.x则展示了另一种思路:完全拥抱OSGi模块化思维,以Bundle为单元构建、部署和诊断。
💡 建议:新项目优先考虑OSGi启动Tomcat模式,充分发挥OSGi的模块化和动态性优势。
四、OSGi是如何实现不同版本JAR隔离的,JVM是如何支持的?
4.1 问题背景:传统JVM的类加载局限
在传统JVM中,同一个全限定名的类只会被加载一次。当不同版本的JAR包含相同包名的类时,JVM只会加载第一个找到的版本,后续同名类被忽略。这导致了经典的“JAR包冲突”问题。
标准JVM使用树状模型(Tree Model) 进行类加载(双亲委派模型),无法支持同一类的不同版本共存。
4.2 OSGi的解决方案:每个Bundle独立ClassLoader
OSGi为每个Bundle提供自己的ClassLoader。每个Bundle通过MANIFEST.MF明确声明:
-
Export-Package:导出哪些包供其他Bundle使用
-
Import-Package:需要导入哪些包
4.3 图形模型 vs 树状模型
OSGi使用图形模型(Graph Model) 进行类加载,而非JVM的树状模型:
-
树状模型(JVM):类加载器形成单一继承链,父加载器加载的类对子加载器可见,但反之不然
-
图形模型(OSGi):Bundle之间可以直接共享类,无需将JAR提升到父类加载器
这种图形模型允许同时部署同一类的不同版本且无冲突。
4.4 JVM如何支持这种隔离?
OSGi的类隔离并非依赖JVM的特殊支持,而是利用JVM已有的ClassLoader机制:
每个ClassLoader维护自己的类命名空间:不同ClassLoader加载的同名类在JVM中被视为不同类型
类卸载依赖GC:当ClassLoader不再被引用时,其加载的所有类可被GC回收
OSGi R4及以上版本支持重叠的导出包(overlapping exported packages)
💡 本质:OSGi通过为每个Bundle分配独立的ClassLoader,在JVM的类加载机制之上构建了一层模块化的类命名空间隔离。
五、OSGi是如何实现热部署的,运行中的程序是如何实现被替换的?
5.1 热部署的本质
所谓热部署,就是在不停止服务运行的前提下动态更新服务内容,达到100%在线率的目标。
Java中一个类一旦被加载就无法被卸载(除非其ClassLoader被GC回收)。OSGi的破解之道是:以Bundle为粒度进行更新,连同类加载器一起换掉。
5.2 热部署的两种实现方式
方式一:Bundle.update()
直接调用Bundle.update()方法更新Bundle:
-
框架创建新的Bundle修订版(Revision)
-
重新解析依赖、重建ClassLoader
-
在ACTIVE状态重启Activator和已注册服务
局限性:如果Bundle是singleton模式且有对外暴露的package,update()可能失败。
方式二:uninstall + install + start
先卸载旧Bundle,再安装并启动新Bundle:
-
uninstall时,若Bundle有对外暴露的package且被其他Bundle使用,框架会保留其ClassLoader
-
执行refresh命令,找到依赖了旧Bundle的所有Bundle,将它们置为unresolve状态,解除对旧ClassLoader的引用
-
被卸载的Bundle的类随后可被GC回收
5.3 服务层的热更新机制
OSGi中,热部署最关键的方式是使用服务(Service) :
新版本Bundle注册同名服务,但设置更高的SERVICE_RANKING属性
服务消费者通过OSGi的代理层调用服务
框架自动将请求路由到排名最高的服务实现
依赖方无需任何修改即可使用新版本服务
5.4 热部署的注意事项
热部署并非“银弹”:
-
更新对外暴露package的Bundle需要refresh操作
-
服务消费者需正确处理服务的动态来去(通过ServiceListener或DS的@Reference)
-
旧的ClassLoader只有在没有任何引用时才会被GC回收
💡 最佳实践:优先使用声明式服务(DS) 管理服务依赖,框架会自动处理服务的动态绑定和解绑。
六、OSGi框架的生命周期是什么,如何保证Bundle卸载时资源不泄露?
6.1 Bundle生命周期管理
OSGi框架对Bundle的生命周期进行全流程管理,覆盖安装、启动、停止、更新、卸载和监控。生命周期层精确定义了每个阶段的操作。
6.2 卸载流程:stop → uninstall → GC
第一步:Bundle.stop()
调用Bundle.stop()时:
-
框架回调BundleActivator.stop(BundleContext)方法
-
开发者在此方法中必须手动停止线程、关闭连接、释放文件句柄
-
框架自动注销该Bundle注册的所有OSGi服务
-
Bundle从ACTIVE回到RESOLVED状态
第二步:Bundle.uninstall()
调用Bundle.uninstall()时:
-
框架从活跃列表中移除该Bundle
-
清除依赖连线(Wire)
-
Bundle进入UNINSTALLED状态
-
Bundle的自定义ClassLoader失去所有来自框架的强引用
第三步:JVM垃圾回收
-
当ClassLoader不再被任何地方引用时,其加载的所有Class对象失去引用
-
下一次Full GC时,这些Class对象(包括静态变量、方法区元数据)被彻底回收
6.3 资源泄露的常见原因与防范
OSGi规范要求:停止Bundle时必须释放其分配的所有资源。但OSGi并未提供强制执行的机制。
常见泄露场景:
| 线程未停止 | 线程持有Bundle类的引用,ClassLoader无法GC | 在stop()中interrupt()或设置退出标志 |
| 静态变量持有引用 | 其他Bundle的静态变量拽住本Bundle对象 | 避免将本Bundle实例存入外部静态集合 |
| JDK内置缓存 | 如DriverManager缓存JDBC驱动 | 在stop()中手动deregister |
| 服务引用未释放 | 其他Bundle仍持有本Bundle的服务代理 | 使用DS自动管理,或在stop()中显式清理 |
6.4 最佳实践:使用声明式服务(DS)
现代OSGi开发中,强烈推荐使用声明式服务(DS) 替代手动BundleActivator:
java
@Component
public class MyComponent {
@Activate
void activate() {
// 初始化资源
}
@Deactivate
void deactivate() {
// 自动清理:框架会确保此方法被调用
// 线程停止、资源释放都在这里完成
}
@Reference
void bindSomeService(SomeService service) {
// 服务自动注入
}
void unbindSomeService(SomeService service) {
// 服务自动解绑
}
}
DS组件在@Deactivate阶段会自动:
-
注销注册的服务
-
释放引用的服务
-
调用开发者定义的清理逻辑
💡 核心原则:卸载是否成功,取决于开发者是否在stop/Deactivate中彻底清理了所有外部引用。框架负责切断官方引用链,开发者负责清理自定义引用。
📝 总结
| OSGi vs Spring | OSGi管模块间(规范),Spring管模块内(框架),互补关系 |
| 完整执行流程 | 框架启动→系统Bundle→安装→解析(RESOLVED)→激活(ACTIVE) |
| Tomcat部署模式 | OSGi启动Tomcat(Bundle级部署)优于Tomcat启动OSGi(需桥接) |
| 多版本JAR隔离 | 每个Bundle独立ClassLoader,使用图形模型替代树状模型 |
| 热部署原理 | Bundle级替换+ClassLoader更换+服务代理+Service Ranking |
| 资源不泄露 | stop清理→uninstall断引用→GC回收,配合DS最佳实践 |
如果本文对你有帮助,欢迎点赞👍、收藏⭐、评论💬,让更多开发者了解OSGi的魅力!
有任何疑问,欢迎在评论区留言交流~


