欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OSGi 内存泄露

程序员文章站 2024-01-08 17:56:34
...

问题背景:

1.内存泄漏:OSGi容器中,不同版本的类永久共存。

 

 OSGi 动态化模块系统,允许不同版本的相同类共存,OSGi只需在方法区内存加载新的classloader。

 但是客观上会占用更多的内存。如果对OSGi动态性使用不当,可能会因为不正确持有某个过期模块(被更新或卸载的模块)中一个类的实例,导致该类的类加载器无法被回收,进而导致该类加载器下所有类都无法被GC回收掉。

为了防止保留类加载器带来的内存泄露,我们必须使用弱键和弱值。目标是不在内存中保持一个已卸载的bundle的类空间。我们必须使用弱值,因为每个映射项目的值(BridgeClassLoader)都强引用着键(ClassLoader),于是以此方式否定它的弱点。这是WeakHashMap javadoc规定的标准建议。通过使用一个弱缓存我们避免了跟踪所有的bundle,而且不必对他们的生命周期做出反应。

问题二:运行时的ClassNotFoundException

情形1: 间接引用带来的问题

原因:Plugin_1 没有直接使用Plugin_2Java文件,但运行时使用了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类加载器。

 

JavaJ2EE的类加载模型都是层次化的,只能委托给上一层类加载器;

 

OSGi类加载模型则是网络图状的,可以在bundle间互相委托。——这样更合理,因为bundle间的依赖关系并不是层次化的。

 

 

优点

找不到类时的错误提示更友好。假如bundleE不存在,则bundleC就不会被解析成功,会有错误消息提示为何未能解析;而不是报错ClassNotFoundExceptionNoClassDefFoundError

效率更高。

 

在标准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 bundleOSGi 4引入的概念,它是一种不完整的bundle,必须要附加到一个host bundle上才能工作;fragment能够为host bundle添加类或资源,在运行时,fragment中的类会合并到host bundle的内部classpath中。

 

fragment有什么作用?

 

【场景1bundle中有针对特定平台的代码

假设bundle对不同平台的实现方式稍有不同,WindowsLinux下代码有不同之处,即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:灵活的类加载器架构

高并发环境下死锁问题

都会锁定自己的类加载器实例。

 

相关标签: OSGi 内存泄露

上一篇:

下一篇: