JmxでリモートJVMからMxBeanを取得するサンプル

追記 2014-03-30
取り敢えず作ってみた。
Jmxで取得したJavaVMメモリ使用状況をcsvに出力するWindowsサービス(中の人はJava)を書いた - ka-ka_xyzの日記



最近PHPの泥沼(いやPHPをDisっているわけではなく、PHPで書かれたレガシーコードが激しく腐海と言う意)に漬かっていてどうにもJavaで何か書きたくなったので突発企画。

リモートに有るJavaVMからJmxのMxBeanを取ってきて、JavaVMのヒープ領域使用状況を取得します。ヒープ領域の説明やGCとの絡みはこの辺参照。

Java Review:JavaVMのメモリ管理をマスターする (1/2) - ITmedia エンタープライズ Java Review:JavaVMのメモリ管理をマスターする (1/2) - ITmedia エンタープライズ

Jmxの接続先(監視対象)としてJava6,7で動作確認。監視対象のJvmを起動する時に下記オプションを追加してください。

-Dcom.sun.management.jmxremote.port=<ポート番号>
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

今回のソースでは認証やSSLには対応してません。

サンプルソース

package jp.gr.java_conf.ka_ka_xyz.example;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
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;

/**
 * リモートJVMからMxBeanを取得するサンプル
 * */
public class Example{

	private static MBeanServerConnection remote = null;
	public static void main(String[] args) throws Exception {

		//接続先ホスト名(ベタ書きやっつけ仕事)
		String host = "localhost";
		//jmxポート番号(ベタ書きやっつけ仕事)
		int port = 17999;
		final String serviceUrlStr = "service:jmx:rmi:///jndi/rmi://" 
				+ host + ":" + port + "/jmxrmi";
		final JMXServiceURL target = new JMXServiceURL(serviceUrlStr);
		
		JMXConnector connector = null;
		try {
			connector = JMXConnectorFactory.connect(target);
			// リモートJvmへのコネクション作成
			remote = connector
					.getMBeanServerConnection();
		} catch (IOException e) {
			//コネクションエラー発生時の処理(手抜き)
			System.err.println("filed to connect: " + target.toString());
			e.printStackTrace();
			System.exit(-1);
		}
		
		//Survivor領域についてのPool名
		final String survivorSpaceMemoryPoolMxbeanName = "PS Survivor Space";
		//Eden領域についてのPool名
		final String edenSpaceMemoryPoolMxbeanName =  "PS Eden Space";
		//Old Gen領域についてのPool名
		final String oldGenSpaceMemoryPoolMxbeanName = "PS Old Gen";
		//Perm Gen領域についてのPool名
		final String permGenSpaceMemoryPoolMxbeanName = "PS Perm Gen";
		//Code Cache領域についてのPool名
		final String codeCacheSpaceMemoryPoolMxbeanName = "Code Cache";

		List<MemoryPoolMXBean> beans = new ArrayList<MemoryPoolMXBean>();
		
		//MxBeanを取得
		beans.add(getMemoryPoolMxBean(survivorSpaceMemoryPoolMxbeanName));
		beans.add(getMemoryPoolMxBean(edenSpaceMemoryPoolMxbeanName));
		beans.add(getMemoryPoolMxBean(oldGenSpaceMemoryPoolMxbeanName));
		beans.add(getMemoryPoolMxBean(permGenSpaceMemoryPoolMxbeanName));
		beans.add(getMemoryPoolMxBean(codeCacheSpaceMemoryPoolMxbeanName));
		
		//参考情報出力
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd_HH:mm:ss z");
		System.out.println("datetime: " + sdf.format(new Date()));
		System.out.println("Jmx Url: " + serviceUrlStr);
		
		for(MemoryPoolMXBean bean : beans) {
			if(bean == null){continue;}
			System.out.println(bean.getName() + ": " + bean.getUsage());
			//下記のようにMemoryUsageインスタンスを取得して個別の値を取得することも可能
			//MemoryUsage usage = bean.getUsage();
		}
		if(connector != null){connector.close();}
	}
	/**
	 * 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;
	}
}

今気づいたけど、一応localhostじゃなくてリモートサーバーのJvm呼び出しといた方が良かったかな。理屈の上ではおkなはずなんで、検証は取り敢えず後で。

実行結果はこんな感じ

datetime: 2014.03.23_01:48:20 JST
Jmx Url: service:jmx:rmi:///jndi/rmi://localhost:17999/jmxrmi
PS Survivor Space: init = 8388608(8192K) used = 262144(256K) committed = 262144(256K) max = 262144(256K)
PS Eden Space: init = 50331648(49152K) used = 15273856(14915K) committed = 42598400(41600K) max = 1059258368(1034432K)
PS Old Gen: init = 134217728(131072K) used = 12752952(12454K) committed = 134217728(131072K) max = 2147483648(2097152K)
PS Perm Gen: init = 21757952(21248K) used = 25684856(25082K) committed = 25690112(25088K) max = 85983232(83968K)
Code Cache: init = 2555904(2496K) used = 2484416(2426K) committed = 2555904(2496K) max = 50331648(49152K)

後はWindowsサービスに組み込んだりcsvへの出力を書いたりすればお手軽なヒープ使用状況確認ツールが作れそう(jconsoleだとサービス起動出来ないから不便なんだよなあ)。