JBoss AS 5.1下的分页JPA查询导致内存泄漏

时间:2010-07-15 08:09:17

标签: java hibernate jpa jboss5.x hibernate-search

我正在尝试将Hibernate Search集成到我目前正在开发的项目中。这种努力的第一步非常简单 - 使用Hibernate Search(使用Lucene)对所有现有实体进行索引。映射到域模型中的实体的许多表包含大量记录(> 1百万),并且我使用简单的分页技术将它们分成更小的单元。但是,在索引实体时,我遇到了一些内存泄漏。这是我的代码:

@Service(objectName = "LISA-Admin:service=HibernateSearch")
@Depends({"LISA-automaticStarters:service=CronJobs", "LISA-automaticStarters:service=InstallEntityManagerToPersistenceMBean"})
public class HibernateSearchMBeanImpl implements HibernateSearchMBean {
    private static final int PAGE_SIZE = 1000;

    private static final Logger LOGGER = LoggerFactory.getLogger(HibernateSearchMBeanImpl.class);

    @PersistenceContext(unitName = "Core")
    private EntityManager em;

    @Override
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void init() {
        FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

        Session s = (Session) em.getDelegate();
        SessionFactory sf = s.getSessionFactory();
        Map<String, EntityPersister> classMetadata = sf.getAllClassMetadata();

        for (String key : classMetadata.keySet()) {
            LOGGER.info("Class: " + key + "\nEntity name: " + classMetadata.get(key).getEntityName());

            Class entityClass = classMetadata.get(key).getMappedClass(EntityMode.POJO);
            LOGGER.info("Class: " + entityClass.getCanonicalName());

            if (entityClass != null && entityClass.getAnnotation(Indexed.class) != null) {
                index(fullTextEntityManager, entityClass, classMetadata.get(key).getEntityName());
            }
        }
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void index(FullTextEntityManager pFullTextEntityManager, Class entityClass, String entityName) {
        LOGGER.info("Class " + entityClass.getCanonicalName() + " is indexed by hibernate search");

        int currentResult = 0;

        Query tQuery = em.createQuery("select c from " + entityName + " as c order by oid asc");
        tQuery.setFirstResult(currentResult);
        tQuery.setMaxResults(PAGE_SIZE);

        List entities;

        do {
            entities = tQuery.getResultList();
            indexUnit(pFullTextEntityManager, entities);

            currentResult += PAGE_SIZE;
            tQuery.setFirstResult(currentResult);
        } while (entities.size() == PAGE_SIZE);

        LOGGER.info("Finished indexing for " + entityClass.getCanonicalName() + ", current result is " + currentResult);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void indexUnit(FullTextEntityManager pFullTextEntityManager, List entities) {
        for (Object object : entities) {
            pFullTextEntityManager.index(object);
            LOGGER.info("Indexed object with id " + ((BusinessObject)object).getOid());
        }
    }
}

它只是一个简单的MBean,我的 init 方法是我通过JBoss的JMX控制台手动执行的。当我在JVisualVM中监视方法的执行时,我看到内存使用量不断增长,直到消耗掉所有堆,尽管发生了大量垃圾收集但没有释放内存导致我相信我在内存中引入了内存泄漏码。但是我无法发现有问题的代码,所以我希望你能帮助找到它。

问题肯定不在索引本身,因为即使没有它我也会得到泄漏,所以我认为我没有正确地进行分页。但是,对我所拥有的实体的唯一引用是列表实体,在每次循环调用 indexUnit 之后,应该很容易进行垃圾回收。

提前感谢您的帮助。

修改

将代码更改为

    List entities;

    do {
        Query tQuery = em.createQuery("select c from " + entityName + " as c order by oid asc");
        tQuery.setFirstResult(currentResult);
        tQuery.setMaxResults(PAGE_SIZE);

        entities = tQuery.getResultList();
        indexUnit(pFullTextEntityManager, entities);

        currentResult += PAGE_SIZE;
        tQuery.setFirstResult(currentResult);
    } while (entities.size() == PAGE_SIZE);

缓解了这个问题。泄漏仍在那里,但没有那么糟糕。我猜JPA查询本身有一些问题,保留参考不应该,但谁知道。

3 个答案:

答案 0 :(得分:0)

看起来注入的EntityManager持有对查询返回的所有实体的引用。它是一个容器管理的EM,因此它应该在事务结束时自动关闭或清除 - 但是你正在做一堆非事务性查询。

如果您只是要为实体编制索引,则可能需要在init()循环结束时调用em.clear()。实体将被分离(EntityManager跟踪对它们所做的更改),但是如果它们只是GC,那应该不是问题。

答案 1 :(得分:0)

似乎这个问题不会找到真正的解决方案。最后,我刚刚将索引代码移到一个单独的应用程序中 - 泄漏仍然存在,但这并不重要,因为应用程序在关键容器之外运行完成(有一个巨大的堆)

答案 2 :(得分:0)

我认为没有“泄密”;但是,我确实认为你在持久化上下文中积累了大量实体(是的,你是,因为你正在加载它们),并且最终会占用所有内存。每次循环后你需要clear EM(没有clear,分页没有帮助)。像这样:

    do {
        entities = tQuery.getResultList();
        indexUnit(pFullTextEntityManager, entities);

        pFullTextEntityManager.clear(); 

        currentResult += PAGE_SIZE;
        tQuery.setFirstResult(currentResult);
    } while (entities.size() == PAGE_SIZE);