我正在运行几个 tomcat 服务器,我们将程序员编写和实现的应用程序部署到这些服务器上。问题是,在我们的一些服务器中,尤其是那些有多个请求的服务器,内存很快就会被填满,服务器的性能开始变差,甚至根本无法运行。我是这个职位的新手,我的前任提到这是因为应用程序中的代码编写不正确,导致它占用了所有内存……这很公平,但我想知道我是否可以做些什么来缓解或消除这种情况,因为目前实施的解决方案是每周多次重新启动 tomcat,直到代码得到改进,在我看来,这感觉有点过头了!(没有双关语的意思)
下面是在需要终止并重新启动 tomcat 之前 htop 的输出(这是另一回事,大多数情况下不能要求 tomcat 礼貌地退出,您必须终止它 -9,不确定这是否正常)
我检查了一些资源但我无法找到任何可以解决我的问题的具体方法,因此任何好的专业知识都会受到欢迎!
我附上了一张图片,你可以看到这个过程似乎重复了多次,但它并没有像有些人说的那样使用超过 300GB 的内存,而只有 7GB,不完全确定这意味着什么。
实际上,这可能是 htop 的问题,因为如果你执行 ps,你只能看到以下进程:
root 5215 3.4 64.8 8310716 5301436 ? Sl Nov04 146:25 /usr/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.awt.headless=true -Xms5G -Xmx5G -XX:PermSize=512m -XX:MaxPermSize=512m -XX:NewSize=1G -XX:MaxNewSize=1G -Duser.langua
无论如何,回到我的观点,它太容易超载了,有什么方法可以在 Tomcat 版本 7.0.28 中防止这种情况发生?
这是 server.xml
<?xml version='1.0' encoding='utf-8'?>
<Server port="8105" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Environment name="databasePlaceHolder" type="java.lang.String" value="com_xxx_yyy_au"/>
<Environment name="com.xxx.databasename" type="java.lang.String" value="com_xxx_yyy_au"/>
<Environment name="com.xxx.JMS.url" type="java.lang.String" value="tcp://localhost:61616"/>
<Environment name="remoteServerURL" type="java.lang.String" value="https://yyy.xxx.com/"/>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8109" protocol="AJP/1.3" redirectPort="0443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
这是setenv.sh文件内容:
JAVA_OPTS="-Djava.awt.headless=true -Xms5G -Xmx5G -XX:PermSize=512m -XX:MaxPermSize=512m -XX:NewSize=1G -XX:MaxNewSize=1G -Duser.language=en -Duser.region=GB"
JAVA_OPTS="${JAVA_OPTS} -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution"
答案1
至于 htop 是否错误,我认为您已启用线程信息,因此您看到的每个 java 条目都是同一进程的一个线程。要验证这一点,请切换“显示线程”(按“H”切换)。
从 htop 屏幕截图中我注意到你的服务器有 8G 的 RAM,因此将 JVM 限制为 5G + PermGen + 一些开销,你应该可以假设没有其他占用大量内存的进程在运行。
接下来要检查的是垃圾收集器,根据您的 Java 版本,有一个标志(至少在 Oracle Java/OpenJDK 中)使 JVM 记录每个 GC 事件,通常是这样的:-verbose:gc -XX:+PrintGCDateStamps -Xloggc:SOMEFILENAME
并检查是否增加 GC 活动,如果发生内存泄漏,您会看到垃圾收集随着时间的推移变得越来越频繁,直到它使用所有 CPU 尝试释放内存但没有成功,并且您可以在日志中看到内存不足异常。此时,您必须将kill -9
应用程序作为您的问题。但现在您将有一个事后 GC 活动日志来证明是否存在内存泄漏。
接下来,如果您部署了多个应用程序,请尝试将每个应用程序拆分到单个 tomcat 实例中(如果可能)。或者在内存不足时启用堆转储。
至于在没有修复代码的可能性的情况下该怎么办,好吧,假设有内存泄漏,设置对 GC 频率的监控,例如,如果一分钟内有 3 次完整 GC 尝试,则自动重新启动 tomcat。
虽然很丑,但如果没有其他选择,它可以让你晚上睡觉。
答案2
由于我没有看到用于启动 JVM 的完整命令行参数,也不知道您在那里部署的应用程序的性质,所以我只能猜测您的应用程序正在创建大量“长寿命”对象,这些对象会进入旧代空间,而您那里的内存不足。此外,旧代空间中的 GC 收集非常昂贵,您的 JVM 可能在某个时候无法跟上 GC 运行并陷入停顿。
话虽如此,我可以建议以下 JVM 调整参数。
删除下面两个:
-XX:NewSize=1G
-XX:MaxNewSize=1G
并添加以下内容:
-XX:+UseParallelOldGC
-XX:SurvivorRatio=10
-XX:NewRatio=2
如果问题没有解决,请继续将 NewRatio 增加到 3、4、5,并查看 JVM 是否足够稳定,可以继续运行而不会出现任何问题。另外,我不确定为什么您需要 512M 的 permgen 大小-XX:PermSize=512m
。请咨询您的应用程序开发人员,看看他们是否真的需要那么多,并尽可能减少它。
此外,当问题发生时,在您终止进程之前,请运行以下命令并在此处发布输出,这将为试图帮助您的人提供线索。(注意:您必须以 root 身份运行)。
jmap -heap <pid_of_jvm>
PS:@Fredi 解释的 htop 输出是正确的,它误导性地将 LWP 线程 ID 标记为 PID。