由于Tomcat中的孤立线程,我遇到了内存泄漏问题。特别是,似乎Guice和JDBC驱动程序没有关闭线程。
Aug 8, 2012 4:09:19 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: A web application appears to have started a thread named [com.google.inject.internal.util.$Finalizer] but has failed to stop it. This is very likely to create a memory leak.
Aug 8, 2012 4:09:19 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: A web application appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.
我知道这与其他问题类似(例如this one),但在我的情况下,“不要担心它”的答案是不够的,因为它对我造成了问题。我有CI服务器定期更新此应用程序,并且在6-10次重新加载后,CI服务器将挂起,因为Tomcat内存不足。
我需要能够清除这些孤立的线程,以便我可以更可靠地运行我的CI服务器。任何帮助将不胜感激!
答案 0 :(得分:52)
我自己刚刚解决了这个问题。与其他一些答案相反,我不建议发出t.stop()
命令。此方法已被弃用,并且有充分的理由。执行此操作时参考Oracle's reasons。
但是,有一种解决方案可以消除此错误,而无需诉诸t.stop()
...
您可以使用@Oso提供的大部分代码,只需替换以下部分
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for(Thread t:threadArray) {
if(t.getName().contains("Abandoned connection cleanup thread")) {
synchronized(t) {
t.stop(); //don't complain, it works
}
}
}
使用MySQL驱动程序提供的以下方法替换它:
try {
AbandonedConnectionCleanupThread.shutdown();
} catch (InterruptedException e) {
logger.warn("SEVERE problem cleaning up: " + e.getMessage());
e.printStackTrace();
}
这应该正确关闭线程,错误应该消失。
答案 1 :(得分:15)
我遇到了同样的问题,正如杰夫所说的那样,“不要担心它的方法”不是要走的路。
我做了一个ServletContextListener,它在关闭上下文时停止挂起的线程,然后在web.xml文件上注册了这样的ContextListener。
我已经知道停止一个线程并不是处理它们的一种优雅方式,但是否则服务器会在两次或三次部署后继续崩溃(并不总是可以重新启动应用服务器)。
我创建的课程是:
public class ContextFinalizer implements ServletContextListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ContextFinalizer.class);
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver d = null;
while(drivers.hasMoreElements()) {
try {
d = drivers.nextElement();
DriverManager.deregisterDriver(d);
LOGGER.warn(String.format("Driver %s deregistered", d));
} catch (SQLException ex) {
LOGGER.warn(String.format("Error deregistering driver %s", d), ex);
}
}
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for(Thread t:threadArray) {
if(t.getName().contains("Abandoned connection cleanup thread")) {
synchronized(t) {
t.stop(); //don't complain, it works
}
}
}
}
}
创建类后,将其注册到web.xml文件:
<web-app...
<listener>
<listener-class>path.to.ContextFinalizer</listener-class>
</listener>
</web-app>
答案 2 :(得分:13)
最少侵入性的解决方法是强制从webapp的类加载器之外的代码初始化MySQL JDBC驱动程序。
在tomcat / conf / server.xml中,修改(在Server元素内):
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
到
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
classesToInitialize="com.mysql.jdbc.NonRegisteringDriver" />
这假设您将MySQL JDBC驱动程序放入tomcat的lib目录而不是webapp.war的WEB-INF / lib目录中,因为重点是在之前加载驱动程序并独立于您的web应用程序。
参考文献:
答案 3 :(得分:11)
从MySQL连接器5.1.23开始,提供了一种方法来关闭已放弃的连接清理线程,AbandonedConnectionCleanupThread.shutdown
。
但是,我们不希望在代码中直接依赖于其他不透明的JDBC驱动程序代码,因此我的解决方案是使用反射来查找类和方法,并在找到时调用它。以下完整的代码片段就是所需要的,在加载JDBC驱动程序的类加载器的上下文中执行:
try {
Class<?> cls=Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread");
Method mth=(cls==null ? null : cls.getMethod("shutdown"));
if(mth!=null) { mth.invoke(null); }
}
catch (Throwable thr) {
thr.printStackTrace();
}
如果JDBC驱动程序是MySQL连接器的最新版本,则它会干净地结束线程,否则什么都不做。
注意它必须在类加载器的上下文中执行,因为该线程是静态引用;如果在运行此代码时驱动程序类尚未被卸载或尚未卸载,则该线程将不会为后续JDBC交互运行。
答案 4 :(得分:6)
我把上面答案的最佳部分合并到一个易于扩展的课程中。这将Oso的原始建议与Bill的驱动程序改进和Software Monkey的反思改进相结合。 (我也喜欢Stephan L'的简单回答,但有时修改Tomcat环境本身并不是一个好选择,特别是如果你必须处理自动缩放或迁移到另一个Web容器。)
我没有直接引用类名,线程名和stop方法,而是将它们封装到一个私有的内部ThreadInfo类中。使用这些ThreadInfo对象的列表,您可以使用相同的代码包含其他麻烦的线程。这比大多数人可能需要的解决方案要复杂一些,但是在你需要的时候应该更普遍地工作。
import java.lang.reflect.Method;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Context finalization to close threads (MySQL memory leak prevention).
* This solution combines the best techniques described in the linked Stack
* Overflow answer.
* @see <a href="https://stackoverflow.com/questions/11872316/tomcat-guice-jdbc-memory-leak">Tomcat Guice/JDBC Memory Leak</a>
*/
public class ContextFinalizer
implements ServletContextListener {
private static final Logger LOGGER =
LoggerFactory.getLogger(ContextFinalizer.class);
/**
* Information for cleaning up a thread.
*/
private class ThreadInfo {
/**
* Name of the thread's initiating class.
*/
private final String name;
/**
* Cue identifying the thread.
*/
private final String cue;
/**
* Name of the method to stop the thread.
*/
private final String stop;
/**
* Basic constructor.
* @param n Name of the thread's initiating class.
* @param c Cue identifying the thread.
* @param s Name of the method to stop the thread.
*/
ThreadInfo(final String n, final String c, final String s) {
this.name = n;
this.cue = c;
this.stop = s;
}
/**
* @return the name
*/
public String getName() {
return this.name;
}
/**
* @return the cue
*/
public String getCue() {
return this.cue;
}
/**
* @return the stop
*/
public String getStop() {
return this.stop;
}
}
/**
* List of information on threads required to stop. This list may be
* expanded as necessary.
*/
private List<ThreadInfo> threads = Arrays.asList(
// Special cleanup for MySQL JDBC Connector.
new ThreadInfo(
"com.mysql.jdbc.AbandonedConnectionCleanupThread", //$NON-NLS-1$
"Abandoned connection cleanup thread", //$NON-NLS-1$
"shutdown" //$NON-NLS-1$
)
);
@Override
public void contextInitialized(final ServletContextEvent sce) {
// No-op.
}
@Override
public final void contextDestroyed(final ServletContextEvent sce) {
// Deregister all drivers.
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver d = drivers.nextElement();
try {
DriverManager.deregisterDriver(d);
LOGGER.info(
String.format(
"Driver %s deregistered", //$NON-NLS-1$
d
)
);
} catch (SQLException e) {
LOGGER.warn(
String.format(
"Failed to deregister driver %s", //$NON-NLS-1$
d
),
e
);
}
}
// Handle remaining threads.
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for (Thread t:threadArray) {
for (ThreadInfo i:this.threads) {
if (t.getName().contains(i.getCue())) {
synchronized (t) {
try {
Class<?> cls = Class.forName(i.getName());
if (cls != null) {
Method mth = cls.getMethod(i.getStop());
if (mth != null) {
mth.invoke(null);
LOGGER.info(
String.format(
"Connection cleanup thread %s shutdown successfully.", //$NON-NLS-1$
i.getName()
)
);
}
}
} catch (Throwable thr) {
LOGGER.warn(
String.format(
"Failed to shutdown connection cleanup thread %s: ", //$NON-NLS-1$
i.getName(),
thr.getMessage()
)
);
thr.printStackTrace();
}
}
}
}
}
}
}
答案 5 :(得分:2)
我从Oso更进一步,在两点上改进了上面的代码:
将Finalizer线程添加到need-to-kill检查:
for(Thread t:threadArray) {
if(t.getName().contains("Abandoned connection cleanup thread")
|| t.getName().matches("com\\.google.*Finalizer")
) {
synchronized(t) {
logger.warn("Forcibly stopping thread to avoid memory leak: " + t.getName());
t.stop(); //don't complain, it works
}
}
}
睡一会儿让线程有时间停下来。没有它,tomcat一直在抱怨。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.debug(e.getMessage(), e);
}
答案 6 :(得分:2)
比尔的解决方案看起来不错,但我在MySQL错误报告中直接找到了另一种解决方案:
[2013年6月5日17:12] Christopher Schultz 这是一个更好的解决方法,直到其他更改。
启用Tomcat的JreMemoryLeakPreventionListener(默认情况下在Tomcat 7上启用),并将此属性添加到元素中:
<强> classesToInitialize = “com.mysql.jdbc.NonRegisteringDriver”强>
如果您已经设置了“classesToInitialize”,只需将NonRegisteringDriver添加到以逗号分隔的现有值。
和答案:
[2013年6月8日21:33] Marko Asplund 我使用JreMemoryLeakPreventionListener / classesToInitialize解决方法(Tomcat 7.0.39 + MySQL Connector / J 5.1.25)进行了一些测试。
在应用变通方法之前,线程转储在多次重新部署webapp之后列出了多个AbandonedConnectionCleanupThread实例。应用解决方法后,只有一个AbandonedConnectionCleanupThread实例。
我不得不修改我的应用程序,并将MySQL驱动程序从webapp移到Tomcat lib。 否则,类加载器无法在Tomcat启动时加载com.mysql.jdbc.NonRegisteringDriver。
我希望所有仍在与这个问题作斗争的人都会有所帮助...
答案 7 :(得分:2)
似乎已在 5.1.41 中修复了此问题。您可以将Connector / J升级到5.1.41或更高版本。 https://dev.mysql.com/doc/relnotes/connector-j/5.1/en/news-5-1-41.html
AbandonedConnectionCleanupThread的实现现在已得到改进,因此现在有四种方法可供开发人员处理这种情况:
当使用默认的Tomcat配置并将Connector / J jar放入本地库目录时,Connector / J中的新内置应用程序检测器现在会在5秒内检测到Web应用程序的停止并且杀死AbandonedConnectionCleanupThread。还避免了关于线程不可阻挡的任何不必要的警告。如果将Connector / J jar放入全局库目录,则该线程将保持运行,直到卸载JVM为止。
当使用属性clearReferencesStopThreads =&#34; true&#34;配置Tomcat的上下文时,Tomcat将在应用程序停止时停止所有生成的线程,除非Connector / J与其他人共享Web应用程序,在这种情况下,Connector / J现在可以防止Tomcat不适当地停止;关于不可停止线程的警告仍会发送到Tomcat的错误日志中。
当在上下文销毁时调用AbandonedConnectionCleanupThread.checkedShutdown()的每个Web应用程序中实现ServletContextListener时,如果驱动程序可能与其他应用程序共享,则Connector / J现在再次跳过此操作。在这种情况下,不会向Tomcat的错误日志发出关于线程不可阻挡的警告。
调用AbandonedConnectionCleanupThread.uncheckedShutdown()时,即使Connector / J与其他应用程序共享,AbandonedConnectionCleanupThread也会关闭。但是,之后可能无法重新启动该线程。
如果查看源代码,他们会在线程上调用setDeamon(true),因此不会阻止关闭。
Thread t = new Thread(r, "Abandoned connection cleanup thread");
t.setDaemon(true);
答案 8 :(得分:1)
见To prevent a memory leak, the JDBC Driver has been forcibly unregistered。 Bill的答案取消注册所有Driver实例以及可能属于其他Web应用程序的实例。我已经通过检查Driver实例属于右ClassLoader
。
以下是生成的代码(在单独的方法中,因为我的contextDestroyed
还有其他事情要做):
// See https://stackoverflow.com/questions/25699985/the-web-application-appears-to-have-started-a-thread-named-abandoned-connect
// and
// https://stackoverflow.com/questions/3320400/to-prevent-a-memory-leak-the-jdbc-driver-has-been-forcibly-unregistered/23912257#23912257
private void avoidGarbageCollectionWarning()
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver d = null;
while (drivers.hasMoreElements()) {
try {
d = drivers.nextElement();
if(d.getClass().getClassLoader() == cl) {
DriverManager.deregisterDriver(d);
logger.info(String.format("Driver %s deregistered", d));
}
else {
logger.info(String.format("Driver %s not deregistered because it might be in use elsewhere", d.toString()));
}
}
catch (SQLException ex) {
logger.warning(String.format("Error deregistering driver %s, exception: %s", d.toString(), ex.toString()));
}
}
try {
AbandonedConnectionCleanupThread.shutdown();
}
catch (InterruptedException e) {
logger.warning("SEVERE problem cleaning up: " + e.getMessage());
e.printStackTrace();
}
}
我想知道电话AbandonedConnectionCleanupThread.shutdown()
是否安全。它会干扰其他Web应用程序吗?我希望不是,因为AbandonedConnectionCleanupThread.run()
方法不是静态的,而AbandonedConnectionCleanupThread.shutdown()
方法是。