本内容について
JMX(Java Management eXtentions)は稼働中のJavaに対する管理インターフェースを提供します。
今回はAWS EC2で稼働するLinuxサーバのTomcatでJMX設定を有効化し、Javaのヒープやスレッド数を取得、CloudWatchで可視化する内容です。
プログラム概要
※Javaは1.2で学習が止まっているのと、そこからほぼ書いていなかったため、記述方法などについてはご容赦ください。
サーバ内へ常駐し、所定の間隔で情報を通知する形にしています。
ループ内でJMXの参照を取得、各メトリックを取得し、一括でCloudWatchに通知する流れです。
取得する対象のMemoryPoolMxBeanや、スレッドの名称などは、後述のCommand-line JMX Client等で確認し、環境に合った対象にしておく必要があります。(JavaのGC方式によって取得対象のMemoryPoolMxBeanの名称などが異なります。)
- 毎回 getMBeanServerConnection() を呼んでいるのは、Tomcatサーバの一時的な停止が発生した場合にも、起動後に取得できるようにしています。
- 本プログラムはログを出力していないので、必要であれば追加してください。(Exceptionを黙殺しているのはそのため・・・)
参考
JMXで具体的に何が取得できるのかは、「Command-line JMX Client」のお世話になった。
コード
code.java
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.ThreadMXBean;
import javax.management.ObjectName;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClientBuilder;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.amazonaws.services.cloudwatch.model.PutMetricDataResult;
import com.amazonaws.services.cloudwatch.model.StandardUnit;
/**
* リモートJVMからMemoryMBeanを取得する
* */
public class JMXReporter{
private static MBeanServerConnection remote = null;
public static void main(String[] args) throws Exception {
if (args.length != 4){
System.out.println("引数: ホスト名 取得対象tomcat service名称 ポート番号 間隔(秒)");
System.exit(1);
}
// JMX接続先ホスト名、ポート、接続間隔の取得・設定
String host = "localhost";
int port = Integer.parseInt(args[2]);
int iv = Integer.parseInt(args[3]);
final String serviceUrlStr = "service:jmx:rmi:///jndi/rmi://"
+ host + ":" + port + "/jmxrmi";
final JMXServiceURL target = new JMXServiceURL(serviceUrlStr);
// 無限ループ
while(true){
JMXConnector connector = null;
try {
// JMX接続のコネクション取得
// 対象の起動停止を考慮し、都度接続する。
connector = JMXConnectorFactory.connect(target);
remote = connector
.getMBeanServerConnection();
} catch (IOException e) {
// 接続エラー時はスキップ
System.err.println("failed to connect: " + target.toString());
Thread.sleep(iv * 1000);
continue;
}
// MemoryPoolMxBeanからの情報取得(GC方式変更時は対象名称の修正が必要)
List<MemoryPoolMXBean> beans = new ArrayList<MemoryPoolMXBean>();
beans.add(getMemoryPoolMxBean("Par Survivor Space"));
beans.add(getMemoryPoolMxBean("Par Eden Space"));
beans.add(getMemoryPoolMxBean("CMS Old Gen"));
beans.add(getMemoryPoolMxBean("Metaspace"));
beans.add(getMemoryPoolMxBean("Compressed Class Space"));
beans.add(getMemoryPoolMxBean("Code Cache"));
// CloudWatchのクライアント取得
final AmazonCloudWatch cw =
AmazonCloudWatchClientBuilder.defaultClient();
// CloudWatchのディメンションを作成
List<Dimension> dimList = new ArrayList<Dimension>();
dimList.add( new Dimension()
.withName("host")
.withValue(args[0]));
dimList.add( new Dimension()
.withName("tomcat instance")
.withValue(args[1]));
// MemoryPoolの各beanから値を取り出してListへ積んでおく
List<MetricDatum> datumList = new ArrayList<MetricDatum>();
for(MemoryPoolMXBean bean : beans) {
if(bean == null){continue;}
datumList.add( new MetricDatum()
.withMetricName(bean.getName())
.withUnit(StandardUnit.None)
.withValue(new Double(bean.getUsage().getUsed()))
.withDimensions(dimList));
}
// スレッド数データも追加
ObjectName ajpThreadName = new ObjectName(
args[1] + ":name=\"ajp-nio-8009\",type=ThreadPool");
Double busyth = 0.0;
try {
busyth = new Double( remote.getAttribute(
ajpThreadName, "currentThreadsBusy").toString());
}
catch(Exception e) {
// 黙殺
}
datumList.add( new MetricDatum()
.withMetricName("currentThreadsBusy")
.withUnit(StandardUnit.None)
.withValue( busyth )
.withDimensions(dimList));
// 積んだデータを一括でPut
PutMetricDataRequest request = new PutMetricDataRequest()
.withNamespace("CUSTOM JMX")
.withMetricData( datumList );
try {
PutMetricDataResult response = cw.putMetricData(request);
}
catch( Exception e) {
// 黙殺
}
if(connector != null){connector.close();}
Thread.sleep(iv * 1000);
}
}
/**
* MxBean名からMemoryPoolMXBeanインスタンスを取得します。
* @param beanName MxBean名
* */
private static MemoryPoolMXBean getMemoryPoolMxBean(String beanName) throws IOException{
MemoryPoolMXBean mpmxbean
= ManagementFactory.newPlatformMXBeanProxy(
remote,
ManagementFactory.MEMORY_POOL_MXBEAN_DOMAIN_TYPE + ",name=" + beanName,
MemoryPoolMXBean.class);
return mpmxbean;
}
}