OSGi 内存泄露
问题背景:
1.内存泄漏:OSGi容器中,不同版本的类永久共存。
OSGi 动态化模块系统,允许不同版本的相同类共存,OSGi只需在方法区内存加载新的classloader。
但是客观上会占用更多的内存。如果对OSGi动态性使用不当,可能会因为不正确持有某个过期模块(被更新或卸载的模块)中一个类的实例,导致该类的类加载器无法被回收,进而导致该类加载器下所有类都无法被GC回收掉。
为了防止保留类加载器带来的内存泄露,我们必须使用弱键和弱值。目标是不在内存中保持一个已卸载的bundle的类空间。我们必须使用弱值,因为每个映射项目的值(BridgeClassLoader)都强引用着键(ClassLoader),于是以此方式否定它的“弱点”。这是WeakHashMap javadoc规定的标准建议。通过使用一个弱缓存我们避免了跟踪所有的bundle,而且不必对他们的生命周期做出反应。
问题二:运行时的ClassNotFoundException
情形1: 间接引用带来的问题
原因:Plugin_1 没有直接使用Plugin_2的Java文件,但运行时使用了Plugin_2中的Jar包中的文件。
解决措施:将Plugin_2中的Jar包导出来, 同时在Plugin_1中导入这些包。
情形2: 利用String反射到类或对象的时候出现的问题
Plugin_1涉及到由String转化为类对象Plugin_2_ClassA(Plugin_2_ClassA表示Plugin_2中的Class A)的操作, 这个时候调用非常隐晦,也需要按照情形1进行处理。
情形3: 加载顺序不对带来的问题
IDE环境下, 这个问题出现在控制台中
IDE环境下, 这个问题出现在configuration目录下面的Log目录中。
1. 如果是IDE环境下出现的问题, Run Configuration中配置一下启动顺序
2. 发布环境下, config.ini 需要做新的调整,先加载底层插件,再加载依赖插件,最后加载不被任何插件依赖的项目 (参见文章开始的参考资料4)
3. 发布环境下, 如果问题一再出现, 请删除\configuration目录下的org.eclipse.osgi 目录, 再进行启动
OSGi类加载流程
OSGi每个模块都有自己独立的classpath。
如何实现这一点呢?是因为OSGi采取了不同的类加载机制:
——OSGi为每个bundle提供一个类加载器,该加载器能够看到bundle Jar文件内部的类和资源;
——为了让bundle能互相协作,可以基于依赖关系,从一个bundle类加载器委托到另一个bundle类加载器。
Java和J2EE的类加载模型都是层次化的,只能委托给上一层类加载器;
而OSGi类加载模型则是网络图状的,可以在bundle间互相委托。——这样更合理,因为bundle间的依赖关系并不是层次化的。
优点
找不到类时的错误提示更友好。假如bundleE不存在,则bundleC就不会被解析成功,会有错误消息提示为何未能解析;而不是报错ClassNotFoundException或NoClassDefFoundError。
效率更高。
在标准Java类加载模型中,总是会在classpath那一长串列表中进行查找;而OSGi类加载器能立即知道去哪里找类。
流程
Step 1: 检查是否java.*,或者在bootdelegation中定义
当bundle类加载器需要加载一个类时,首先检查包名是否以java.*开头,或者是否在一个特定的配置文件(org.osgi.framework.bootdelegation)中定义。如果是,则bundle类加载器立即委托给父类加载器(通常是Application类加载器)。
这么做有两个原因:
唯一能够定义java.*包的类加载器是bootstrap类加载器,这个规则是JVM要求的。如果OSGI bundle类加载器试图加载这种类,则会抛Security Exception。
一些JVM错误地假设父加载器委托永远会发生,内部VM类就能够通过任何类加载器找到特定的其他内部类。所以OSGi提供了org.osgi.framework.bootdelegation属性,允许对特定的包(即那些内部VM类)使用父加载器委托。
Step 2: 检查是否在Import-Package中声明
检查是否在Import-Package中声明。如果是,则找到导出包的bundle,将类加载请求委托给该bundle的类加载器。如此往复。
Step 3: 检查是否在Require-Bundle中声明
检查是否在Require-Bundle中声明。如果是,则将类加载请求委托给required bundle的类加载器。
Step 4: 检查是否bundle内部类
检查是否是该bundle内部的类,即当前JAR文件中的类。
Step5: 检查fragment
搜索可能附加在当前bundle上的fragment中的内部类。
什么是fragment?
Fragment bundle是OSGi 4引入的概念,它是一种不完整的bundle,必须要附加到一个host bundle上才能工作;fragment能够为host bundle添加类或资源,在运行时,fragment中的类会合并到host bundle的内部classpath中。
fragment有什么作用?
【场景1】bundle中有针对特定平台的代码
假设bundle对不同平台的实现方式稍有不同,Windows和Linux下代码有不同之处,即bundle中有针对特定平台的代码。
我们应该为每个平台提供不同的bundle吗?——显然不能,因为那会造成代码重复。
或者将共同代码放到bundle A中,Windows特定的那部分代码放到bundle Pwin中,Linux特定的那部分代码放到bundle Plinux中。——有问题:Pwin肯定要依赖A中某些包,我们就必须在A中导出这些包,如果只有Pwin用到这些包岂不破坏封装性。
最好的解决方法是把Pwin作为fragment,附加到A中。这样Pwin就能看到A中的所有包,A也能看到Pwin的所有包。
【场景2】针对不同国家用户提供不同的i18n
GUI程序通常会通过properties文件定义i18n信息,可以将不同的i18n存到不同的fragment中。运行时用户只需要下载host bundle以及特定的i18n fragment即可,不需要把其他国家的i18n也下载下来。
Step6: 动态类加载
OSGi:灵活的类加载器架构
高并发环境下死锁问题
都会锁定自己的类加载器实例。