Tomcatで動くJavaのWebアプリの動作が重くなって困ってます


Web/AP機とDB機の2台構成で、SAStrutsで作ったWebアプリを動かしてます。CentOS,Apache,Tomcat,MySQLです。

起動直後はいい感じですが、使ってるうちに重くなります。重くなると、ごく簡単なページですら表示に数分かかります。

重くなるきっかけは、アプリ内の検索機能で結果が大量(100万件以上)になる検索をされたときです。そのリクエスト自体に時間がかかるのは仕方ないとして、その後の他の関係ないリクエストも含めて全体が重くなってしまいます。その都度Tomcat再起動をすると軽快に戻りますが、現象の再現性は高く、何度でも発生します。

重くなる検索さえなければ、2~3日でも軽快に動作し続けます。またDB管理用にWeb/AP機にphpMyAdminも入れてますが、これはTomcatが重い時でもサクサクです。

現状では、どこが悪いか調べる方法すらわかりません。VisualVMを用意しましたが、値を見てもよくわかりませんでした。。どういう問題が考えられ、どういう調査をすればいいでしょうか。「ここを調べてみて」を教えていたたければ、調べて結果をここに追記します。

よろしくお願いいたします。

※仕様など詳細はコメント欄に書きます。

回答の条件
  • 1人10回まで
  • 登録:
  • 終了:2011/04/21 02:00:03
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:a-kuma3 No.6

回答回数4964ベストアンサー獲得回数2150

ポイント22pt

確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。

これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。

そうなってると思いますよ。

アプリ側に問題があるかどうかは、提示された内容だけだと、ちょっと分からないですけどね。


S2JDBC の iterate で抜いたデータを、アプリ側で List や Map に溜めていると思うのですが、

それをためないようにして試してみたらどうでしょう?

検索結果が常に0件になる、という動作になると思いますが、iterate の側に問題があるか、

アプリ側に問題があるかどうかの、切り分けができるんじゃないでしょうか。


もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。

適切かどうかは、チューニングをした結果として出てきます。

まずは、デフォルトで動作させてみて、性能値を見ながら、適切な値を探る、というふうに決めていきます。

ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。

Perm を大きくしなければいけない場合、というのは、クラスが多い時です。

heap に余裕が無いのに、OutOfMemoryError が出るので、足りない時には、すぐ分かります。


New 領域を増やす、というか、-XX:NewSize で指定する必要があるケースというのは、あまり無いと思います。

デフォルトでは、New と Old のサイズの比率は、動的に変わりますから。

少なくとも、2GB のヒープに対して、256MB の New では小さすぎます。

id:nacookan

前の回答にも書きましたが、検索結果のうち、先頭の数レコードのエンティティと、全件のIDの値をそれぞれListに持っています。

これらをListにためないで、つまり空回りさせてみるというテスト、いい調査だと思います。さっそくあとでやってみます。できれば、フレームワークやライブラリではなく、自分のコードに問題があったほうが修正できるので、それを期待します。つまり、空回りさせたら、ヒープは全然増えなかった(増えたとしても、上限に達したときにGCが走ってストンと落ちて、、というのが理想ですね。

それから、起動パラメータ(JVMのチューニング)について、大変参考になりました。見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。

2011/04/17 22:02:06

その他の回答7件)

id:adachi_c No.1

回答回数13ベストアンサー獲得回数0

ポイント16pt

psコマンドでTomcatプロセスのメモリ使用量を見てください。

軽い検索をやった場合と、思い検索をやったときの違いを見てください。

100万件検索した結果、検索結果をどこかのメモリ領域にいれ、解放し忘れているのではないかと思われます。

で、新しい処理をやろうとして、メモリ不足で、ページングで時間がかかっているのではないでしょうか。

また、100万件の検索をするにしても、SQLでselect * などとするのではなく、カラムを指定する。

検索対象のテーブルをインデックス化する、といったことが必要だと思われます。

id:nacookan

ありがとうございます。

