OSGi DS 原型参考未发布
OSGi DS Prototype Reference not released
我在 Liferay 7/DXP 或 osgi 6 上下文中创建了一个简单的 vaadin portlet,我注意到如果我使用带有原型范围的 osgi 声明性服务,我的引用不会被垃圾收集,但如果我这样做使用服务对象。为什么?
注意:我已经更新了这个问题,并在最后放了一个更简单的例子。
我的主要组件是一个原型组件,它有一个对象的原型引用。如果我使用 osgi 声明性服务来声明我的依赖项(以下清单中的 HelloPresenter),那么我的依赖项将不会被释放并永远保留在堆中:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceScope;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
@Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
private HelloPresenter helloPresenter;
@Override
protected void init(VaadinRequest request) {
this.setContent(helloPresenter.getViewComponent());
}
}
所以我尝试以编程方式为我的 HelloPresenter 获取我的服务实例,这工作正常:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
@Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
private HelloPresenter helloPresenter;
@Override
protected void init(VaadinRequest request) {
Bundle bundle = FrameworkUtil.getBundle(HelloPresenter.class);
ServiceReference<HelloPresenter> serviceReference = bundle.getBundleContext().getServiceReference(HelloPresenter.class);
ServiceObjects<HelloPresenter> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
helloPresenter = serviceObjects.getService();
this.addDetachListener(event -> serviceObjects.ungetService(helloPresenter));
helloPresenter.init();
this.setContent(helloPresenter.getViewComponent());
}
}
所以我想知道为什么我的 HelloPresenter 在第一种情况下不会被 osgi 框架释放,但在第二种情况下会释放?
我的 portlet (UI) 对象也是用
创建的
serviceObjects.getService();
并与
一起发布
serviceObjects.ungetService(uiObject);
我尝试了其他场景,在我的 HelloPresenter 中设置了另一个原型引用,这也将生成一个不会被释放和垃圾收集的引用。所以我的经验是,每当你创建一个包含原型引用的服务对象时,在释放服务对象后,引用不会被释放并卡在 jvm 堆中
所以我觉得要么我做错了什么,要么错过了一个参数,这使得我的原型引用永远不会被释放,或者混合 osgi 声明服务和 serviceObjects 有问题......
你知道我如何让我的第一个例子工作吗?我想使用注释并确保它们在关闭我的 portlet 后成为垃圾收集器 ui。
更新
我创建了一个更多示例,其中包含用于执行 gogo shell 命令的单例组件和还包含原型引用的原型对象:
@Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
AProInAProComp aProInAProComp;
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}
每次我执行命令 (GogoShellService#atest) 都会创建一个新的原型实例,之后也应该被销毁,但我仍然可以在我的堆中看到这个对象,并且 运行 垃圾回收没有不要清理这个...
osgi 调试输出如下:
[org_apache_felix_scr:94] getService {de.foo.bar.bax.gogo.GogoShellService}={osgi.command.function=atest, component.name=de.foo.bar.bax.gogo.GogoShellService, component.id=2944, osgi.command.scope=test, service.id=7827, service.bundleid=51, service.scope=bundle}: stack of references: []
APrototypeComponent(2942)] ServiceFactory.getService()
AProInAProComp(2941)] ServiceFactory.getService()
AProInAProComp(2941)] This thread collected dependencies
AProInAProComp(2941)] getService (ServiceFactory) dependencies collected.
AProInAProComp(2941)] Querying state active
AProInAProComp(2941)] Changed state from active to active
APrototypeComponent(2942)] This thread collected dependencies
APrototypeComponent(2942)] getService (ServiceFactory) dependencies collected.
APrototypeComponent(2942)] Querying state satisfied
APrototypeComponent(2942)] For dependency aProInAProComp, optional: false; to bind: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Changed state from satisfied to active
APrototypeComponent(2942)] ServiceFactory.ungetService()
APrototypeComponent(2942)] DependencyManager: aProInAProComp close component unbinding from org.apache.felix.scr.impl.manager.ComponentContextImpl@3927bc1d at tracking count 1 refpairs: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Querying state active
APrototypeComponent(2942)] Changed state from active to satisfied
我不明白为什么我的原型实例无法被垃圾回收...
新读者更新
从 Apache Felix SCR 2.1.14 开始,这个问题应该得到解决,原始问题中完全有效的代码将不再导致错误行为。
原答案
首先,感谢您创建一个简单的示例来演示您的问题!
您完全正确,SCR 应该在停用后释放您的组件的所有引用。对于 ReferenceScope.PROTOTYPE_REQUIRED
范围内的引用,这应该导致服务实例被释放和整理。
遗憾的是,好像 SCR 的这个功能已经有一段时间没用了。我代表你提出了 https://issues.apache.org/jira/browse/FELIX-5974,所以它应该很快被修复,但现在 "easy" 解决方法是你自己控制生命周期。
@Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
ComponentServiceObjects<AProInAProComp> cso;
AProInAProComp aProInAProComp
@Activate
void start() {
aProInAProComp = cso.getService();
}
@Deactivate
void stop() {
cso.ungetService(aProInAProComp);
aProInAProComp = null;
}
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}
我在 Liferay 7/DXP 或 osgi 6 上下文中创建了一个简单的 vaadin portlet,我注意到如果我使用带有原型范围的 osgi 声明性服务,我的引用不会被垃圾收集,但如果我这样做使用服务对象。为什么?
注意:我已经更新了这个问题,并在最后放了一个更简单的例子。
我的主要组件是一个原型组件,它有一个对象的原型引用。如果我使用 osgi 声明性服务来声明我的依赖项(以下清单中的 HelloPresenter),那么我的依赖项将不会被释放并永远保留在堆中:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceScope;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
@Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
private HelloPresenter helloPresenter;
@Override
protected void init(VaadinRequest request) {
this.setContent(helloPresenter.getViewComponent());
}
}
所以我尝试以编程方式为我的 HelloPresenter 获取我的服务实例,这工作正常:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
@Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
private HelloPresenter helloPresenter;
@Override
protected void init(VaadinRequest request) {
Bundle bundle = FrameworkUtil.getBundle(HelloPresenter.class);
ServiceReference<HelloPresenter> serviceReference = bundle.getBundleContext().getServiceReference(HelloPresenter.class);
ServiceObjects<HelloPresenter> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
helloPresenter = serviceObjects.getService();
this.addDetachListener(event -> serviceObjects.ungetService(helloPresenter));
helloPresenter.init();
this.setContent(helloPresenter.getViewComponent());
}
}
所以我想知道为什么我的 HelloPresenter 在第一种情况下不会被 osgi 框架释放,但在第二种情况下会释放?
我的 portlet (UI) 对象也是用
创建的serviceObjects.getService();
并与
一起发布serviceObjects.ungetService(uiObject);
我尝试了其他场景,在我的 HelloPresenter 中设置了另一个原型引用,这也将生成一个不会被释放和垃圾收集的引用。所以我的经验是,每当你创建一个包含原型引用的服务对象时,在释放服务对象后,引用不会被释放并卡在 jvm 堆中
所以我觉得要么我做错了什么,要么错过了一个参数,这使得我的原型引用永远不会被释放,或者混合 osgi 声明服务和 serviceObjects 有问题......
你知道我如何让我的第一个例子工作吗?我想使用注释并确保它们在关闭我的 portlet 后成为垃圾收集器 ui。
更新
我创建了一个更多示例,其中包含用于执行 gogo shell 命令的单例组件和还包含原型引用的原型对象:
@Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
AProInAProComp aProInAProComp;
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}
每次我执行命令 (GogoShellService#atest) 都会创建一个新的原型实例,之后也应该被销毁,但我仍然可以在我的堆中看到这个对象,并且 运行 垃圾回收没有不要清理这个...
osgi 调试输出如下:
[org_apache_felix_scr:94] getService {de.foo.bar.bax.gogo.GogoShellService}={osgi.command.function=atest, component.name=de.foo.bar.bax.gogo.GogoShellService, component.id=2944, osgi.command.scope=test, service.id=7827, service.bundleid=51, service.scope=bundle}: stack of references: []
APrototypeComponent(2942)] ServiceFactory.getService()
AProInAProComp(2941)] ServiceFactory.getService()
AProInAProComp(2941)] This thread collected dependencies
AProInAProComp(2941)] getService (ServiceFactory) dependencies collected.
AProInAProComp(2941)] Querying state active
AProInAProComp(2941)] Changed state from active to active
APrototypeComponent(2942)] This thread collected dependencies
APrototypeComponent(2942)] getService (ServiceFactory) dependencies collected.
APrototypeComponent(2942)] Querying state satisfied
APrototypeComponent(2942)] For dependency aProInAProComp, optional: false; to bind: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Changed state from satisfied to active
APrototypeComponent(2942)] ServiceFactory.ungetService()
APrototypeComponent(2942)] DependencyManager: aProInAProComp close component unbinding from org.apache.felix.scr.impl.manager.ComponentContextImpl@3927bc1d at tracking count 1 refpairs: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Querying state active
APrototypeComponent(2942)] Changed state from active to satisfied
我不明白为什么我的原型实例无法被垃圾回收...
新读者更新
从 Apache Felix SCR 2.1.14 开始,这个问题应该得到解决,原始问题中完全有效的代码将不再导致错误行为。
原答案
首先,感谢您创建一个简单的示例来演示您的问题!
您完全正确,SCR 应该在停用后释放您的组件的所有引用。对于 ReferenceScope.PROTOTYPE_REQUIRED
范围内的引用,这应该导致服务实例被释放和整理。
遗憾的是,好像 SCR 的这个功能已经有一段时间没用了。我代表你提出了 https://issues.apache.org/jira/browse/FELIX-5974,所以它应该很快被修复,但现在 "easy" 解决方法是你自己控制生命周期。
@Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
@Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
ComponentServiceObjects<AProInAProComp> cso;
AProInAProComp aProInAProComp
@Activate
void start() {
aProInAProComp = cso.getService();
}
@Deactivate
void stop() {
cso.ungetService(aProInAProComp);
aProInAProComp = null;
}
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
@Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}