OSGi DS原型参考未发布

时间:2018-10-16 16:03:40

标签: osgi liferay-7 vaadin8 declarative-services

我已经在Liferay 7 / DXP或osgi 6上下文中创建了一个简单的vaadin portlet,并且我注意到,如果我将osgi声明式服务与原型作用域一起使用,则引用不会被垃圾回收,但是如果我使用serviceObjects,它们就会被垃圾回收。 。为什么?

注意:我已经更新了这个问题,并在最后添加了一个更简单的示例。

我的主要组件是原型组件,该组件具有对对象的原型引用。如果我使用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 

我不明白为什么我的原型实例无法得到垃圾回收...

1 个答案:

答案 0 :(得分:0)

针对新读者的更新

从Apache Felix SCR 2.1.14开始,此问题应得到解决,原始问题中完全有效的代码将不再导致错误的行为。

原始答案

首先,感谢您为创建一个简单的示例来演示您的问题所做的努力!

您绝对正确,SCR应该在停用后释放所有组件的引用。对于ReferenceScope.PROTOTYPE_REQUIRED范围内的引用,这应该导致服务实例被释放和整理。

可悲的是,似乎SCR的这一功能已经没有使用一段时间了。我代表您提出了https://issues.apache.org/jira/browse/FELIX-5974,因此应该尽快解决,但是目前,“简单”的解决方法是自己控制生命周期。

@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() + ")";
  }
}