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

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

 
阅读更多

问题描述

  在一个项目中,有一个单独的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程序 会不会更好一点?

相关推荐

    多线程安全退出实例源代码

    6. **finally块**:在多线程代码中,`try-finally`结构通常用于确保在任何情况下都能执行清理操作,比如关闭文件流、网络连接等。 7. **线程局部变量(ThreadLocal)**:这些变量为每个线程提供独立的副本,避免了...

    POSIX多线程程序设计.pdf

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

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

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

    Win32 多线程程序设计(pdf)

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

    POSIX多线程程序设计

    在多线程编程中,调试是程序员经常面临的挑战之一。为了帮助编程者应对这一挑战,本书还专门讨论了调试问题,并为如何从一开始就避免错误和性能问题提供了有价值的建议。这些内容对任何希望提升自己多线程编程技能的...

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

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

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

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

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

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

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

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

    多线程GDI程序

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

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

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

    win32多线程程序设计源码_侯捷

    《win32多线程程序设计源码_侯捷》是一个关于Windows平台上使用C++进行多线程编程的资源集合,由知名IT专家侯捷编著。这个资源可能包括了一系列的示例代码、讲解文档,旨在帮助开发者深入理解和实践win32 API中的多...

    VC 6.0获得每个多线程的退出码.rar

    VC 获得线程的退出码,这是一个多线程比如启动线程一之后,系统会自动给该线程分配一个识别码,本程序就是获得这个识别码,一共有两个示例,完成的功能是一样的。比如其中一个线程的控制是这样的:  void CDemoDlg:...

    java多线程文件传输

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

    C# 多线程编程实例实战

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

    mfc多线程聊天程序

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

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

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

    C#队列Queue多线程用法实例

    队列在多线程环境下常常用于任务调度、消息传递等场景,因为它们能有效地管理和同步数据访问。本实例将详细讲解如何在多线程中使用C#的Queue类。 首先,我们创建一个队列实例,通过`new Queue()`来指定存储的数据...

    海康网络相机利用SDK 多线程读取图像程序

    海康网络相机利用SDK进行多线程读取图像的程序设计是一个高级的计算机视觉与图像处理技术,主要涉及以下几个核心知识点: 1. **海康威视SDK**:海康威视是全球知名的安防设备制造商,提供了丰富的SDK(Software ...

    java多线程聊天程序

    在多线程聊天程序中,多线程技术至关重要。Java中的`Thread`类和`Runnable`接口允许我们创建并运行多个线程。通常,我们会创建一个主线程负责GUI的更新,确保界面的流畅性;另一个或多个线程则处理网络通信,如接收...

Global site tag (gtag.js) - Google Analytics