Java Tomcat GUI/JDBC内存泄漏
由于Tomcat中的孤立线程,我遇到内存泄漏。特别是,Guice和JDBC驱动程序似乎没有关闭线程Java Tomcat GUI/JDBC内存泄漏,java,tomcat,Java,Tomcat,由于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]
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.
我知道这与其他问题(如)类似,但就我而言,“别担心”的答案是不够的,因为这会给我带来问题。我有一个定期更新此应用程序的CI服务器,在6-10次重新加载后,CI服务器将挂起,因为Tomcat内存不足
我需要能够清除这些孤立线程,以便更可靠地运行CI服务器。任何帮助都将不胜感激 我也遇到过同样的问题,正如杰夫所说,“不必担心”的方法不是最好的选择 我做了一个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
}
}
}
}
}
公共类ContextFinalizer实现ServletContextListener{
私有静态最终记录器Logger=LoggerFactory.getLogger(ContextFinalizer.class);
@凌驾
public void contextInitialized(ServletContextEvent sce){
}
@凌驾
公共无效上下文已销毁(ServletContextEvent sce){
枚举驱动程序=DriverManager.getDrivers();
驱动程序d=null;
while(drivers.hasMoreElements()){
试一试{
d=drivers.nextElement();
DriverManager.deregisterDriver(d);
LOGGER.warn(String.format(“驱动程序%s已注销”,d));
}catch(SQLException-ex){
LOGGER.warn(String.format(“取消注册驱动程序%s时出错”,d),例如);
}
}
Set threadSet=Thread.getAllStackTraces().keySet();
Thread[]threadArray=threadSet.toArray(新线程[threadSet.size()]);
for(线程t:threadArray){
if(t.getName().contains(“放弃的连接清理线程”)){
同步(t){
t、 stop();//不要抱怨,这很有效
}
}
}
}
}
创建类后,将其注册到web.xml文件中:
<web-app...
<listener>
<listener-class>path.to.ContextFinalizer</listener-class>
</listener>
</web-app>
我比Oso更进一步,从两点改进了上面的代码:
将终结器线程添加到需要终止检查:
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);
}
我只是自己处理这个问题。与其他一些答案相反,我不建议发出t.stop()
命令。此方法已被弃用,理由充分。这样做的参考
但是,有一种解决方案可以消除此错误,而无需求助于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 connector 5.1.23开始生效,提供了一种关闭放弃的连接清理线程的方法,放弃的连接清理线程。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();
}
试试看{
Class cls=Class.forName(“com.mysql.jdbc.弃置连接清理线程”);
方法mth=(cls==null?null:cls.getMethod(“shutdown”);
如果(mth!=null){mth.invoke(null);}
}
捕获(可丢弃的thr){
thr.printStackTrace();
}
如果JDBC驱动程序是MySQL连接器的最新版本,否则什么也不做,那么这就彻底结束了线程
注意,它必须在类加载器的上下文中执行,因为线程是静态引用;如果在运行此代码时驱动程序类未被卸载或尚未卸载,则线程将不会运行以进行后续JDBC交互。最小侵入性的解决方法是从webapp类加载器外部的代码强制初始化MySQL JDBC驱动程序
在tomcat/conf/server.xml中,修改(在server元素内):
到
- 对于mysql-connector-java-8.0.x,请改用
com.mysql.cj.jdbc.NonRegisteringDriver
这假设您将MySQL JDBC驱动程序放在tomcat的lib目录中,而不是放在webapp.war的WEB-INF/lib目录中,因为整个要点是在webapp之前并独立于webapp加载驱动程序
参考资料:
- 源代码v5.1
- 源代码v8.0
我把上面答案中最好的部分组合成一个易于扩展的类。这将Oso的原始建议与Bill的驱动程序改进和软件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();
}
}
}
}
}
}
}
import java.lang.reflect.Method;
导入java.sql.Driver;
导入java.sql.DriverManager;
导入java.sql。
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
classesToInitialize="com.mysql.jdbc.NonRegisteringDriver" />
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();
}
}
}
}
}
}
}
// 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();
}
}
Thread t = new Thread(r, "Abandoned connection cleanup thread");
t.setDaemon(true);