Java Tomcat GUI/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]

由于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.
我知道这与其他问题(如)类似,但就我而言,“别担心”的答案是不够的,因为这会给我带来问题。我有一个定期更新此应用程序的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);