Java プロセスが CPU 負荷かけているぞ!いますぐ確認するべき手順を紹介します!

はじめに

とある日、本番運用バッチサーバーで CPU が100%になり、アラートが発生しました。

本番環境で CPU 100% なんて、もうあせりますよね><

起動中のバッチ処理を確認しても、数秒で終わる軽い処理のみで重い処理は動いていませんでした。

うーん、起動中のバッチ処理が要因ではないとすると、何のプロセスが要因で CPU 負荷をかけているのだろう?という状態でした。

ということで、この時に確認した「なんのプロセスが CPU に負荷をかけているか」の手順を紹介します!

 TOP コマンドで CPU 負荷かけているプロセスを確認しよう!

まず、バッチサーバーにログインし、top コマンドにてサーバー上で動作しているプロセスを確認します。

デフォルトだと、COMMAND 部分に java や php しか表示されないので、「c オプション」を指定してコマンド詳細が見れるようにします。

$ top -c
Tasks: 125 total,   7 running,  86 sleeping,   0 stopped,   0 zombie
Cpu(s): 95.8%us,  9.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.2%si,  0.0%st
Mem:   4040136k total,  2172772k used,  1867364k free,     9508k buffers
Swap:        0k total,        0k used,        0k free,    60576k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                        
 2521 apache    20   0 3627m 816m  876 S 127.0 20.7   4060:43 /etc/alternatives/java -Djava.awt.headless=true -DJENKINS_HOME=/var/lib/jenkins -jar /usr/lib/ 
29125 apache    20   0  602m 230m  17m R 13.3  5.8   0:01.48 /usr/bin/php
29263 apache    20   0  495m 125m  17m R 13.0  3.2   0:00.63 /usr/bin/php

一番上の PID: 2521 の /etc/alternatives/java プロセスが、%CPU: 127.0となっています。

この Java プロセスが、CPU 負荷かけているのが一目瞭然ですね><

コマンド内容みると、-DJENKINS_HOME=/var/lib/jenkins とあるので、どうやら Jenkins のプロセスのようです。

うーん、Jenkins で実行中のジョブ確認しても、重い処理は実行されていない状態でした。Jenkins のなんのプロセスが負荷かけているのだろう???

もうちょっと詳しく見てみないとわかりませんね。

ということで、負荷をかけている jenkins の該当 Java プロセスのスレッド情報を確認します。

CPU 負荷かけているJavaプロセスのスレッド情報を確認しよう!

  • CPU負荷かけているスレッドIDを確認

topコマンドにて 「H オプション」を指定し、上記で確認した CPU 負荷かけている該当 Javaプロセス (PID: 2521) に関するスレッドIDを確認します。

$ top -n 1 -H -p 2521
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND  
13422 apache    20   0 3627m 932m    0 S 51.7 23.6  35:14.04 /etc/alternatives/java -Dcom.sun.akuma.Daemon=daemonized -Djava.awt.headless=true -DJENKINS
13423 apache    20   0 3627m 932m    0 S 47.7 23.6  69:37.58 /etc/alternatives/java -Dcom.sun.akuma.Daemon=daemonized -Djava.awt.headless=true -DJENKINS
13407 apache    20   0 3627m 932m    0 S 45.7 23.6  35:01.37 /etc/alternatives/java -Dcom.sun.akuma.Daemon=daemonized -Djava.awt.headless=true -DJENKINS

上記結果のようにスレッドID が一覧として出力されます。

「%CPU」部分見て、CPU 浪費しているスレッドIDをピックアップします。

 

  • スレッドダンプ確認用に、スレッドIDを16進数に変換

スレッドダンプ出力した結果では、スレッドIDが16進数で出力されるため、スレッドID (上記だと13422/13423/13407) を16進数に変換したものを用意してメモしておきます。

$ printf %x\\n 13422
346e
$ printf %x\\n 13423
346f
$ printf %x\\n 13407
345f

これで、スレッドダンプ確認の準備ができました。

それでは、スレッドダンプ出力します。

 

  • Java プロセスのスレッドダンプ出力

jstack コマンドにて、指定されたPIDのJava プロセスに関するスレッドのスタックトレースを出力できます。

大元のJavaプロセスのPID: 2521 を指定して jstack コマンドを実行します。

$ jstack 2521 > thread_dump.txt

 

  • 出力したダンプファイルで、CPU負荷かけているスレッドを確認

Javaプロセスに関するスレッド情報が全て出力されるので、負荷をかけている該当スレッドIDを検索します。

上記結果の「346e」を例にして確認します。

16進数に変換したCPU負荷かけているスレッドIDに、先頭に「0x」を付与して「0x346e」で検索します。

