悲剧了,这个多线程程序为什么不能在指定时间自动退出?(详细分析)问题描述 在一个项目中,有一个单独的java
悲剧了,这个多线程程序为什么不能在指定时间自动退出?(详细分析)
问题描述
在一个项目中,有一个单独的java程序,它使用了第三方类库,而且是必须使用的那种,但是这个第三方类库有个致命的问题:它如同一头永远处于饥饿状态的野兽,它会不断的吃掉内存,最终导致“java.lang.OutOfMemoryError: Java heap space”异常。
?
?
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() 方法执行完之后程序自动退出来,否则进程就会一直等待所有的非守护线程退出。下面是关于守护线程的一些描述信息。
// 第一个线程 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 看一下。下面是去掉第三方类库的程序执行线程状态信息。
?
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 属性,不是守护线程。
?
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 的人干什么呢?!
?
?
?
?
?
1 楼 koujun 2012-06-11 用linux的cron来 start/stop程序 会不会更好一点?