psコマンドで見るメモリ不足というのは、OSのメモリのことかと思います。さらにJavaVMにもメモリを割り当てるわけですが、正直、その適切な値の求め方もわからない状況で、とりあえず多めの2GBをヒープサイズ(-Xmx)に割り当ててる、という感じです、現状は。

いただいた回答へ返信しようと、psコマンドでチェックしてみました。たしかに重い検索のときはメモリ使用量が多くなっています。そして、割り当てたメモリを超えてしまう場合に、重い状況になるようです。

プログラム的には、S2JDBCを使って検索をしています。検索結果は一度に取得せず、iterateの仕組みを使っています。

参考 : http://s2container.seasar.org/2.4/ja/s2jdbc_manager_auto.html#イテレーションによる検索

これを使えば、メモリには常に1件分のデータしかため込まないと信じているのですが、それが違うのでしょうか。。。

また、同様に、カラムを指定するのも、S2JDBCのSQL自動生成を使っていると難しそうです。もちろんselectBySqlを使えば可能かと思いますが、検索処理をほとんど作り直しになってしまうため、最後の手段にしたいです。。。

2011/04/17 16:58:09
id:asuka645 No.2

回答回数856ベストアンサー獲得回数97

ポイント16pt

VisualVMでCPUプロファイルをあたってみて下さい。

おそらくCPU時間を極端に消費しているスレッドがあるはずで、そこに何らかの原因が潜んでいます。

id:nacookan

ありがとうございます。

重い状況が起きているときにVisualVMでやってみるということでしょうか。今度試してみます。

ところで、CPUの話をすると、重い状況のときはCPU的にはまだまだ余裕があります。

8コアなので最大800%なのですが、100%だけ消費している状況です。

これは、複数のCPUに振り分けできないような処理(Javaなのでsyncronizeされてる処理?)が

1つのCPUを限界まで使っていて、他のCPUはそれが終わるまでヒマ、ということでしょうか。

自分のコードにはsyncronizeはどこにも書いてないので、フレームワーク内ですかね。

2011/04/17 17:01:58
id:pretaroe No.3

回答回数531ベストアンサー獲得回数75

ポイント8pt

DBの接続あたりを見直してみては?

コネクションプールは使っておられるでしょうか?

あとはTomatのログとか

http://mism.blog13.fc2.com/blog-entry-291.html

id:nacookan

ありがとうございます。

コネクションプールは使っています。10接続だったと思います。

重い状況のときは、show processlistをしても何も出ないことからもわかるように、DBには何の負荷もかかってないようです。

Tomcatのログは、catalina.outのログローテーションは設定しています。

確かにそれでも1ファイルの量は多いですが、重い検索をしたときに全体が重くなって、再起動したら軽くなるという状況の理由としては弱いように感じます。

2011/04/17 17:04:30
id:a-kuma3 No.4

回答回数4964ベストアンサー獲得回数2150

ポイント23pt

ぱっと見た感じ、GC で CPU を食いまくってる感じ。

Tomcat のプロセスが CPU を食ってて、物理メモリにも余裕があって、swap はほとんど使ってないのだから、

ページングだとか、java VM 以外のプロセスやスレッドが、どうとか、そんなことは考えなくて良いと思う。


まずは、-verbose:gc を指定するなどして、重たくなったときの GC の挙動を確認した方が良いと思う。

大量検索をしたときの検索結果を、セションに保持しててヒープの空きが無くなってるとか、じゃないかなあ。


後、アプリの作り次第だけど、java の起動パラメータは見直した方が良いと思う。

一番気になるのは、Perm 領域のサイズがでかすぎること。

Action クラス以外に、いくつクラスがあるか知らないけど、デフォルトか、せいぜい 128M もあれば十分。


New領域のサイズ指定も、必要なんかな?

チューニングした結果で決めたサイズじゃないのであれば、指定を無くして、割合は VM に任せるべき。

id:nacookan

ありがとうございます。

考えなくていいところを教えていただくと心強いです。

いただいた情報から、さらに調査をしてみました。確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。

これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。

検索結果をセッションに保持するようなコードは書いていません。ヒープの上限に達する前に検索が無事に終わったケースでは、検索が終わった後にVisualVMからGCを実行させると、ストンとグラフが下まで落ちます。検索処理の実行中や、重い状況になってしまったあとにVisualVMからGCを実行すると、何も変化がありません。

つまり、検索処理でものすごいメモリを消費しているということでしょうか。前の方の回答にも書きましたが、S2JDBCのiterateの仕組みを使っているので、メモリは大量に消費しないはずなのですが。。。その考え方が間違っているのか、コードが悪いんですかね。。。

もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。

ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。

2011/04/17 17:13:25
id:asuka645 No.5

回答回数856ベストアンサー獲得回数97

ポイント15pt

イテレーションによる検索」にあるようなコーディングをしていればメモリ消費は抑制されますが、検索結果を逐次変数や配列に格納しているとしたら、メモリ効率が悪くなる可能性があります。

ご確認下さい。

id:nacookan

ありがとうございます。

検索結果は、その一部のレコード(画面表示するための先頭のn件)だけはエンティティをリストに保持し、一部のカラム(IDに相当する値)だけは、あとで今の検索結果に対して一括処理をさせるために、全件の分を別途リストに保存しています。ですので、メモリを食うとしたら後者の全件のIDを持っているリストだと思われます。

ですが、これは単純なList<String>で、IDひとつはせいぜい10桁なので、たとえば100万件だとしても、30MBくらいですよね(1文字3バイトx10文字x100万件)。2GBのヒープがいっぱいになるとは考えにくいと思ってます。違いますでしょうか。

ちなみにJavaVM内のメモリの利用割合って何かで調査できるもんなのでしょうか。

2011/04/17 21:50:31
id:a-kuma3 No.6

回答回数4964ベストアンサー獲得回数2150ここでベストアンサー

ポイント22pt

確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。

これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。

そうなってると思いますよ。

アプリ側に問題があるかどうかは、提示された内容だけだと、ちょっと分からないですけどね。


S2JDBC の iterate で抜いたデータを、アプリ側で List や Map に溜めていると思うのですが、

それをためないようにして試してみたらどうでしょう?

検索結果が常に0件になる、という動作になると思いますが、iterate の側に問題があるか、

アプリ側に問題があるかどうかの、切り分けができるんじゃないでしょうか。


もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。

適切かどうかは、チューニングをした結果として出てきます。

まずは、デフォルトで動作させてみて、性能値を見ながら、適切な値を探る、というふうに決めていきます。

ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。

Perm を大きくしなければいけない場合、というのは、クラスが多い時です。

heap に余裕が無いのに、OutOfMemoryError が出るので、足りない時には、すぐ分かります。


New 領域を増やす、というか、-XX:NewSize で指定する必要があるケースというのは、あまり無いと思います。

デフォルトでは、New と Old のサイズの比率は、動的に変わりますから。

少なくとも、2GB のヒープに対して、256MB の New では小さすぎます。

id:nacookan

前の回答にも書きましたが、検索結果のうち、先頭の数レコードのエンティティと、全件のIDの値をそれぞれListに持っています。

