悲劇です。このマルチスレッドプログラムはなぜ指定の時間に自動的に終了できないですか?詳細に分析する)


問題の説明
一つのプロジェクトの中で、単独のjavaプログラムがあります。第三者のクラスライブラリを使って、しかも必ず使わなければならないです。しかし、この第三者のクラスには致命的な問題があります。これは永遠に飢餓状態にある野獣のように、メモリを食べ続けます。最終的には「java.lang.OutOfMemoryErr:Java heap space」の異常を招きます。
 
 
ロゴマーク
12:00:02.597 ERROR Thread-1 emay.sms.relay.EmaySDKClient-run
java.lang.OutOfMemoryError:Java heap space
 
 
異常がありましたが、脱退しません。発見された時だけ、このプロセスを殺すために人工的にこれに行きます。このような状況は何度も繰り返し発生し、一部の悪影響をもたらしました。
 
役に立たない解決策
定期的に再起動してもらえますか?これはほとんど最後の解決方法です。システムの未知の要素が多すぎるからです。メインメソッドの末尾に下記のコードをつけて、「毎日0時にプログラムが自動的に終了する」という意味です。システムにはタスク管理プログラムがありますので、まだプロセスがあるかどうかを確認します。終了したら再起動します。)
 
 
            while (true) {
                Thread.sleep(3600 * 1000);
                java.util.Date now = new java.util.Date();
                if (now.getHours() == 0) {
                    break;
                }
            }
 
 
これは問題ないように見えるので、コンパイルして運転し直します。しかし、翌日の0時の日記では再起動の記録が発見されませんでした。つまり、予想通りに0時に終了しませんでした。
 
管理の改善
javaでは、スレッドにdaemen属性があります。プログラムのメインライン以外のスレッドをdaemen属性に設定するべきです。これで、main()メソッドが実行された後、プログラムが自動的にキャンセルされることを保証できます。そうでなければ、プロセスは常に非ガードスレッドの終了を待っています。以下は守護スレッドについての説明です。
http://blog.csdn.net/lanniao1/article/details/1831626 書き記す
デーモン
Javaには二つのThreadがあります。「守護スレッド」と「ユーザスレッド」があります。守護スレッドは「バックグラウンドで汎用的なサポートを提供する」スレッドであり、プログラム本体ではない。文面では保護スレッドは仮想マシンによって内部で作成されたものと理解しやすく、ユーザースレッドは自分で作成したものとなります。実際にはそうではなく、どのスレッドも「守護スレッド」または「ユーザスレッド」とすることができます。彼らはほとんどの点で同じです。唯一の違いは仮想マシンがいつ出発するかを判断することです。
ユーザスレッド:Java仮想マシンは、そのすべてのユーザスレッド(非ガードスレッド)が離れた後、自動的に離れます。
守护スレッド:守护スレッドはユーザースレッドにサービスを提供するもので、他のユーザースレッドが稼働していないと、サービス対象がなくなり、継続する理由がない。
  
プログラムがデーモンスレッドのみの場合、このプログラムは実行を終了します。 
Thread.set Daemenメソッドは、スレッドのDaemenモード、trueはDaemenモード、falseはUserモードを設定するのに便利です。set Daemenメソッドはスレッド起動前に起動しなければならず、スレッドが実行中に起動すると異常が発生します。isDaemen法はこのスレッドが守護スレッドであるかどうかをテストします。ちなみに、他のスレッドが守護スレッド内に生成されると、これらの新たに生成されたスレッドはDaemen属性を設定せず、すべてが守護スレッドとなり、ユーザスレッドも同じである。
例えば、私達が熟知しているJavaゴミ回収スレッドは典型的な守護スレッドです。私達のプログラムにはもう何も運行中のThreadがないと、プログラムにごみが発生しなくなり、ゴミ回収機もすることができなくなります。だから、ゴミ回収スレッドがJava仮想マシンに残されているスレッドである時、Java仮想機会は自動的に離れます。
 
このプログラムには2つのスレッドがあり、1つのスレッドは常に第三者クラスライブラリを呼び出す方法であり、1つのスレッドはネットワーククライアントであり、この2つのスレッドはすべてdaemon属性に設定されている。これらはそれぞれRelayとCientで、Thread類のサブクラスです。このようにしてからメールの方法の内容は大体以下の通りです。
 
             //      
             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時に退出していませんでした。なぜですか?
 
このプログラムはいったいどれぐらいスレッドが立っていますか?
おそらく、このサードパーティクラスでは他のスレッドが起動しているかもしれません。そうでないと、set Daemenの精神とは違っています。どのスレッドが起動しているかを確認してください。JDKでは、jstackは現在のスレッドの動作状況とスレッドの現在の状態を確認できます。使い方はjstack pidです。pidはプロセスIDであり、jpsやpsやtopなどのツールで入手できる。
jstackで見てください。以下は第三者クラスのライブラリを削除するプログラム実行スレッドの状態情報です。
 
