* 编辑:我自己找到了内存泄漏的答案,并将其与其他人一起发布。如果有人能够回答为什么我必须完全应用修复(请参阅我的回答中的问题),我很乐意给出答案:-) *
我编写了一个简单的util,它定期读取日志文件并将条目保存到数据库中。代码不多,看似很好。但是今天在一大堆原木上运行之后,我发现它还没有准备好生产......在第一份工作完成所有原木的咀嚼之后,接下来的15个工作岗位将会启动,直到不再建立连接为止并且下一个作业将因SqlException而失败。在接下来的五个小时里,该应用程序在内存不足之前就会发现异常情况。
使用以下cron触发器“* * / 2 * * *?”设置作业,这意味着“每两分钟运行一次”。奇怪的是,在第一份工作完成后(花了大约一个小时),剩下的工作将一个接一个地迅速开始。这是出乎意料的,因为我认为每个工作之间必须有两分钟。
现在我想知道可能出现什么问题。我猜测原因在于我如何使用Hibernate和/或Quartz。也许我没有按照自己的意愿发布会话,或者误解了Quartz如何安排工作?或者我应该将Hibernate工厂注入工作中,而不是在每个工作中创建它? getCurrentSession()vs openSession()?不知道。我仍然不明白为什么这会产生任何影响,因为作业应该在退出时释放所有资源。无论如何,大多数相关代码将在下面看到。
无论我是否将连接问题排序,代码都会遭受某种内存泄漏。就好像每个执行的作业都以某种方式保存在堆上。执行
java -Xmx17M -DcronTriggerExpression="*/1 * * * * ?" -jar myjar.jar
,
我可以通过以最少的内存运行程序来强制显示问题。在为hibernate构建会话工厂时,在得到OutOfMemoryError之前,在这种情况下只需要连续运行四次。
public class ExportJob implements Job {
LogImporter importer;
public ExportJob() { //needed for Quartz
setUpHibernate();
importer = new LogImporter(); //injected, but this saves space on SO :)
}
public void execute(JobExecutionContext context) throws JobExecutionException {
/* even if this body is empty, I get a OutOfMemoryError */
}
private SessionFactory setUpHibernate() {
logger.debug("Setting up hibernate");
this.sessionFactory = new Configuration().configure().buildSessionFactory();
return this.sessionFactory;
}
private Session getSession() {
return sessionFactory.openSession();
}
}
答案 0 :(得分:2)
至少快速射击是由Quartz引起的。它每2分钟排队一次,所以一小时后你应该有59个工作就绪了。
你是对的,关闭SessionFactory,而不是会话修复问题。这也解释了运行我们的Connections,只是为了关闭循环 - 并希望获得奖励;-)。虽然Hibernate允许多个SessionFactory,但每个SessionFactory都会维护一个Connection。
从他们的文件:
当所有映射都被解析时 org.hibernate.cfg.Configuration,应用程序必须获得一个工厂 对于org.hibernate.Session实例。这家工厂的目的是 由所有应用程序线程共享。 Hibernate确实允许你的 应用程序实例化多个org.hibernate.SessionFactory。 如果您使用多个数据库,这将非常有用。
所以最后的修复应该是在别处创建一个SessionFactory并重用它来在每个线程中生成会话,或者在这种情况下创建作业。
答案 1 :(得分:1)
不确定这是否有帮助,但几乎听起来您希望每2分钟按顺序运行一次。如果是这种情况,您可能希望实现StatefulJob而不是Job。这样,您可以确保正在运行的并发作业不会占用所有数据库连接。
答案 2 :(得分:1)
从您的第一个版本的帖子中,我注意到以下代码:
for (LogEntry logEntry : newEntries) {
session.save(logEntry);
...................
}
每次保存新的logEntry时,它都将保留在hibernate Session中。每个持久对象都放在第一级缓存(您的JVM内存)中,而不是CG。因此,如果在事务中保存了许多logEntry,则可能会遇到内存耗尽。在保存一定数量的对象后,应该刷新并清除hibernate会话以释放一些内存。请参阅this以获取使用hibernate批量插入的最佳实践
顺便说一下,你将最大堆大小设置为17MB,这是如此之小,以至于在真正有问题的代码运行之前你的应用程序很容易耗尽内存。因此,OutOfMemoryError
可能是由于内部的代码-loop。
我建议您在发生OutOfMemoryError
时进行堆转储。然后检查堆转储以查看哪些实例被怀疑导致内存泄漏。您可以参考this了解如何使用 VisualVM 来收集和分析堆转储。
答案 3 :(得分:1)
事实证明我必须在使用后关闭SessionFactory。如果我没有,会话工厂会因某种原因保留在内存中。因此,只需在sessionFactory.close()
末尾添加execute()
即可。不再是OutOfMemoryError。
现在为什么我必须这样做是超出我的:我认为通常的Java规则仍然适用。当作业不再运行时(Quartz可能将其置零),所有依赖对象都被垃圾收集(包括SessionFactory)。那应该已经释放了它的资源。还是这样我想?