Rus*_*tam 5 memory-leaks ejb cdi weld jakarta-ee
使用 WildFly 18.0.1 创建多个 @Dependent 实例来测试内存泄漏
@Dependent
public class Book {
@Inject
protected GlobalService globalService;
protected byte[] data;
protected String id;
public Book() {
}
public Book(GlobalService globalService) {
this.globalService = globalService;
init();
}
@PostConstruct
public void init() {
this.data = new byte[1024];
Arrays.fill(data, (byte) 7);
this.id = globalService.getId();
}
}
@ApplicationScoped
public class GlobalFactory {
@Inject
protected GlobalService globalService;
@Inject
private Instance<Book> bookInstance;
public Book createBook() {
return bookInstance.get();
}
public Book createBook2() {
Book b = bookInstance.get()
bookInstance.destroy(b);
return b;
}
public Book createBook3() {
return new Book(globalService);
}
}
@Singleton
@Startup
@ConcurrencyManagement(value = ConcurrencyManagementType.BEAN)
public class GlobalSingleton {
protected static final int ADD_COUNT = 8192;
protected static final AtomicLong counter = new AtomicLong(0);
@Inject
protected GlobalFactory books;
@Schedule(second = "*/1", minute = "*", hour = "*", persistent = false)
public void schedule() {
for (int i = 0; i < ADD_COUNT; i++) {
books.createBook();
}
counter.addAndGet(ADD_COUNT);
System.out.println("Total created: " + counter);
}
}
Run Code Online (Sandbox Code Playgroud)
创建 200k 本书后,我得到 OutOfMemoryError。我很清楚,因为它写在这里
CDI | 应用程序/从属范围| 内存泄漏 - javax.enterprise.inject.Instance<T> 未收集垃圾
但我还有一个问题:
为什么 OutOfMemoryError 仅在 Book 中的 GlobalService 是无状态 EJB 时发生,而不是在 @ApplicationScoped 时发生。我认为 GlobalFactory 的 @ApplicationScoped 足以得到 OutOfMemoryError。
哪种方法更好 createBook2() 或 createBook3()?两者都消除了 OutOfMemoryError 的问题
我对(1)印象深刻和惊讶。必须自己尝试,确实如您所说!在 WildFly 18.0.1 和 15.0.1 上尝试过,行为相同。我什至解雇了 jconsole 并且堆使用图有一个非常健康的锯齿状,对于这种@ApplicationScoped情况,每次 GC 后内存都准确地返回到基线。然后,我开始尝试。
我无法相信 CDI 实际上正在破坏@Dependentbean 实例,因此我PreDestroy在Book. 正如预期的那样,该方法从未被调用,但我开始获得 OOME,即使是对于@ApplicationScopedCDI bean!
为什么添加一个@PostConstruct方法会使应用程序的行为有所不同?我认为正确的问题是逆,即为什么是去除的的@PostConstruct的OOME消失在做什么?由于 CDI 必须@Dependent使用它们的父对象销毁对象 - 在这种情况下Instance<Book>,它必须@Dependent在Instance. 调试,你会看到它。这个列表是保存对所有创建@Dependent对象的引用并最终导致内存泄漏的列表。显然(没有时间找到证据)Weld 正在应用优化:如果@Dependent对象@PostConstruct在其依赖注入树中没有方法,Weld 不会将其添加到此列表中。这是(我的猜测)为什么(1)工作时,GlobalService是@ApplicationScoped.
在将 EJB 注入 CDI bean 时,CDI 必须将其自己的生命周期与 EJB 生命周期绑定。显然(再次,我的猜测)CDI 正在创建一个@PostConstruct钩子,当GlobalServiceEJB 绑定两个生命周期时。根据 JSR 365 (CDI 2.0) ch 18.2:
无状态会话 bean 必须属于
@Dependent伪作用域。
因此,在其对象链中Book获取了一个@PostConstruct钩子@Dependent:
Book [@Dependent, no @PostConstruct] -> GlobalService [@Dependent, @PostConstruct]
Run Code Online (Sandbox Code Playgroud)
因此Instance<Book>需要Book对其创建的每一个的引用,以便调用@PostConstruct依赖GlobalServiceEJB的方法(由 CDI 隐式创建)。
解决了(1)(希望)的谜团后,让我们继续(2):
createBook2(): 缺点是用户必须知道目标 bean 是@Dependent. 如果有人改变了范围,那么销毁它是不合适的(除非你真的知道你在做什么)。然后保留对死实例的引用似乎令人毛骨悚然:)createBook3():一个缺点是GlobalFactory必须知道Book. 或许这还不算太糟糕,书籍工厂知道它们的依赖关系是合理的。但是,你不会得到像@PostConstruct/那样的 CDI 好东西@PreDestroy,一本书的拦截器(例如,事务在 CDI 中被实现为拦截器)。另一个缺点是普通对象引用了 CDI bean。如果这些属于一个狭窄的范围(例如@RequestScoped),您可能会在它们的正常生命周期之外保留对它们的引用,从而产生不可预测的结果。现在对于(3)以及最佳解决方案是什么,我认为这在很大程度上取决于您的确切用例。例如,如果您想在每个Book. 或者,如果 book 是一个只需要设置其 id 的 POJO,您只需继续使用createBook3().
| 归档时间: |
|
| 查看次数: |
451 次 |
| 最近记录: |