Tomcat内存管理

7
我正在运行Tomcat7,这台服务器非常强大,有8GB的RAM和8个核心。
我的问题是,RES内存越来越高,直到服务器不再响应,甚至不调用OnOutOfMemoryError。
Tomcat配置:
-Xms1024M
-Xmx2048M
-XX:PermSize=256m
-XX:MaxPermSize=512m
-XX:+UseConcMarkSweepGC
-XX:OnOutOfMemoryError='/var/tomcat/conf/restart_tomcat.sh'

内存信息:

Memory:     Non heap memory = 106 Mb (Perm Gen, Code Cache),
Loaded classes = 14,055,
Garbage collection time = 47,608 ms,
Process cpu time = 4,296,860 ms,
Committed virtual memory = 6,910 Mb,
Free physical memory = 4,906 Mb,
Total physical memory = 8,192 Mb,
Free swap space = 26,079 Mb,
Total swap space = 26,079 Mb
Perm Gen memory:    88 Mb / 512 Mb    ++++++++++++
Free disk space:    89,341 Mb 

与 top 命令相比,Tomcat 使用的内存并不高。

app memory graph

当尝试连接 SMTP 服务器或 Facebook 服务器时,我还遇到了 java.net.SocketException: No buffer space available 问题。

我使用 Hibernate,带有以下 c3p0 连接池配置:

        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://urldb/schema?autoReconnect=true</property>
        <property name="hibernate.connection.username">username</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
        <property name="hibernate.connection.password"></property>
        <property name="connection.characterEncoding">UTF-8</property>

        <property name="hibernate.c3p0.acquire_increment">1</property>
        <property name="hibernate.c3p0.idle_test_period">300</property>
        <property name="hibernate.c3p0.timeout">5000</property>
        <property name="hibernate.c3p0.max_size">50</property>
        <property name="hibernate.c3p0.min_size">1</property>
        <property name="hibernate.c3p0.max_statement">0</property>
        <property name="hibernate.c3p0.preferredTestQuery">select 1;</property>
        <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>

我找不到任何东西... 有人知道我应该去哪里找吗?

谢谢!

[更新1]堆转储:

HEAP HISTOGRAM :

class [C                                    269780  34210054
class [B                                    5600    33836661
class java.util.HashMap$Entry               221872  6212416
class [Ljava.util.HashMap$Entry;            23797   6032056
class java.lang.String                      271170  5423400
class org.hibernate.hql.ast.tree.Node       103588  4972224
class net.bull.javamelody.CounterRequest    28809   2996136
class org.hibernate.hql.ast.tree.IdentNode  23461   2205334
class java.lang.Class                       14677   2113488
class org.hibernate.hql.ast.tree.DotNode    13045   1852390
class [Ljava.lang.String;                   48506   1335600
class [Ljava.lang.Object;                   12997   1317016 


Instance Counts for All Classes (excluding platform) :

103588 instances of class org.hibernate.hql.ast.tree.Node
33366 instances of class antlr.ANTLRHashString
28809 instances of class net.bull.javamelody.CounterRequest
24436 instances of class org.apache.tomcat.util.buf.ByteChunk
23461 instances of class org.hibernate.hql.ast.tree.IdentNode
22781 instances of class org.apache.tomcat.util.buf.CharChunk
22331 instances of class org.apache.tomcat.util.buf.MessageBytes
13045 instances of class org.hibernate.hql.ast.tree.DotNode
10024 instances of class net.bull.javamelody.JRobin
9084 instances of class org.apache.catalina.loader.ResourceEntry
7931 instances of class org.hibernate.hql.ast.tree.SqlNode 

[UPDATE 2] server.xml :

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="UTF-8"
               maxThreads="150"
               minSpareThreads="25"
               maxSpareThreads="75"
               enableLookups="false"
               acceptCount="1024"
               server="unknown"
               address="public_ip"
    />

****【更新3】日志文件输出:****

    2012-06-04 06:18:24,152 [http-bio-ip-8080-exec-3500] ERROR org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/api].[Jersey REST Service]- Servlet.ser
vice() for servlet [Jersey REST Service] in context with path [/socialapi] threw exception
java.net.SocketTimeoutException: Read timed out

    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:129)
    at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:532)
    at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:501)
    at org.apache.coyote.http11.InternalInputBuffer$InputStreamInputBuffer.doRead(InternalInputBuffer.java:563)
    at org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityInputFilter.java:118)
    at org.apache.coyote.http11.AbstractInputBuffer.doRead(AbstractInputBuffer.java:326)
    at org.apache.coyote.Request.doRead(Request.java:422)

[更新4] ServletContext

我在我的应用程序中使用ServletContextListener来创建控制器并与event.getServletContext().setAttribute保持引用。这些控制器加载配置和翻译(Perm中的88Mb)。

然后为了使用数据库,我使用:

SessionFactory sf = dbManager.getSessionFactory(DatabaseManager.DB_KEY_DEFAULT);
Session session = sf.openSession();
Transaction tx = null; 

try {
    tx = session.beginTransaction();

    //Do stuuf

    tx.commit();

} catch (Exception e){
    //Do something
} finally {
    session.close();
}
  1. 这可能是泄漏的源头吗?
  2. 为什么不使用手动事务/会话,你会怎样做?

