*EDIT: I found the answer to the memory leak myself, and posted it along with the others. If someone can answer why I have to do apply the fix at all (see question in my answer), I will gladly award the answer 🙂 *
I wrote a simple util that periodically reads log files and saves the entries to a database. Not much code and seemingly works fine. But after running it on a big lump of logs today I found that it was not production ready … After the first job was finished chewing through all of the logs, the next 15 jobs would fire off, until no more connections could be established and the next job would fail with a SqlException. For the next five hours the app spewed out exceptions unnoticed until running out of memory.
The job was set up using the following cron trigger, “* */2 * * * ?”, which means “run every two minutes”. The weird thing is that after the first job was finished (which took approx. an hour) the remaining jobs would fire rapidly, one after another. This was unexpected, as I would think there had to be two minutes between each job.
Now I wonder what could be wrong. I am guessing the reason is either in how I am using Hibernate and/or Quartz. Maybe I am not releasing the session as I should, or have misunderstood how Quartz schedules jobs?Or maybe I should inject the Hibernate factory into to the job, and not create it in each job? getCurrentSession() vs openSession()? No idea. I still do not understand why this should have any effect, as the jobs should release all resources on exit. Anyway, most of the relevant code is to be seen below.
No matter if I get the connection problem sorted, the code is suffering from some sort of memory leak. It is as if every job executed is somehow kept on the heap. By executing
java -Xmx17M -DcronTriggerExpression="*/1 * * * * ?" -jar myjar.jar,
I can force the problem to appear by running the program with a minimal amount of memory. It just takes four consecutive runs in this case before I get a OutOfMemoryError when building the session factory for hibernate.
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();
}
}
At least the rapid firing is caused by Quartz. It’s queuing up jobs every 2 minutes, so after one hour you should have 59 jobs itching to start.
You are right, closing the SessionFactory, not the session fixes the problem. That also explains running our of Connections, just to close the loop – and hopefully get an award ;-). Although Hibernate allows multiple SessionFactory, each one will maintain a Connection.
From their documentation:
So the final fix should be to create one SessionFactory elsewhere and reuse it to generate sessions in each thread, or job in this case.