これらをListにためないで、つまり空回りさせてみるというテスト、いい調査だと思います。さっそくあとでやってみます。できれば、フレームワークやライブラリではなく、自分のコードに問題があったほうが修正できるので、それを期待します。つまり、空回りさせたら、ヒープは全然増えなかった(増えたとしても、上限に達したときにGCが走ってストンと落ちて、、というのが理想ですね。

それから、起動パラメータ(JVMのチューニング)について、大変参考になりました。見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。

2011/04/17 22:02:06
id:asuka645 No.7

回答回数856ベストアンサー獲得回数97

ポイント15pt

ですが、これは単純なList<String>で、IDひとつはせいぜい10桁なので、たとえば100万件だとしても、30MBくらいですよね(1文字3バイトx10文字x100万件)。2GBのヒープがいっぱいになるとは考えにくいと思ってます。違いますでしょうか。

C言語でコーディングしているわけではないので、そう単純に計算できるものではありません。

そのListに対して頻繁にGCを繰り返してCPUリソースを食い潰している可能性もあります。


ちなみにJavaVM内のメモリの利用割合って何かで調査できるもんなのでしょうか。

MemoryMXBeanクラスを使ってチェックしてみて下さい。

id:nacookan

なるほど、じゃあリストあきらめてStringBuilderで文字列結合だけでやるとか、ヘタしたらそれくらいのことが必要なのかも知れないですね。全件のIDを持たなきゃいけないのは要求仕様上どうしてもなので。。。

MemoryMXBeanクラスというのは、見たところVisualVMで見れる情報しか取れそうにないのですが、もっと詳細に取れるのでしょうか?例えば、どの型が何MBとか、そういう割合が見えればだいぶヒントになると思うのですが。。。

2011/04/18 00:50:37
id:a-kuma3 No.8

回答回数4964ベストアンサー獲得回数2150

ポイント15pt

見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。

ある程度、分かっているようなので、↓レベルは、既にクリアしてますかね。

http://www.itmedia.co.jp/enterprise/articles/0905/27/news002.htm...


性能チューニングって、プログラムのコードだけで解決できないことも多いので、OS やらミドルウェアやらいろんなことを知って無いと、難しいですよね。


「チューニングガイド」とか、「tuning guide」とかでググると、それなりの情報が手に入りますが、具体的で、問題解決の即決するようなドキュメントには、なかなかお目にかかれません。

ある程度、大きな組織で仕事をやっているのであれば、開発寄りの部署で、もう少し具体的なチューニングガイドや性能改善の事例を持ってたりする(普通は、社外秘)ので、そちらに当たってみるのも手かと。


チューニングのための持ち駒を増やしておいて、後は経験を積むしかないのかなあ、と。

そういう意味では、「今」が良い機会なんじゃないかと思います。


全件のIDを持たなきゃいけないのは要求仕様上どうしてもなので。。。

どういう使い方をするのか分かりませんが、ID のリストをセションなど、寿命が長いインスタンスとして作る必要が無ければ、外部に吐き出しておく手はあります。

ファイルだと、多少性能は落ちるかもしれませんが、ある程度の件数以上だったら、というようなことをすれば、通常の検索による処理時間を大きく変えることにはならないと思います。

  • id:nacookan
    システムの詳細な仕様です

    ■Web/APサーバー
    -CPU8コア/メモリ8GB
    -CentOS5.5 64bit
    -Apache2.2.3+mod_proxy_ajp
    -Tomcat5.5.31+jsvc
    -Java1.6.0_24

    ■DBサーバー
    -CPU8コア/メモリ8GB
    -CentOS5.5 64bit
    -MySQL5.1.53

    ■アプリケーション
    -SAStruts 1.0.4-SP6
    -Actionクラス数は約220
    -テーブル数は約140

    ■Tomcatの起動
    -Xms1024m -Xmx2048m
    -XX:NewSize=256m -XX:MaxNewSize=256m
    -XX:PermSize=1024m -XX:MaxPermSize=1024m
  • id:nacookan
    以下は重いときのWeb/AP機のtopの一部です。このときDB機はshow processlistをやっても何も出ません。

    top - 16:23:02 up 43 days, 2:11, 6 users, load average: 1.00, 1.09, 0.79
    Tasks: 231 total, 1 running, 229 sleeping, 0 stopped, 1 zombie
    Cpu(s): 4.2%us, 0.5%sy, 0.0%ni, 94.2%id, 1.0%wa, 0.0%hi, 0.1%si, 0.0%st
    Mem: 8177084k total, 7983808k used, 193276k free, 226588k buffers
    Swap: 2096472k total, 160k used, 2096312k free, 3921268k cached

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    25206 tomcat 18 0 2536m 2.1g 10m S 100.1 27.3 18:34.62 jsvc

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません