`
coding1688
  • 浏览: 237539 次
  • 来自: 上海
社区版块
存档分类
最新评论

悲剧了,这个多线程程序为什么不能在指定时间自动退出?(详细分析)

 
阅读更多

问题描述

  在一个项目中,有一个单独的java程序,它使用了第三方类库,而且是必须使用的那种,但是这个第三方类库有个致命的问题:它如同一头永远处于饥饿状态的野兽,它会不断的吃掉内存,最终导致“ java.lang.OutOfMemoryError: Java heap space”异常。

 

 

log 写道
12:00:02.597 ERROR Thread-1 emay.sms.relay.EmaySDKClient - run
java.lang.OutOfMemoryError: Java heap space
 

 

  虽然出现了异常,但它不会退出。只有等到发现的时候,人工去这个杀掉这个进程。这种情况多次反复出现,造成了一些不良影响。

 

不管用的解决方案

  能不能让它定期重启一下呢?(这几乎总是最后的解决方法了,因为系统中未知的因素太多)程序退出之后,所占用的内存不也就释放掉了嘛!
  在 main() 方法的末尾加上下面的代码,意思是说“在每天0点的时候程序自动退出”。(因为在系统中有一个任务管理程序,它会检查进程是否还在,若已终止则重新启动。)

 

 

            while (true) {
                Thread.sleep(3600 * 1000);
                java.util.Date now = new java.util.Date();
                if (now.getHours() == 0) {
                    break;
                }
            }
 

 

  这个看上去没什么问题,于是编译重新运行。然而,在第二天 0 点的日志中没有发现重启的记录,也就是说它没有按照预想的那样,在0点并没有退出!

 

管用的改进?

  在 java 中,线程有个 daemon 属性(是否守护线程),应该将程序中主线程之外的线程设置为 daemon 属性,这样才能保证 main() 方法执行完之后程序自动退出来,否则进程就会一直等待所有的非守护线程退出。下面是关于守护线程的一些描述信息。

http://blog.csdn.net/lanniao1/article/details/1831626 写道
守护线程(Daemon)
  Java有两种Thread:“守护线程(Daemon Thread)”与“用户线程(User Thread)”。守护线程是一种“在后台提供通用性支持”的线程,它并不属于程序本体。从字面上我们很容易将守护线程理解成是由虚拟机(virtual machine)在内部创建的,而用户线程则是自己所创建的。事实并不是这样,任何线程都可以是“守护线程”或“用户线程”。他们在几乎每个方面都是相同的,唯一的区别是判断虚拟机何时离开:
  用户线程:Java虚拟机在它所有用户线程(非守护线程)已经离开后自动离开。
  守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
  当程序只有守护线程时,该程序便可以结束运行。 
  Thread.setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。setDaemon(boolean on)方法必须在线程启动(start)之前调用,当线程正在运行时调用会产生异常(IllegalThreadStateException)。isDaemon方法将测试该线程是否为守护线程。值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
  例如,我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
 

  在这个程序中,有两个线程,一个线程是不断调用第三方类库提供的方法,一个线程是一个网络客户端,将这两个线程都设置为 daemon 属性。这两个类分别为 Relay 和 Client,是 Thread 类的子类。这样之后 main() 方法的内容大体如下:

 

             // 第一个线程
             Relay relay = new Relay();
             // ...
             relay.setDaemon(true);
             relay.start();

             // 第二个线程
             Client client = new Client();
             client.setDaemon(true);
             client.start();

             // 下面的代码用于在每天0点退出
             while (true) {
                Thread.sleep(3600 * 1000);
                java.util.Date now = new java.util.Date();
                if (now.getHours() == 0) {
                    break;
                }
            }

 

这总该管用了吧?!经过检查第二天的日志,发现还是没有在0点退出,为什么?

 

这个程序到底起了多少线程?

  猜测,也许这个第三方类库中可能启动一些别的线程,否则就与 setDaemon(true) 的精神相违背。那得看一下它到底启动了哪些线程,在JDK中,jstack 可以观察到jvm中当前所有线程的运行情况和线程当前状态,使用方式是 jstack pid;其中 pid 为进程id,可用 jps 或 ps 或 top 等工具得到。

  用 jstack 看一下。下面是去掉第三方类库的程序执行线程状态信息。

 

jstack 写道
[root@web186 emay_sms_relay]# jstack 8082
2012-06-11 13:35:01
Full thread dump Java HotSpot(TM) Client VM (16.3-b01 mixed mode, sharing):

"Attach Listener" daemon prio=10 tid=0x081db400 nid=0x562e runnable [0x00000000]
java.lang.Thread.State: RUNNABLE

"Thread-1" daemon prio=10 tid=0x081d5400 nid=0x1f9d runnable [0xb41ca000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at smj.client.SmjClient.recv(SmjClient.java:96)
at smj.client.SmjClient.loop(SmjClient.java:139)
at smj.client.SmjClient.run(SmjClient.java:124)
at java.lang.Thread.run(Thread.java:619)

"Low Memory Detector" daemon prio=10 tid=0x08096400 nid=0x1f99 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE

"CompilerThread0" daemon prio=10 tid=0x08093000 nid=0x1f98 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x08091400 nid=0x1f97 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=10 tid=0x0807e400 nid=0x1f96 in Object.wait() [0xb48b4000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x87040b00> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
- locked <0x87040b00> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x0807cc00 nid=0x1f95 in Object.wait() [0xb4905000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x87040a08> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:485)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked <0x87040a08> (a java.lang.ref.Reference$Lock)

"main" prio=10 tid=0x08058400 nid=0x1f93 waiting on condition [0xb6b96000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at kdx.sms.relay.Main.main(Main.java:22)

"VM Thread" prio=10 tid=0x0807b400 nid=0x1f94 runnable

"VM Periodic Task Thread" prio=10 tid=0x08098400 nid=0x1f9a waiting on condition

JNI global references: 994

[root@web186 emay_sms_relay]#

 

 

下面是增加调用第三方类库的线程之后新增的线程。其中有两个线程没有 daemon 属性,不是守护线程。

 

jstack 写道
"Thread-1" daemon prio=10 tid=0x08200c00 nid=0x1eed waiting on condition [0xb3fe3000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at emay.sms.relay.EmaySDKClient.run(EmaySDKClient.java:405)

"MonitorThread" prio=10 tid=0x081e3400 nid=0x1eec in Object.wait() [0xb4136000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x89bf61b0> (a java.util.TaskQueue)
at java.util.TimerThread.mainLoop(Timer.java:509)
- locked <0x89bf61b0> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:462)

"Thread-2" prio=10 tid=0x081e2c00 nid=0x1eeb waiting on condition [0xb4187000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at cn.emay.sdk.communication.socket.ReceiveThread.run(ReceiveThread.java:53)
 

 

也就是说,由于这两个非守护线程的存在, main()方法执行完之后,程序不会自动终止,因为它一直在执着的等待这两个线程的结束,它会一直等下去,直到poweroff。

 

最终解决

当然,问题总是可以解决的,那就是显式的调用 System.exit() 方法强制退出程序啦。如下所示:

 

            while (true) {
                Thread.sleep(3600 * 1000);
                java.util.Date now = new java.util.Date();
                if (now.getHours() == 0) {
                    //break;
                    System.exit(100);
                }
            }
 

 

经过观测,这个方法达到了预期的目标,每天0点的时候就会自动退出。

 

后记

  从上述解决方案的本质上讲,算不得一个好的解决方案,这相当于让程序自己来监控自己,发现自己有问题就退出,这样的解决方法通常会有不管用的时候。因为程序在出现问题的时候,用于监控的那段程序也许也不灵了。(是不是可以联想到现实生活中来

  最好的方式还是第三方监控,就是说单独写个脚本(或程序)来监控进程是否正常,比如占用的内存、CPU、网络连接、数据库连接等是否超过指定的限度,在超过限度时进行告警或者自动终止、自动重启等。当然,这种监控也不是万能的,如果能够一劳永逸的解决问题,那还要我们搞 IT 的人干什么呢?!

 

 

 

 

 

3
1
分享到:
评论
1 楼 koujun 2012-06-11  
用linux的cron来 start/stop程序 会不会更好一点?

相关推荐

    易语言正确退出线程

    在编程领域,线程是程序执行的基本单元,特别是在多任务操作系统中。易语言是一种中文编程环境,它提供了方便的线程操作接口。本篇将详细探讨如何在易语言中实现“正确退出线程”这一重要知识点。 首先,理解线程的...

    POSIX多线程程序设计.pdf

    《POSIX多线程程序设计》深入描述了IEEE的开放系统接口标准——POSIX线程,通常称为Pthreads标准。本书首先解释了线程的基本概念,包括异步编程、线程的生命周期和同步机制;然后讨论了一些高级话题,包括属性对象、...

    一个多线程示例程序及多线程常见问题介绍

    在IT领域,多线程是程序设计中的一个重要概念,它允许程序同时执行多个任务,显著提高了计算机系统的效率和响应...学习这个示例将有助于提升我们在多线程编程方面的技能,使我们能够编写更加高效和可靠的并发应用程序。

    Win32 多线程程序设计(pdf)

    在多线程程序中,资源管理也是一个重要的话题。线程可以拥有自己的堆栈空间,但全局变量和动态分配的内存通常是共享的,需要特别小心处理。`ExitThread`函数用于结束线程的执行,而`CloseHandle`函数用于关闭线程...

    简单QT多线程聊天程序

    这个“简单QT多线程聊天程序”旨在提供一个基础的学习平台,帮助开发者理解如何在QT环境中利用多线程进行并发处理。 QT框架是一个跨平台的C++库,提供了丰富的GUI工具和网络编程接口。在这个聊天程序中,多线程技术...

    Win32 多线程程序设计(候捷译).rar

    4. 线程退出与等待:线程执行完毕后会自动退出,也可以调用ExitThread函数强制退出;主线程可以通过WaitForSingleObject或WaitForMultipleObjects函数等待一个或多个线程结束。 三、线程间的通信 线程间通信是多...

    Win32多线程程序设计(pdf加全书签)

    《Win32多线程程序设计》是一本深入探讨C++在Win32平台上实现多线程编程的专业书籍。该书全面覆盖了多线程技术的基础理论与实践应用,为开发者提供了详尽的指导。书中的"加全书签"功能使得读者能够更方便地定位和...

    C#后台多线程实现自动采集指定网页上面指定标签新闻

    在本项目“C#后台多线程实现自动采集指定网页上面指定标签新闻”中,开发者利用C#语言的特性,创建了一个能够后台运行的新闻采集系统。这个系统能够高效地从设定的网站上抓取指定标签的新闻内容,包括图片、文字等...

    易语言多线程模块

    而在多线程环境下,多个线程可以并发执行,使得程序能够并行处理多个任务,提升了程序的运行效率。易语言的多线程模块正是提供了这样的功能,使开发者能够在易语言中编写多线程程序。 创建线程是多线程编程的第一步...

    C#多线程互斥实例 多线程获取同一变量

    在这个"多线程互斥实例 多线程获取同一变量"的示例中,我们将探讨如何在多个线程中安全地访问共享资源,避免数据不一致性和竞态条件。 首先,我们需要理解多线程中的一些核心概念: 1. **线程**:线程是操作系统...

    用c#写的多线程PING的程序

    在这个项目中,我们讨论的是一个使用C#编程语言实现的多线程`ping`程序,它可以同时对多个目标进行探测,并且能够统计和展示结果。下面我们将深入探讨这个程序涉及的技术点。 首先,我们要了解C#中的多线程。在C#中...

    多线程GDI程序

    在这个微软VC6的示例中,程序展示了如何在多线程环境中有效利用GDI进行绘图。多线程的使用通常是为了提高性能,尤其是在处理密集型计算或I/O操作时。在图形绘制的场景下,如果在一个线程中完成所有的绘制工作,可能...

    程序崩溃自动重启以及将未捕获到的异常写退出栈

    在IT行业中,程序崩溃是常见的问题,特别是在自动化测试和生产环境中。为了确保系统的稳定性和可靠性,开发者需要采取一些策略来处理这种情况。本篇文章将详细探讨如何实现“程序崩溃自动重启”以及“未捕获异常时写...

    MFC的多线程队列程序源代码

    在单线程程序中,任务是顺序执行的,而在多线程程序中,可以同时执行多个任务,这极大地提高了程序的效率和响应性。在MFC中,`CWinThread`类是线程的基础,它封装了Windows API的线程创建和管理。 队列,作为一种...

    java多线程文件传输

    Java多线程文件传输是Java编程中一个重要的实践领域,特别是在大数据处理、网络通信和分布式系统中。在Java中,多线程可以提高程序的执行效率,尤其在处理并发任务时,如大文件的上传、下载和传输。下面将详细探讨...

    C# 多线程编程实例实战

    为了解决这个问题,需要满足两个要求:当一个线程正在写入数据时,其他线程不能写,也不能读;当一个线程正在读取数据时,其他线程不能写,但能够读。 为了实现单个写入程序/多个阅读程序的线程同步,需要引入锁...

    mfc多线程聊天程序

    - **线程安全**:确保在多个线程访问共享数据时,数据的一致性和完整性。使用线程同步机制防止数据冲突。 - **消息泵**:在网络线程中,通常需要有一个消息泵来处理线程内部的消息,这样可以响应线程特定的事件,如...

    易编远航第一期-第九套多线程幻想神域实战视频

    2.熟悉在多线程下参数传递及子程序调用 3.副本功能的简单制作 4.特色功能的简单思路 幻想神域多线程实战视频 第一章(基础设置) 1.多线程实战之中控台简单说明 2.多线程实战之绑定模式与字库制作 3.多线程实战...

    mfc安全退出线程

    在MFC(Microsoft Foundation Classes)框架中,线程的管理是一项关键任务,特别是在多线程应用程序中。"mfc安全退出线程"是指在MFC环境中,如何正确、安全地终止一个正在运行的线程,避免引发数据不一致、资源泄露...

Global site tag (gtag.js) - Google Analytics