zoukankan      html  css  js  c++  java
  • 线程池造成服务器内存泄漏

    部署新包的时候,发现tomcat起不来了,一看内存,服务器内存吃光了,进而发现java有多余进程

    然后发现 tomcat     shutdown时,并没有释放java,造成内存奔溃了


    参考此帖:https://zhidao.baidu.com/question/433533916441175764.html


    可能存在多个tomcat进程或者是由于软件在tomcat中开启了新的线程,而且未设置成daemon,造成的主线程不能退出。
    解决方案:
    可以使用ps命令先查找到所有的tomcat进行,然后依次处理。
    注意:以下例子仅仅作为操作操作参考
    查看tomcat进程
    [root@localhost bin]# ps -ef|grep tomcat  
    root      1513     1  2 23:41 pts/1    00:00:01 /usr/local/share/java/jdk1.6.0_25/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat-6.0.32/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/opt/apache-tomcat-6.0.32/endorsed -classpath /opt/apache-tomcat-6.0.32/bin/bootstrap.jar -Dcatalina.base=/opt/apache-tomcat-6.0.32 -Dcatalina.home=/opt/apache-tomcat-6.0.32 -Djava.io.tmpdir=/opt/apache-tomcat-6.0.32/temp org.apache.catalina.startup.Bootstrap start  
    杀死tomcat
    [root@localhost bin]# kill -9 1513

    先干掉多余的java进程


    然后,有这么一段:

    tomcat shutdown 后经常会发现仍然有进程存在,其中tomcat/bin 目录下的catalina.sh是比较常用的shell,一般开启关闭tomcat操作如下:

    1
    2
    3
    4
    #启动tomcat
    ./catalina.sh start
    #关闭tomcat
    ./catalina.sh stop


      但是往往一个工程,开发一段时间后,会发现./catalina.sh stop关闭不了tomcat,而必须使用kill -9 <pid> 这样的强制命令去
      杀死tomcat,这么做当然可以,但是手法不是那么的优雅
      在tomat未被./catalina stop关闭的情况下,导致误以为tomcat已经关闭成功的哥们 会在更新完代码后,./catalina start一下,于是在服务器中就产生了2个tomcat的实例,log混乱,而后每次都用kill -9 <pid> 才放心。
      其实不用那样,一般关闭不了的情况,是由于程序员自己在tomcat中开启了新的线程,而且未设置成daemon,造成的主线程不能退出.
      怎么发现工程里到底哪里开启的新的非守护线程呢,其实jdk提供了jstack工具,可以帮助我们分析
      查看方法很简单
      $JAVA_HOME/bin/jstack  <pid>
      pid是指进程ID, 用ps -ef|grep tomcat 就可以查看到:
      

    1
    2
    3
    [root@localhost bin]# ps -ef|grep tomcat
      root      1513     1  2 23:41 pts/1    00:00:01 /usr/local/share/java/jdk1.6.0_25/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat-6.0.32/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/opt/apache-tomcat-6.0.32/endorsed -classpath /opt/apache-tomcat-6.0.32/bin/bootstrap.jar -Dcatalina.base=/opt/apache-tomcat-6.0.32 -Dcatalina.home=/opt/apache-tomcat-6.0.32 -Djava.io.tmpdir=/opt/apache-tomcat-6.0.32/temp org.apache.catalina.startup.Bootstrap start
      root      1544  1462  0 23:42 pts/1    00:00:00 grep --color=auto tomcat


      这里看到的进程ID是 1513
      调用jstack查看:
      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    [root@localhost bin]# jstack 1513
      2011-07-12 23:44:00
      Full thread dump Java HotSpot(TM) Client VM (20.0-b11 mixed mode, sharing):
      "Attach Listener" daemon prio=10 tid=0xb41d7c00 nid=0x606 waiting on condition [0x00000000]
      java.lang.Thread.State: RUNNABLE
      "TP-Monitor" daemon prio=10 tid=0xb41d6400 nid=0x5fa in Object.wait() [0xb3e0b000]
      java.lang.Thread.State: TIMED_WAITING (on object monitor)
      at java.lang.Object.wait(Native Method)
      - waiting on <0x87143720> (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable)
      at org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable.run(ThreadPool.java:565)
      - locked <0x87143720> (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable)
      at java.lang.Thread.run(Thread.java:662)
      "TP-Processor4" daemon prio=10 tid=0xb41d4c00 nid=0x5f9 runnable [0xb3e5c000]
      java.lang.Thread.State: RUNNABLE
      at java.net.PlainSocketImpl.socketAccept(Native Method)
      at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
      - locked <0x87124770> (a java.net.SocksSocketImpl)
      at java.net.ServerSocket.implAccept(ServerSocket.java:462)
      at java.net.ServerSocket.accept(ServerSocket.java:430)
      at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:311)
      at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:668)
      at org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:879)
      at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
      at java.lang.Thread.run(Thread.java:662)

      其中看到"Attach Listener" daemon prio=10 tid=0xb41d7c00 nid=0x606 waiting on condition [0x00000000]
      java.lang.Thread.State: RUNNABLE
      这行,最前变的"Attach Listener" 是线程名, 紧跟其后的 daemon是线程的守护状态,
      其中主线程不是daemon的,所以是这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    "main" prio=10 tid=0xb6d05000 nid=0x5ea runnable [0xb6ee9000]
    java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
    - locked <0x871644a8> (a java.net.SocksSocketImpl)
    at java.net.ServerSocket.implAccept(ServerSocket.java:462)
    at java.net.ServerSocket.accept(ServerSocket.java:430)
    at org.apache.catalina.core.StandardServer.await(StandardServer.java:431)
    at org.apache.catalina.startup.Catalina.await(Catalina.java:676)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:628)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)


      在"main" 后没有daemon,看到这样的线程状态,顺藤摸瓜,找到对应new Thread的地方setDaemon(true)就可以,痛痛快快的./catalina stop了

    于是想起刚用了线程池,将线程池改为直接实例化线程对象,结果显示并未造成tomcat关不掉的现象,所以将锁定在线程池上。


    参考此贴:http://blog.csdn.net/mendeliangyang/article/details/50686134


    原来,


    new Thread建立的线程都是守护线程,原因就是新建线程默认上使用建立线程时的当前线程所处的守护状态。tomcat的请求处理线程为守护线程,所以我们一般情况下建立的线程也是守护线程。然而,Executors除外。 


    这里我采用方案二,实现ServletContextListener接口

    项目是spring boot而非spring mvc,故参考此帖:

    http://412887952-qq-com.iteye.com/blog/2292475

    添加监听:


    @WebListener
    public class MyServletContextListener implements ServletContextListener {
        @Override
        public void contextDestroyed(ServletContextEvent arg0) {
            System.out.println("ServletContex销毁");
            ThreadUtil.shutDown();
        }
    
        @Override
        public void contextInitialized(ServletContextEvent arg0) {
            System.out.println("ServletContex初始化");
        }
    }
    

    在销毁中   调用ThreadUtil的shutDown函数:


    public class ThreadUtil {
        private static ExecutorService service = Executors.newFixedThreadPool(100);
    
        public static void submit(Runnable runnable) {
    
            service.submit(runnable);
         //   runnable.run();   // 此举是ok的,默认守护线程
        }
    
        public static void shutDown() {
            service.shutdownNow();
        }
    
    }


    ok,搞定。


    值得注意的是:

    必须调用shutdownNow函数,shutdown与shutdownNow的区别,见:http://justsee.iteye.com/blog/999189


    shutDown() 

        当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。 

                shutdownNow() 

         根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 
         它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。 



    附件1:

    http://blog.csdn.net/mendeliangyang/article/details/50686134

    原文地址

    最近研究embeded tomcat,特别是关于tomcat启动和关闭的模块。通过查看相应的源代码, 我们知道tomcat的关闭是通过往相应的关闭端口发送指定的关闭指令来达到关闭tomcat的目的。但是有的时候,通过shutdown.bat或 shutdown.sh却不能有效地关闭tomcat,网上也有很多人提出这个问题。通过相关资料,最后问题出现线程上。 

    首先看java虚拟机退出的条件,如下所示: 
    a,调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。 
    b,非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。 

    如上所示,第一条是通过exit退出,第二条指出了一个正常程序退出的条件,就是所有的非守护线程都已经停止运行。我们看相应embed tomcat的启动代码,如下所示: 

    Java代码 复制代码 收藏代码
    1. tomcat.start();   
    2. tomcat.getServer().await();  
    [java] view plain copy
    1. tomcat.start();  
    2. tomcat.getServer().await();  


    最后一条有效的运行命令即是await,通过调用shutdown命令时,这个await就会成功的返回。按照常理来说,整个程序即会成功的完成。但是程序有时候并没有成功的结束,原因就在于程序中还存在着非守护进程。 

    对于tomcat来说,tomcat程序中开启的所有进程都是守护进程,所以tomcat自身可以保证程序的正常结束。当await结束时,tomcat所就正常的结束了,包括相应的监听端口等,都已经成功结束。然而,由于项目程序中仍然还有其它线程在运行,所以导致java虚拟机并没有成功的退出。 

    在我们的项目中,很多时候都运用到了线程。比如,异步调用等。不过,幸运的是,这些线程往往都是守护线程,原因就在于tomcat在运行我们的项目时,对于每一个请求,tomcat是使用了守护线程来进行相应的请求调用,这个保证在以下代码: 
    Java代码 复制代码 收藏代码
    1. // Start poller threads   
    2.             pollers = new Poller[pollerThreadCount];   
    3.             for (int i = 0; i < pollerThreadCount; i++) {   
    4.                 pollers[i] = new Poller(false);   
    5.                 pollers[i].init();   
    6.                 Thread pollerThread = new Thread(pollers[i], getName() + "-Poller-" + i);   
    7.                 pollerThread.setPriority(threadPriority);   
    8.                 pollerThread.setDaemon(true);   
    9.                 pollerThread.start();   
    10.             }  
    [java] view plain copy
    1. // Start poller threads  
    2.             pollers = new Poller[pollerThreadCount];  
    3.             for (int i = 0; i < pollerThreadCount; i++) {  
    4.                 pollers[i] = new Poller(false);  
    5.                 pollers[i].init();  
    6.                 Thread pollerThread = new Thread(pollers[i], getName() + "-Poller-" + i);  
    7.                 pollerThread.setPriority(threadPriority);  
    8.                 pollerThread.setDaemon(true);  
    9.                 pollerThread.start();  
    10.             }  


    所以,一般情况下,在我们的项目代码中使用new Thread建立的线程都是守护线程,原因就是新建线程默认上使用建立线程时的当前线程所处的守护状态。tomcat的请求处理线程为守护线程,所以我们一般情况下建立的线程也是守护线程。然而,Executors除外。 

    使用Executors建立后台线程并执行一些多线程操作时,Executors会使用相对应的threadFactory来对 runnable建立新的thread,所以使用默认的threadFactory时就会出问题。默认的ThreadFactory强制性的将新创建的线程设置为非守护状态,如下所示: 
    Java代码 复制代码 收藏代码
    1. public Thread newThread(Runnable r) {   
    2.             Thread t = new Thread(group, r,   
    3.                                   namePrefix + threadNumber.getAndIncrement(),   
    4.                                   0);   
    5.             if (t.isDaemon())   
    6.                 t.setDaemon(false);   
    7.             if (t.getPriority() != Thread.NORM_PRIORITY)   
    8.                 t.setPriority(Thread.NORM_PRIORITY);   
    9.             return t;   
    10.         }  
    [java] view plain copy
    1. public Thread newThread(Runnable r) {  
    2.             Thread t = new Thread(group, r,  
    3.                                   namePrefix + threadNumber.getAndIncrement(),  
    4.                                   0);  
    5.             if (t.isDaemon())  
    6.                 t.setDaemon(false);  
    7.             if (t.getPriority() != Thread.NORM_PRIORITY)  
    8.                 t.setPriority(Thread.NORM_PRIORITY);  
    9.             return t;  
    10.         }  


    所以,一般情况下,我们使用executors创建多线程时,就会使用默认的threadFactory(即调用只有一个参数的工厂方法),而创建出来的线程就是非守护的。而相应的程序就永远不会退出,如采用Executors创建定时调度任务时,这个调试任务永远不会退出。解决的办法就是重写相对应的 threadFactory,如下所示: 
    new ThreadFactory() { 
            public Thread newThread(Runnable r) { 
                Thread s = Executors.defaultThreadFactory().newThread(r); 
                s.setDaemon(true); 
                return s; 
            } 
        } 

    同理,对于java web项目中的线程程序,一定要记住将相应的线程标记为守护线程(尽管它默认就是守护的)。而对于使用Executors,一定要记住传递相应的threadFactory实现,以重写相应的newThread方法,将线程标记为守护线程。 

    以上的结论对于普通的java项目同样有效,为了正常的结束相应的程序,一定要正确的使用相应的线程,以避免java程序不能退出的问题。


    第二种解决方案

    建一个监听类,该类实现ServletContextListener接口中的contextInitiialized()方法和contextDestroyed()方法。然后在tomcat中注册该监听类,开启或关闭tomcat时,都将先执行该监听类

    1、举例:新建监听类CgmIndexListener

    [java] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. package com.saas.cgm;  
    2.   
    3.   
    4. import javax.servlet.ServletContextEvent;  
    5. import javax.servlet.ServletContextListener;  
    6. //若使用ServletContextListener接口,可能需添加javax.servlet-5.1.12.jar或其它库  
    7. public class CgmIndexListener implements ServletContextListener{     
    8. private static ExecutorService exec = Executors.newCachedThreadPool();//线程池  
    9.    //关闭tomcat前关闭线程   
    10. public void contextDestroyed(ServletContextEvent arg0) {     
    11.    exec.shutdownNow();;     
    12. }     
    13.   //开启tomcat前执行线程  
    14. public contextInitiialized(ServletContextEvent arg0) {     
    15.   RamIndexWriter ramIndexWriter = new RamIndexWriter();//新建一个线程  
    16.   exec.execute(ramIndexWriter);  
    17. }  
    2、注册该监听类:在tomcat安装目录下的conf文件夹内的web.xml文件中添加:
    [html] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. <listener>  
    2.             <listener-class>com.saas.cgm.CgmIndexListener</listener-class>  
    3.     </listener>  

    3、此时,关闭tomcat时,tomcat首先关闭线程池exec中的ramIndexWriter 线程,当没有子线程在运行时,java.exe进程也就能被顺利关闭了


    附件2:http://412887952-qq-com.iteye.com/blog/2292475


    过滤器(
    Filter)文件

    com.kfit.filter.MyFilter.java

    package com.kfit.filter;

     

    import java.io.IOException;

     

    import javax.servlet.Filter;

    import javax.servlet.FilterChain;

    import javax.servlet.FilterConfig;

    import javax.servlet.ServletException;

    import javax.servlet.ServletRequest;

    import javax.servlet.ServletResponse;

    import javax.servlet.annotation.WebFilter;

     

    /**

     *

     * 使用注解标注过滤器

     * @WebFilter将一个实现了javax.servlet.Filter接口的类定义为过滤器

     * 属性filterName声明过滤器的名称,可选

     * 属性urlPatterns指定要过滤URL模式,也可使用属性value来声明.(指定要过滤的URL模式是必选属性)

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @WebFilter(filterName="myFilter",urlPatterns="/*")

    publicclass MyFilter implements Filter{

     

        @Override

        publicvoid init(FilterConfig configthrows ServletException {

            System.out.println("过滤器初始化");

        }

       

     

        @Override

        publicvoid doFilter(ServletRequest request, ServletResponse response,

                FilterChain chainthrows IOException, ServletException {

            System.out.println("执行过滤操作");

            chain.doFilter(requestresponse);

        }

       

        @Override

        publicvoid destroy() {

            System.out.println("过滤器销毁");

        }

    }

    ServletContext监听器(Listener)文件

    com.kfit.listener.MyServletContextListener:

    package com.kfit.listener;

     

    import javax.servlet.ServletContextEvent;

    import javax.servlet.ServletContextListener;

    import javax.servlet.annotation.WebListener;

     

    /**

     * 使用@WebListener注解,实现ServletContextListener接口

     *

     * @author Angel(QQ:412887952)

     * @version v.0.1

     */

    @WebListener

    public class MyServletContextListener implements ServletContextListener {

     

             @Override

             public void contextDestroyed(ServletContextEvent arg0) {

                       System.out.println("ServletContex销毁");

             }

     

             @Override

             public void contextInitialized(ServletContextEvent arg0) {

                        System.out.println("ServletContex初始化");

             }

    }

    ServletContext监听器(Listener)文件(HttpSessionListener

    MyHttpSessionListener.java

    package com.kfit.listener;

     

    import javax.servlet.annotation.WebListener;

    import javax.servlet.http.HttpSessionEvent;

    import javax.servlet.http.HttpSessionListener;

     

    /**

     * 监听Session的创建与销毁

     *

     */

    @WebListener

    publicclassMyHttpSessionListenerimplementsHttpSessionListener {

     

        @Override

        publicvoid sessionCreated(HttpSessionEvent se) {

            System.out.println("Session 被创建");

        }

     

        @Override

        publicvoid sessionDestroyed(HttpSessionEvent se) {

            System.out.println("ServletContex初始化");

        }

     

    }

    注意不要忘记在 SpringBootSampleApplication.java 上添加 @ServletComponentScan 注解。

    启动的过程中我们会看到输出:

    ServletContex初始化

    过滤器初始化

    服务启动后,随便访问一个页面,会看到输出:

    执行过滤操作
    Session 被创建

    为什么无法看到session的过程:http://zhidao.baidu.com/link?url=EP-wlLvKpo8zI5NaIZrESzCdivq3Xg8VgOWQOvfpSLl3opTgvESerpo4wsG6tOs_dm6cQQMF_kQ6THNjNzr2Nq

    至于如何使用代码的方式注册FilterListener,请参考上一篇文章关键Servlet的介绍。不同的是需要使用FilterRegistrationBean  ServletListenerRegistrationBean 这两个类。




  • 相关阅读:
    前向传播与反向传播
    卷积运算
    使用GUI工具Portainer.io管控Docker容器
    NextCloud: 打造自己的网盘
    金融危机和经济危机有什么不同
    【转载】Windows环境的Workflow神器:AutoHotkey
    Lua常用模块
    Lua基本语法
    区分 IaaS、SaaS 和 PaaS
    【笔记】流畅的Python
  • 原文地址:https://www.cnblogs.com/silyvin/p/9106796.html
Copyright © 2011-2022 走看看