5

我在 Tomcat 服务器上部署了一个简单的 webapp。webapp 是一个 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"
4

2 回答 2

3

我将我的 jvm 从 1.6 update 21 更新到 1.6 update 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 在不到 30 秒的时间内填充所有内存堆。我发现了创建大量 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();
于 2013-01-11T10:17:03.920 回答
0

直方图是一个好的开始,现在您知道要寻找什么了。

接下来,收集堆转储并查看SqlFragment实例的来源。分析Eclipse MAT- 这可能会向您显示代码作为“问题嫌疑人”,您可以从那里开始 - 分析这些类的输入/输出引用。还要检查终结器。

此外,jProfiler 快照可以告诉您谁在分配SqlFragment- 这将向您显示谁负责该分配 - 无论是您的代码还是休眠。

此外,我还建议在 OOM 异常之前收集线程转储,以便您了解系统中发生的情况。

于 2013-01-10T20:56:28.500 回答