jstackは書いています
[root@web186emay_sms_レイラ.
jstack 8082
2012-06-11 13:35:01
Full thread dump Java HotSpot(TM)Cient VM(16.3-b 01 mixed mode、shring):
「Attach Listener」daemon pro=10 tid=0 x 081 db 400 nid=0 x 562 e runnable[0 x 0000]
java.lang.Thread.State:RUNNINABLE
「Thread-1」daemen pro=10 tid=0 x 081 d 5400 nid=0 x 1 f 9 d runnable[0 xb 41ca000]
java.lang.Thread.State:RUNNINABLE
at java.net.SocketInputStream.socketRead 0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at smj.client.Smj Client.recv(Smj Client.java:96)
at smj.client.Smj Client.loop(Smj Client.java:139)
at smj.client.Smj Client.run(Smj Client.java:124)
at java.lang.Thread.run(Thread.java:619)
「Low Memory Detector」daemon pro=10 tid=0 x 08000 nid=0 x 1 f 99 runnable[0 x 0000]
java.lang.Thread.State:RUNNINABLE
「ComplerThread 0」daemon pro=10 tid=0 x 0803 nid=0 x 1 f 98 waiting on condition[0 x 0000]
java.lang.Thread.State:RUNNINABLE
「Signal Displatch」daemen pro=10 tid=0 x 0801400 nid=0 x 1 f 97 runnable[0 x 0000]
java.lang.Thread.State:RUNNINABLE
「Finalizer」daemon pro=10 tid=0 x 0807 e 400 nid=0 x 1 f 96 in Object.wait()[0 xb 48 b 4000]
java.lang.Thread.State:WAITING(on object monitor)
at java.lang.Object.wait(Native Method)
-waiting on<0 x 87040 b 00>(a java.lang.ref.ReferenceQue$Lock)
at java.lang.ref.ReferenceQue.remove(ReferenceQue.java:118)
-locked<0 x 87040 b 00>(a java.lang.ref.ReferenceQue$Lock)
at java.lang.ref.ReferenceQue.remove(ReferenceQue.java:134)
at java.lang.ref.Finalizer$Finalizer Thread.run(Finalizer.java:159)
「Reference Handler」daemen pro=10 tid=0 x 0807 cd=0 x 1 f 95 in Object.wait()[0 xb 4905000]
java.lang.Thread.State:WAITING(on object monitor)
at java.lang.Object.wait(Native Method)
-waiting on<0 x 87040 a 08>(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<0 x 87040 a 08>(a java.lang.ref.Reference$Lock)
「main」pro=10 tid=0 x 08400 nid=0 x 1 f 93 waiting on condition[0 xb 6 b 96000]
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”pro=10 tid=0 x 0807 b 400 nid=0 x 1 f 94 runnable
“VM Periodic Task Thread”pro=10 tid=0 x 0808400 nid=0 x 1 f 9 a waiting on condition
JNI global references:994
[root@web186emay_sms_レイラ.
 
 
以下は、サードパーティライブラリのスレッドを呼び出した後に追加されたスレッドです。この中には2つのスレッドがあります。デーモン属性ではなく、守護スレッドです。
 
jstackは書いています
「Thread-1」daemen pro=10 tid=0 x 0822 c 00 nid=0 x 1 eed waiting on condition[0 xb 3 fe 3000]
java.lang.Thread.State:TIMED_WAITING(sleeping)
at java.lang.Thread.sleep(Native Method)
at emay.sms.relay.EmaySDKClient.run(EmaySDKCliennt.java:405)
「MonitontThread」pro=10 tid=0 x 081 e 3400 nid=0 x 1 eec in Object.wait()[0 xb 4136000]
java.lang.Thread.State:TIMED_WAITING(on object monitor)
at java.lang.Object.wait(Native Method)
-waiting on<0 x 89 bf 61 b 0>(a java.util.TaskQue)
at java.util.TimerThread.manLoop(Timer.java:509)
-locked<0 x 89 bf 61 b 0>(a java.util.TaskQue)
at java.util.TimerThread.run(Timer.java:462)
「Thread-2」pro=10 tid=0 x 081 e 2 c 00 nid=0 x 1 eeb waiting on condition[0 xb 4187000]
java.lang.Thread.State:TIMED_WAITING(sleeping)
at java.lang.Thread.sleep(Native Method)
at cn.emay.sdk.co.mmunication.socket.Receive Thread.run(Receive Thread.java:53)
 
 
つまり、この2つの非ガードスレッドが存在するため、main()メソッドが実行された後、プログラムは自動的に終了しません。この2つのスレッドが終わるのをじっと待っています。
 
最終的な解決
もちろん、問題はいつも解決できます。それは明示的に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をやっている人は何をしますか?