由于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.
Run Code Online (Sandbox Code Playgroud)
我知道这与其他问题(例如这个问题)类似,但在我的情况下,"不要担心它"的答案是不够的,因为它给我带来了问题.我有CI服务器定期更新此应用程序,并且在6-10次重新加载后,CI服务器将挂起,因为Tomcat内存不足.
我需要能够清除这些孤立的线程,以便我可以更可靠地运行我的CI服务器.任何帮助,将不胜感激!
Bil*_*ill 52
我自己刚刚解决了这个问题.与其他一些答案相反,我不建议发出t.stop()命令.此方法已被弃用,并且有充分的理由.参考Oracle的原因.
但是,有一个解决方案可以删除此错误,而无需诉诸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
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用MySQL驱动程序提供的以下方法替换它:
try {
AbandonedConnectionCleanupThread.shutdown();
} catch (InterruptedException e) {
logger.warn("SEVERE problem cleaning up: " + e.getMessage());
e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)
这应该正确关闭线程,错误应该消失.
Oso*_*Oso 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
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
创建类后,将其注册到web.xml文件:
<web-app...
<listener>
<listener-class>path.to.ContextFinalizer</listener-class>
</listener>
</web-app>
Run Code Online (Sandbox Code Playgroud)
Ste*_*n L 13
最少侵入性的解决方法是强制从Web应用程序的类加载器之外的代码初始化MySQL JDBC驱动程序.
在tomcat/conf/server.xml中,修改(在Server元素内):
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
Run Code Online (Sandbox Code Playgroud)
至
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
classesToInitialize="com.mysql.jdbc.NonRegisteringDriver" />
Run Code Online (Sandbox Code Playgroud)
这假设您将MySQL JDBC驱动程序放入tomcat的lib目录而不是webapp.war的WEB-INF/lib目录中,因为重点是在您的webapp 之前和之后加载驱动程序.
参考文献:
Law*_*Dol 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();
}
Run Code Online (Sandbox Code Playgroud)
如果JDBC驱动程序是MySQL连接器的最新版本,则它会干净地结束线程,否则什么也不做.
注意它必须在类加载器的上下文中执行,因为该线程是静态引用; 如果在运行此代码时驱动程序类尚未被卸载或尚未卸载,则该线程将不会为后续JDBC交互运行.
我把上面答案的最佳部分组合成一个易于扩展的类.这将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();
}
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
48813 次 |
| 最近记录: |