我在Tomcat服务器上部署了一个简单的webapp。该Web应用程序是一种REST Web服务,每个Web资源均从MySQL数据库加载数据并返回XML或JSON文档。我使用以下框架堆栈:Jersey(1.14)+ Spring(3.1)+ Hibernate(4.1)+ EHCache(2.5.1)。
我使用jMeter测试了Webapp:我启动了5个线程来请求Web资源。几分钟后,堆开始缓慢填充,达到99%,最后返回OOM异常。我不知道这是否是内存泄漏,但是当我在内存堆中看到大量的org.hibernate.hql.internal.ast.tree.SqlFragment
对象时!
/usr/java/jdk/bin/jmap -histo:live 17047 > /tmp/histo.txt
num #instances #bytes class name
----------------------------------------------
1: 720143 69133728 org.hibernate.hql.internal.ast.tree.SqlFragment
2: 510537 63559320 [C
3: 360221 34581216 org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode
4: 704652 33823296 java.util.HashMap$Entry
5: 360223 31699624 org.hibernate.hql.internal.ast.tree.SqlNode
6: 697354 27894160 java.lang.String
7: 370975 26710200 org.hibernate.hql.internal.ast.tree.Node
8: 171241 25623320 <constMethodKlass>
9: 208125 24948176 [Ljava.lang.Object;
10: 171241 20568632 <methodKlass>
11: 16012 17827384 <constantPoolKlass>
12: 383070 16623136 [I
13: 34829 15170176 [Ljava.util.HashMap$Entry;
14: 226869 12885896 <symbolKlass>
15: 16012 12590168 <instanceKlassKlass>
这是我的jvm选项:
JAVA_OPTS="-Xms1g -Xmx1g -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=30"
最佳答案
我将我的jvm从1.6更新21更新到1.6更新38,该漏洞似乎已修复。
现在,在压力期间,我只有17个SQLFragment实例。
/usr/java/jdk/bin/jmap -histo:live 5612 | grep org.hibernate.hql.internal.ast.tree.SqlFragment
912: 17 952 org.hibernate.hql.internal.ast.tree.SqlFragment
/usr/java/jdk/bin/jmap -histo:live 5612 | grep org.hibernate.hql.internal.ast.tree.SqlFragment
910: 17 952 org.hibernate.hql.internal.ast.tree.SqlFragment
/usr/java/jdk/bin/jmap -histo:live 5612 | grep org.hibernate.hql.internal.ast.tree.SqlFragment
980: 17 952 org.hibernate.hql.internal.ast.tree.SqlFragment
注意:JDK更新不能解决问题!
我使用jmeter测试了我的所有REST资源,以识别出哪个资源泄漏。
我发现了1个资源,现在我可以用jmeter在不到30s的时间内填充所有内存堆。
我发现了创建大量SqlFragment的HQL查询:
String q = "select p from PhysicalItem p where p.product.id=:itemid and p.fileType in (:filetypes) order by p.id desc";
return sessionFactory.getCurrentSession().createQuery(q).setParameter("itemid", logicalItemID).setParameterList("filetypes", fileTypes).list();
这是刚启动Tomcat之后SqlFragment的数量。
jmap -histo:live 27472 | grep -i SqlFragment
608: 15 840 org.hibernate.hql.internal.ast.tree.SqlFragment
和1个HTTP请求后的SqlFragment数量+一个完整的垃圾
jmap -histo:live 27472 | grep -i SqlFragment
503: 37 2072 org.hibernate.hql.internal.ast.tree.SqlFragment
为了快速解决此问题,我将HQL请求重写为SQL:
String sql = "select p.* from physical_item p where p.id_logical = :itemid and p.file_type in (:filetypes) order by p.id desc";
List<String> names = new ArrayList<String>();
for (FileType fileType : fileTypes) {
names.add(fileType.getName());
}
return sessionFactory.getCurrentSession()
.createSQLQuery(sql)
.addEntity(PhysicalItem.class)
.setParameter("itemid", logicalItemID)
.setParameterList("filetypes", names)
.list();
关于java - Hibernate SqlFragment内存泄漏?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14238855/