日々精進

新しく学んだことを書き留めていきます

Javaアプリのメモリリーク調査方法

以下の二つのやり方がある。

  • メモリダンプを取る

以下のコマンドでダンプを取る。

jmap -dump:format=b,file=heap.bin <PID>

Eclipse Memory Analyzerでダンプを見る。

参考:

hkawabata.github.io

  • Java Flight Recorderでログを取る

こっちの方がより詳細な情報がとれる。Oracle Java8にはFlight Recorderが入っている。OpenJDKにも11ぐらいから入っているらしい。

docs.oracle.com

qiita.com

以下のコマンドでJavaアプリを起動すると起動から30分後に.jfrファイルが出力される。

/opt/java/jdk1.8.0_221/bin/java -Xms1g -Xmx1g -Djava.net.preferIPv4Stack=true -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=name=on_startup,filename=./log/record.jfr,settings=./log/template.jfc,dumponexit=true,duration=30m -Dfile.encoding=UTF-8 -jar `ls /home/ec2-user/server/*.jar`

mission controlをダウンロードして.jfrを開くと時系列でメモリ使用量の変化が分かったり、どのオブジェクトがどのメソッドから作られているのかがわかったりする。

www.oracle.com

メモリのDocker Cache領域はメモリが足りなくなったらすぐ解放されるので増えても問題無い

本番環境のメトリクスを見ていたら、メモリのDocker Cache領域がどんどん増えているので焦ったがこの領域はIOのキャッシュのための領域でほかのプロセスがメモリを確保しようとしたらすぐに解放してくれるので増えても問題無いっぽい。 Docker Cacheが増えてもcat /proc/meminfo で表示したMemAvailableは減らない。

参考:

docs.docker.jp

Javaアプリの起動引数-Xmsで指定したメモリ量とJavaアプリが使用しているメモリ量が異なる。

よくあるプラクティスとして-Xmsと-Xmxの値を同じにしてJavaアプリ起動時に一括でメモリを確保させるというのがある。 それをやると、Javaアプリ起動時から常に一定量のメモリが使用中になると思っていたがそういう挙動にならなかったのでその原因を調査した。

factは以下。

  • -Xms6g -Xmx6g を起動引数に設定した
  • cat /proc/meminfoでOSのメモリ使用量を調べたところ6GB未満だった

調査した結果は以下。

-Xms, -Xmxの挙動はOSによって異なる。Linuxの場合vm.overcommit_memoryオプションの設定によっても異なる。 参考: stackoverflow.com

オーバーコミット有効の場合、mallocしても実メモリは確保されない(=つまり、cat /proc/meminfo のMemAvailableが減らない?) 参考: passingloop • Linux のオーバーコミットについて調べてみた

以下のHPに 「overcommit memory機能」が有効な場合、Linuxは、Javaヒープの各領域の最大値に相当する仮想メモリ資源を、Java VMの起動時に、Javaプロセスに対して予約します。 という記載がある。つまり、「予約」するだけで他のプロセスが使えなくなるわけではなさそう。 software.fujitsu.com

メモリには「UNMOVABLE」「RECLAIMABLE」「MOVABLE」「RESERVE」の4種類がある。上記の「予約する」というのはメモリにRESERVEマークを付けるということ? 参考: www.atmarkit.co.jp

-Xmsと-Xmxが指定するのはメモリ割り当てプールという領域。これ以外にPermanent世代領域というのもある 参考: software.fujitsu.com