Tomcatの使用メモリ量をCloudWatchで表示する

2015.02.05

渡辺です。

現在のEC2のデフォルト機能では、EC2インスタンスのメモリ使用量をCloudWatchで確認することはできません。 ですが、AWS CLIと簡単なスクリプトを使えば、Custom MetricsとしてCloudWatchに表示することができます。 ただし、一般的なLinuxのメモリ使用量であればシェルスクリプトで簡単に取得できるのですが、TomcatなどのJavaのアプリケーションサーバではもう一手間必要です。

Javaでのメモリ管理の仕組み

Java(JVM)はヒープ領域というメモリを仮想マシンの内部に確保し、ヒープ領域の中でインスタンスなどを割り当てる仕組みになっています。 したがって、OSから見たJavaのメモリ消費量を見ても、それはJVMが確保しているヒープ領域の使用メモリ量です。 内部的にどの程度メモリを消費しているかは解りません。

JavaMemory

JVMのヒープ領域がどの程度メモリを使っているかを知るためには、JVM内でAPIを利用して取得するなどの方法が必要となります。

JVMのメモリ消費量を内部から取得する

Javaのコードで実行中のJVMのメモリ消費を取得する方法は簡単です。 次のようなコードで利用メモリ量を取得できます。

long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("Used: " + (used/1024) + "[KB]");

後は、AWS SDKを利用して使用メモリを定期的にCLoudWatchに送信すれば良いでしょう。 しかし、この方法はアプリケーション自体の修正が必要です。

JVMのメモリ消費量を外部から取得する

JVMのメモリ消費量を外部から取得するにはなんらかの仕組みでJVMと通信を行うか、ファイルなどを経由して情報を取得しなければなりません。 この時、JavaではJMX(Java Management Extension)を利用して行うと、アプリケーションを修正する必要がないためオススメです。 JMXはJavaアプリケーション管理のフレームワークです。 JMXを利用すれば簡単にJavaアプリケーションから使用メモリを取得することが可能です。

JMXの有効化

JMXはデフォルトで有効になっていないため、Javaの起動オプションで有効にしなければなりません。 今回はTomcatを例に使用メモリを取得するため、/etc/tomcat8/tomcat8.confのJAVA_OPTSに設定を追加します。

JAVA_OPTS="-Dcom.sun.management.jmxremote
           -Dcom.sun.management.jmxremote.port=8999
           -Dcom.sun.management.jmxremote.ssl=false
           -Dcom.sun.management.jmxremote.authenticate=false"

ここではローカルマシンからメモリ消費量を取得する前提とし、jmxとの通信ポートを8999、sslと認証は無効としました。 外部インスタンスから通信を行う場合はセキュリティに考慮して設定を行ってください。

必要に応じて最大ヒープサイズなどのオプションもここで指定します。 オプションを追加したならば、Tomcatを再起動してください。

JMX経由で使用メモリ量を取得する

Javaで記述すれば簡単なコードで取得できます。

String hostname = "localhost:8999";
String serviceUrl = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", hostname);
JMXConnector conn = JMXConnectorFactory.connect(new JMXServiceURL(serviceUrl));
Object o = conn.getMBeanServerConnection().getAttribute(newObjectName("java.lang:type=Memory"), "HeapMemoryUsage");
CompositeData data = (CompositeData) o;
Long used = (Long) data.get("used");
System.out.println(used/1024);

これでTomcatが起動するインスタンス内部からTomcatのJVMで利用しているメモリ量(KB)を取得できます。

なお、JMXを利用するだけであれば、外部ライブラリなどを利用する必要はないため、適当なメインクラスのメインメソッドに貼り付けて、コンパイルすれば単体で実行出来きるでしょう。 AWS-SDKも利用してすべての処理をJavaで閉じることも可能ですが、ライブラリの管理などが面倒なので単体のクラスファイルのが良いでしょう(好みですが)。

CloudWatchにCustomMetricsとして登録する

後はJavaの処理をシェルスクリプトから実行し、結果をAWS SDK経由でCloudWatchに送信するだけです。 細かいエラー処理などは省くとこのようになります。 先ほどのJavaのコードはUseMem.javaのクラスのメインメソッドに記述し、javacでコンパイルしておいてください。

#!/bin/bash

usage=$(java UsedMem)
instanceId=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')

cw_opts="--namespace Classmethod/EC2"
cw_opts=${cw_opts}" --metric-name JavaDiskUsage"
cw_opts=${cw_opts}" --dimensions InstanceId=${instanceId}"
cw_opts=${cw_opts}" --unit Kilobytes"
cw_opts=${cw_opts}" --region ${region}"
cw_opts=${cw_opts}" --value ${usage}"

aws cloudwatch put-metric-data ${cw_opts}

後はcronなどで定期的に実行すればOKです。

CloudWatch_Management_Console

まとめ

Javaのメモリ利用量を外部から取得するにはJMXが簡単です。 JMXで取得したメモリ利用量をCloudWatchにCustomMetricsとして定期的に送信すれば、マネジメントコンソールでJavaのメモリ消費動向を簡単に確認するコトができます。