我正在尝试将 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 中方法的执行时,我发现内存使用量不断增长,直到所有堆都被消耗,尽管发生了很多垃圾收集,但没有内存被释放,这让我相信我在我的程序中引入了内存泄漏。代码。然而,我无法发现有问题的代码,所以我希望您能帮助找到它。
问题肯定不在于索引本身,因为即使没有索引我也会发生泄漏,所以我认为我没有正确执行分页。然而,对我拥有的实体的唯一引用是列表entities,在调用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 查询本身有问题,保留了不应该保留的引用,但谁知道呢。
最佳答案
看起来注入(inject)的 EntityManager 保留了对从查询返回的所有实体的引用。它是一个容器管理的 EM,因此应该在事务结束时自动关闭或清除 - 但您正在执行一堆非事务性查询。
如果您只想对实体建立索引,您可能需要在 init() 中的循环末尾调用 em.clear()。这些实体将被分离(EntityManager 跟踪对它们所做的更改),但如果它们只是要被 GC 处理,那应该不是问题。
关于java - JBoss AS 5.1 下分页 JPA 查询的内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3253627/