openjpa_OpenJPA:内存泄漏案例研究

openjpa

本文将提供完整的根本原因分析详细信息以及解决影响Oracle Weblogic Server 10.0生产环境的Java堆内存泄漏(Apache OpenJPA泄漏)的方法。 这篇文章还将演示在管理javax.persistence.EntityManagerFactory生命周期时遵循Java Persistence API最佳实践的重要性。

环境规格

  • Java EE服务器 :Oracle Weblogic Portal 10.0
  • 操作系统 :Solaris 10
  • JDK :Oracle / Sun HotSpot JVM 1.5 32位@ 2 GB容量
  • Java Persistence API :Apache OpenJPA 1.0.x(JPA 1.0规范)
  • RDBMS :Oracle 10g
  • 平台类型 :Web门户

故障排除工具

问题描述与观察

该问题最初是由Weblogic生产支持团队在生产中断后报告的。 最初的根本原因分析练习确实揭示了以下事实和观察结果:

  • 约2周的流量后,定期观察到生产中断。
  • 失败是由于Java堆(OldGen)耗尽所致,例如OutOfMemoryError:在Weblogic日志中发现Java堆空间错误。
  • 在一段时间后从Foglight监视工具检查了Java堆OldGen的空间利用率以及Java冗长的GC历史数据后,确认了Java堆内存泄漏。

发现上述问题后,决定移至RCA的下一个阶段,并对受影响的Weblogic(JVM)实例执行JVM 堆转储分析

JVM堆转储分析

**现在可以在此处获得解释以下JVM堆转储分析的视频。 为了产生一个
JVM堆转储受支持的团队确实使用了HotSpot 1.5 jmap实用程序,该实用程序生成了大约1.5 GB的堆转储文件(heap.bin)。 然后使用Eclipse Memory Analyzer Tool分析了堆转储文件。 现在,让我们回顾一下堆转储分析,以便我们了解OldGen内存泄漏的根源。

MAT提供了一个初步的泄漏可疑报告,这对于突出您的高内存贡献者非常有用。 对于我们的问题案例,MAT能够识别出可疑泄漏占近600 MB或占OldGen空间总容量的40%。

此时,我们找到了一个java.util.LinkedList实例,该实例使用了将近600 MB的内存,并已加载到我们的应用程序父类加载器之一(@ 0x7e12b708)。 下一步是了解泄漏的对象以及保留的来源。 MAT允许您检查应用程序的任何类加载器实例,并提供检查加载的类和实例的功能。 只需提供地址(例如0x7e12b708)来搜索所需的对象,然后通过选择带有外向引用的“列表对象”>检查加载的类和实例。

从上面的快照中您可以看到,该分析非常有启发性。 我们发现内存保留源中有一个org.apache.openjpa.enhance.PCRegistry实例; 罪魁祸首是实现为LinkedList的_listeners字段。 供您参考,内部使用Apache OpenJPA PCRegistry来跟踪已注册的具有持久性的类。 在Apache OpenJPA 1.0.4版中公开_listeners字段的PCRegistry源代码的片段下面。

/**
 * Tracks registered persistence-capable classes.
 *
 * @since 0.4.0
 * @author Abe White
 */
public class PCRegistry {
    // DO NOT ADD ADDITIONAL DEPENDENCIES TO THIS CLASS

    private static final Localizer _loc = Localizer.forPackage
        (PCRegistry.class);

    // map of pc classes to meta structs; weak so the VM can GC classes
    private static final Map _metas = new ConcurrentReferenceHashMap
        (ReferenceMap.WEAK, ReferenceMap.HARD);

    // register class listeners
    private static final Collection _listeners = new LinkedList();

现在的问题是,为什么这种内部数据结构的内存占用空间如此之大,并且随着时间的推移可能会泄漏? 下一步是深入研究_listeners LinkedLink实例,以检查泄漏的对象。

我们最终发现,泄漏的对象实际上是我们的应用程序用来对Oracle数据库执行各种查询的JDBC和SQL映射定义(元数据)。 对JPA规范,OpenJPA文档和源代码的回顾确实确认了根本原因与javax.persistence.EntityManagerFactory的错误用法有关,例如缺少关闭新创建的EntityManagerFactory实例。

如果仔细查看上面的代码快照,您将意识到close()方法确实负责清理最近使用的元数据存储库实例。 这也确实引起了另一个问题,为什么我们要一遍又一遍地创建这样的Factory实例……调查的下一步是对我们的应用程序代码执行代码遍历,尤其是围绕JPA EntityManagerFactory和EntityManager对象的生命周期管理。

根本原因和解决方案

应用程序代码的代码演练确实表明,该应用程序在每个单个请求上都创建了EntityManagerFactory的新实例,并且没有正确关闭它。

public class Application {
      
       @Resource
       private UserTransaction utx = null;
       
       // Initialized on each application request and not closed!
       @PersistenceUnit(unitName = "UnitName")
       private EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceUnit"); 

       public EntityManager getEntityManager() {
             return this.emf.createEntityManager();
       }
      
       public void businessMethod() {
            
             // Create a new EntityManager instance via from the newly created EntityManagerFactory instance
             // Do something...
             // Close the EntityManager instance
       }
}

JPA EntityManagerFactory的此代码缺陷和改进程序的使用导致在早期JVM堆转储分析中演示的OpenJPA _listeners数据结构内泄漏或积累了元数据存储库实例。 该问题的解决方案是通过Singleton模式集中管理线程安全javax.persistence.EntityManagerFactory的管理和生命周期。 最终解决方案的实现如下:

  • 每个应用程序类加载器仅创建和维护一个javax.persistence.EntityManagerFactory静态实例,并通过Singleton Pattern实现。
  • 为每个应用程序请求创建并处置EntityManager的新实例。

请阅读Stackoverflow上的 讨论 ,因为我们实现的解决方案非常相似。 在针对我们的生产环境实施解决方案之后,不再观察到Java堆OldGen内存泄漏。

参考: OpenJPA:我们的JCG合作伙伴 Pierre-Hugues Charbonneau在Java EE支持模式和Java教程博客上的内存泄漏案例研究

翻译自: https://www.javacodegeeks.com/2013/03/openjpa-memory-leak-case-study.html

openjpa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值