上記で出力したダンプファイルを開いて、「0x346e」を検索します。

すると、nid=0x346e の部分がヒットし、下記のようなスタックトレースが確認できます。

"Handling GET /view/job/{該当ジョブ名}/descriptorByName/hudson.triggers.TimerTrigger/checkSpec from xxx.xxx.xxx.xxx : 
RequestHandlerThread[#15]" #54 daemon prio=5 os_prio=0 tid=0x00007fe588006110 
nid=0x346e runnable [0x00007fe575c6e000]
   java.lang.Thread.State: RUNNABLE
        at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
        at java.util.Calendar.setTimeInMillis(Calendar.java:1804)
        at java.util.GregorianCalendar.add(GregorianCalendar.java:1048)
        at hudson.scheduler.CronTab$CalendarField.addTo(CronTab.java:203)
        at hudson.scheduler.CronTab.floor(CronTab.java:423)
        at hudson.scheduler.CronTabList.previous(CronTabList.java:134)
        at hudson.triggers.TimerTrigger$DescriptorImpl.updateValidationsForNextRun(TimerTrigger.java:109)
        at hudson.triggers.TimerTrigger$DescriptorImpl.doCheckSpec(TimerTrigger.java:93)
        at java.lang.invoke.LambdaForm$DMH/566427335.invokeVirtual_L3_L(LambdaForm$DMH)
        at java.lang.invoke.LambdaForm$BMH/1291054401.reinvoke(LambdaForm$BMH)
        at java.lang.invoke.LambdaForm$MH/516027262.invoker(LambdaForm$MH)
        at java.lang.invoke.LambdaForm$MH/986430670.invokeExact_MT(LambdaForm$MH)
        at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
        at org.kohsuke.stapler.Function$MethodFunction.invoke(Function.java:343)
        at org.kohsuke.stapler.Function.bindAndInvoke(Function.java:184)
        at org.kohsuke.stapler.Function.bindAndInvokeAndServeResponse(Function.java:117)
        at org.kohsuke.stapler.MetaClass$1.doDispatch(MetaClass.java:129)
        at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)
        at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:715)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:845)
        at org.kohsuke.stapler.MetaClass$5.doDispatch(MetaClass.java:248)
        at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)
        at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:715)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:845)
        at org.kohsuke.stapler.MetaClass$5.doDispatch(MetaClass.java:248)
        at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)
        at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:715)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:845)
        at org.kohsuke.stapler.MetaClass$5.doDispatch(MetaClass.java:248)
        at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:58)
        at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:715)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:845)
        at org.kohsuke.stapler.Stapler.invoke(Stapler.java:649)
        at org.kohsuke.stapler.Stapler.service(Stapler.java:238)

これが CPU 負荷かけているスレッドのスタックトレースですね!

 

  • スタックトレースから原因調査

スタックトレース確認すると /view/job/{該当ジョブ名}/ と Jenkins の該当ジョブまで確認できます。

しかし、このジョブは起動中ではありませんでした。

むむむ。。。起動中でないのになんでプロセスが残っているのか、しかもこれがかなり負荷をかけているとは。。。

スタックトレースを見ると、’Calendar.setTimeInMillis‘ やら ‘GregorianCalendar.add‘ とあるので、カレンダーが関連していそうです。

Jenkins でカレンダーというと、つまり日付など設定するところ、、、

は!!!

ジョブの「起動スケジュール」だ!!!

ということで、Jenkinsの該当ジョブの起動スケジュールの設定が関係していそうですね。

根本原因は Jenkins のバグだった

  • 根本原因が判明

上記スレッド情報から調査した結果、どうやら Jenkins のバグ のようでした。

該当ジョブの起動スケジュールがこのバグを踏んでしまっていました。

30 22 28-31 * * これは有効なCron構文ですが、Jenkins Time Trigger Classは、ジョブの正確な次の実行がいつであるかを検証できませんでした。

とコメント蘭にあるので、起動スケジュールに月末指定などすると CPU 100% になるバグがあるようですね。日付が見つからず無限ループしているのですかね。これでCPU100%とは。。。><

  • 対応として

該当ジョブの起動スケジュールが原因ということで、スタックトレースに出ていた該当 Jenkins ジョブの起動スケジュールを削除し、Jenkins 再起動することで CPU 負荷も解消できました。ふーお疲れ様です。

おわりに

一例にはなりますが、Javaプロセスで負荷かけているスレッド情報を詳しく確認することで根本原因調査ができました。

起動スケジュールの設定だけで CPU 100% になるという Jenkins のバグだったとは、思いもよらない結果でしたね。


--------------------------
システム開発のご要望・ご相談はこちらから

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です