当服务器停止响应时,您还应该获取线程转储(请参阅http://wiki.apache.org/tomcat/HowTo#How_do_I_obtain_a_thread_dump_of_my_running_webapp_.3F)以查看JVM实际正在执行的操作。您能否还发布来自server.xml的<Connector>配置,并告诉我们有关在您的DBCP中启用的任何“废弃连接”功能,并告诉我们您的JVM进程的文件句柄限制是多少?日志中是否有任何内容--特别是logs/catalina.out(或stdout去哪里)? - Christopher Schultz
更新2:server.xml。好的,谢谢,下次我会尝试使用jstack来解决阻塞问题!在我的mysql进程列表中,我可以看到一些等待连接,但没有什么奇怪的。我已经在更新3中记录了日志。 - Camille R
但是你的HQL查询已经在某个地方作为字符串创建了吗? - ssedano
你找到漏洞在哪里了吗? - Avihai Marchiano
嗨!是的,那肯定是由于数据库连接和使用问题,所以我不得不重构我正在执行/结束事务的方式,然后它就正常工作了。 - Camille R
显示剩余4条评论
2个回答

14

请尝试使用此参数:

+XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath=dump.log

也可以尝试使用更低的初始内存参数-Xms

然后您可以检查转储文件以查看是否存在对象分配问题。

在运行时尝试:

jps

这将输出所有Java进程,假设TomcatPID 4444:

jmap -dump:format=b,file=heapdump 4444

同时

jhat heapdump

如果您在执行jhat时内存不足,只需添加更多内存即可。从那里,您可以检查应用程序的堆。

另一种方法是启用Hibernate统计信息以检查您是否正在检索更多对象。尽管每个小时看起来像完整的垃圾回收,但这不应该成为问题(还有改进的空间)。

-verbose:gc -Xloggc:/opt/tomcat/logs/gc.out -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

例如使用GCViewer,可以查看内存的每个空间(年老代、伊甸园区、幸存者区和永久代)。

另一个方便的工具:

jstack 4444 > stack.txt

这将检索具有pid 4444的Java进程内运行的每个线程的完整堆栈跟踪。

请注意,如果您以root或其他用户身份启动了Tomcat,则需要特权。

jps

没有权限的进程不会输出,因此您无法连接到它。

由于我不知道您的应用程序是关于什么的(因此我不知道它的要求),300万个实例看起来很多。

使用 Hibernate 统计信息,您可以查看实例化最多的类。

然后调整您的 eden 和 ternured 垃圾回收比例可以更有效率。

新实例化的对象放在 eden 中。当它填满时触发小 GC。未删除的内容将转移到 survivor 空间。当此空间填满时,它将转移到 ternured。当 ternured 满时,将出现 full gc。

在这张图片中(不准确),我忽略了变成 interned 的 String 和不在堆中的内存映射文件。看看您实例化最多的类。过度使用 String 可能会导致 perm 快速填满。

我猜您已经这样做了,但请使用托管的会话工厂,例如 Spring(如果在您的堆栈中),避免手动管理事务和会话。

请记住,在 GC 中,只有当没有对象引用它时,对象才会被删除。因此,只要一个对象在您的应用程序中是可访问的,该对象就会保留。

如果您的 ServletContextListener 实例化控制器并将它们存储在事件 getServletContext 中,请确保之后完全删除引用。如果保留引用,则对象不会被删除,因为它们仍然可以访问。

如果您管理自己的事务和会话(如果无法使用框架,则很好),则必须处理代码维护和错误,而 Spring-tx 等已经解决和改进了这些问题。

我个人会利用 FOSS。但当然有时您不能扩大堆栈。

如果您正在使用 Hibernate,我建议查看 Spring-ormSpring-tx 来管理事务和会话。还要查看 Hibernate 模式 Open Session In View


谢谢,我会添加并检查堆转储(我已经从JavaMelody中检查过了,看起来很好,并且与图表相对应:Heap Classes: 4,367,Instances: 3,376,160,Kilo-Bytes: 372,020)。 - Camille R
谢谢,由于我的服务器现在正常运行,jstack提供的信息有些“无用”,但如果再次出现冻结情况,我会使用脚本执行该操作。 - Camille R
但是,如果Hibernate存在对象泄漏,它不会停留在应用程序的“已使用内存”中(例如:org.hibernate.hql.ast.tree.Node 240,128 Instances 11MB)吗? - Camille R
我指的不是“休眠模式”,而是你的应用程序检索或创建不必要的对象(考虑集合惰性)。 - ssedano
你认为那些不必要的对象会泄漏吗?我在每个函数后关闭会话,理论上不应该出现这种情况吧? - Camille R
我将问题标记为已回答,因为解决它需要相当长的时间。感谢您提供的提示和建议! - Camille R

2

我建议您下载Visual VM 1.3.3,安装所有插件,并将其附加到Tomcat PID上,以便您可以实时查看发生的情况。为什么要等待线程转储呢?它还会告诉您CPU、线程、所有堆代、哪些对象消耗了最多的内存等信息。


为此,他需要添加启动参数。同时可以按需进行jmap转储。jvisualvm几乎同样好用,而且无需下载。-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9004 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false - ssedano
我已经尝试了YourKit(看起来类似于Visual VM),不幸的是它没能帮助我解决问题(我只能在开发环境中使用它),是否有办法将其插入生产服务器? - Camille R
谢谢 ssedano,但这意味着你将一个 JMX 端口公开化,这是我无法想象的。 - Camille R
你可以随时在该端口上丢弃出站流量(如果你是在本地工作)。 - ssedano
不幸的是,我的问题只发生在专用服务器上的生产环境中,而且只有在真实流量下才会出现。我的本地测试服务器并没有复制这个问题。 - Camille R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接