1

我正在尝试将 Hibernate Search 集成到我目前正在从事的项目之一中。这种努力的第一步相当简单 - 使用 Hibernate Search(它在后台使用 Lucene)索引所有现有实体。映射到域模型中实体的许多表包含大量记录(> 100 万条),我使用简单的分页技术将它们拆分为更小的单元。但是,我在索引实体时遇到了一些内存泄漏。这是我的代码:

@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,我通过 JBoss 的 JMX 控制台手动执行它的init方法。当我在 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 查询本身有问题,保留不应该的引用,但谁知道呢。

4

3 回答 3

0

我认为没有“泄漏”;但是,我确实认为您将大量实体累积到持久性上下文中(是的,您是,因为您正在加载它们)并最终吃掉所有内存。您需要clear在每个循环之后使用 EM(没有clear,分页没有帮助)。像这样的东西:

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

        pFullTextEntityManager.clear(); 

        currentResult += PAGE_SIZE;
        tQuery.setFirstResult(currentResult);
    } while (entities.size() == PAGE_SIZE);
于 2010-07-15T19:32:09.887 回答
0

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

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

于 2010-07-15T14:16:48.123 回答
0

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

于 2010-07-15T14:17:09.997 回答