はじめに
Java アプリケーションを Instana で監視すると、ヒープメモリの使用量やGC の挙動、スレッド数など、JVM の標準メトリクスを即座に可視化することができます。
運用開始直後でも 「どの程度メモリを使っているのか」「GC がボトルネックになっていないか」 といった問いに答えやすく、トラブルシューティングや容量計画の初動を大きく短縮できます。
一方、現場ではフレームワークやミドルウェア、サードパーティの 特定ライブラリが公開するメトリクス(例:DB 接続プールの使用率、キュー長、再試行数など)を Instana 上に並べて見たい場面は少なくありません。Instana がセンサーを提供しているテクノロジーであれば少ない手順で可視化できますが、すべてのライブラリが網羅されているわけではありません。
そう言った場合、JMX(Java Management Extensions) を使って任意のメトリクスを可視化することができます。JMX は、ライブラリやアプリが公開している メトリクス(MBean属性)や操作に標準 API 経由でアクセスできる仕組みで、Instana はこの JMXメトリクスを「カスタム JMX メトリクス」として取り込み、ダッシュボードやアラートで活用できます。
今回は、このJMXを使って特定のメトリクスをInstana で可視化する手順をご紹介します。
検証用サンプルアプリ
Instana で Javaアプリ内のデータベースコネクションプールのメトリクスをJMX経由で可視化します。
以下の内容で、データベースにただひたすらデータを登録するアプリケーションを作成します。
環境
- OS: RHEL9.6
- Java: OpenJDK17
- Connection Pool: Apache Commons DBCP2 2.13
- JDBC Driver: IBM Db2 JDBC Driver (Type 4) 12.1.3
- データベース: Db2 v12.1.3
- Instana Agent: エージェントバージョン:2025.12.12.0748、ブートバージョン:1.2.49
データベースとInstana Agentは導入済みとします。
Instana Agent と Db2 の導入・監視については InstanaでDb2を監視してみよう! を参考にしてください。
実装
概要
- 常駐型プロセスとして起動し、Db2 の
Test1テーブルへランダムな整数を高速に INSERT - Apache Commons DBCP2 のコネクションプールを使用し、JMX で標準 MBean 名
org.apache.commons.dbcp2:type=BasicDataSource,name=Db2Poolで登録 - コネクションプールのメトリクス変動が見えるよう、並列処理 + 長時間保持 + バースト の負荷を生成
Db2 テーブル
Db2 に以下のテーブルを作成しておきます。
CREATE TABLE Test1 (
ID INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
Data INTEGER NOT NULL,
CreatedDate TIMESTAMP NOT NULL
);
sudo su - db2inst1
db2 connect to SAMPLE
db2 "<上記SQL>"
JDK と Maven
dnf でインストールしておきます。
# OpenJDK 17 (JRE + JDKツール) を導入
sudo dnf install -y java-17-openjdk java-17-openjdk-devel
# Maven を導入
sudo dnf install -y maven
ソースコード
Java アプリを Mavenプロジェクトとして作成します。
ディレクトリ構成
├── pom.xml
├── run.sh
├── src/main/java/com/example/db2pool
└── App.java
pom.xml
code
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>db2-dbcp-jmx-demo</artifactId>
<version>1.0.0</version>
<name>DB2 + Apache Commons DBCP + JMX Demo</name>
<description>Java17 resident process that inserts into Db2 using commons-dbcp2 pool and exposes JMX metrics</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.class>com.example.db2pool.App</main.class>
</properties>
<dependencies>
<!-- Apache Commons DBCP2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.13.0</version>
</dependency>
<!-- IBM Db2 JDBC Driver (Type 4) -->
<dependency>
<groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId>
<version>12.1.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
App.java
code
package com.example.db2pool;
import org.apache.commons.dbcp2.BasicDataSource;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Duration;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 常駐型プロセス:Db2 の Test1 テーブルへランダムなデータを INSERT。
* DBCP2 の Connection Pool を利用し、JMX に標準の MBean 名で登録。
* NumActive / NumIdle の変動が目視できるよう並列・待機を組み合わせています。
*/
public class App {
private static final Logger LOG = Logger.getLogger(App.class.getName());
// JMX の標準 MBean 名(ドメイン: org.apache.commons.dbcp2, type=BasicDataSource)
private static final String JMX_OBJECT_NAME = "org.apache.commons.dbcp2:type=BasicDataSource,name=Db2Pool";
public static void main(String[] args) throws Exception {
BasicDataSource ds = buildDataSourceFromEnv();
// プール名を JMX に登録(BasicDataSource は getConnection() で初回登録します)
ds.setJmxName(JMX_OBJECT_NAME);
// 先に 1 コネクション借用して JMX 登録を即座に発生させる
try (Connection c = ds.getConnection()) {
LOG.info("Warm-up connection acquired to trigger JMX registration");
}
// メトリクスの周期的なログ出力(JMX 経由と API 経由の両方)
startMetricsLogger(ds);
// NumActive を増減させるワークロードを投入
runLoad(ds);
// 常駐: メインスレッドは終了せず待機
Thread.currentThread().join();
}
/**
* 環境変数から接続設定を読み込み、BasicDataSource を生成します。
* 必要な環境変数: DB2_URL, DB2_USER, DB2_PASSWORD
*/
private static BasicDataSource buildDataSourceFromEnv() {
String url = getenvRequired("DB2_URL");
String user = getenvRequired("DB2_USER");
String pass = getenvRequired("DB2_PASSWORD");
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.ibm.db2.jcc.DB2Driver");
ds.setUrl(url);
ds.setUsername(user);
ds.setPassword(pass);
// プール設定(変動がわかりやすい値)
ds.setInitialSize(2);
ds.setMinIdle(2);
ds.setMaxIdle(6);
ds.setMaxTotal(8);
ds.setMaxWait(Duration.ofSeconds(10));
ds.setValidationQuery("SELECT 1 FROM SYSIBM.SYSDUMMY1");
ds.setTestOnBorrow(true);
ds.setTestWhileIdle(true);
return ds;
}
private static String getenvRequired(String name) {
String v = System.getenv(name);
if (v == null || v.isBlank()) {
throw new IllegalArgumentException("環境変数 " + name + " が未設定です");
}
return v;
}
/**
* DBCP の NumActive/NumIdle が変動するような並列処理を実行します。
*/
private static void runLoad(BasicDataSource ds) {
int parallelWorkers = 12; // プール maxTotal=8 を超える並列で揺らす
ExecutorService exec = Executors.newFixedThreadPool(parallelWorkers);
CountDownLatch start = new CountDownLatch(1);
Random rnd = new Random();
for (int i = 0; i < parallelWorkers; i++) {
int workerId = i;
exec.submit(() -> {
try {
start.await();
while (true) {
// たまに長時間保持して輻輳を起こす(NumActive を増やす)
boolean longHold = rnd.nextDouble() < 0.20; // 20% は長めに保持
insertBatch(ds, 200 + rnd.nextInt(400), longHold ? 1500 : 100);
// 次のバーストまで待機
Thread.sleep(200 + rnd.nextInt(600));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
start.countDown();
}
/**
* 1 コネクションを借用して INSERT を繰り返し、任意の時間保持します。
*/
private static void insertBatch(BasicDataSource ds, int rows, int holdMillis) {
try (Connection con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(
"INSERT INTO Test1 (Data, CreatedDate) VALUES (?, CURRENT_TIMESTAMP)")) {
Random rnd = new Random();
for (int i = 0; i < rows; i++) {
int val = 1 + rnd.nextInt(100000);
ps.setInt(1, val);
ps.executeUpdate();
// 少しウェイトを入れることで借用時間を伸ばす
if (i % 50 == 0) {
Thread.sleep(5);
}
}
// コネクションを追加で保持してプールの空きを詰まらせる
Thread.sleep(holdMillis);
} catch (SQLException e) {
LOG.log(Level.SEVERE, "DB エラー", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* JMX と API の両方から NumActive / NumIdle を 1 秒毎に出力します。
*/
private static void startMetricsLogger(BasicDataSource ds) throws Exception {
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName on = new ObjectName(JMX_OBJECT_NAME);
ses.scheduleAtFixedRate(() -> {
try {
Object numActiveJmx = mbs.getAttribute(on, "NumActive");
Object numIdleJmx = mbs.getAttribute(on, "NumIdle");
int numActiveApi = ds.getNumActive();
int numIdleApi = ds.getNumIdle();
LOG.info(String.format("[Pool] NumActive JMX=%s API=%d / NumIdle JMX=%s API=%d",
numActiveJmx, numActiveApi, numIdleJmx, numIdleApi));
} catch (Exception e) {
LOG.log(Level.WARNING, "JMX メトリクス取得に失敗", e);
}
}, 0, 1, TimeUnit.SECONDS);
}
}
run.sh
#!/usr/bin/env bash
set -euo pipefail
mvn -DskipTests package
export DB2_URL=jdbc:db2://localhost:25010/SAMPLE
export DB2_USER=<input db2 username>
export DB2_PASSWORD=<input db2 password>
java -cp target/db2-dbcp-jmx-demo-1.0.0.jar:target/lib/* com.example.db2pool.App
Instana 側設定
Instana Agent にて、JMXの監視有効化と対象の明確化が必要になります。
com.instana.plugin.javaを検索し、以下を記載します。
object_name は Javaソースコード内でsetJmxNameメソッドで指定した文字列ですね。
com.instana.plugin.java:
jmx:
- object_name: 'org.apache.commons.dbcp2:type=BasicDataSource,name=Db2Pool'
metrics:
- attributes: 'NumActive'
type: 'absolute'
- attributes: 'NumIdle'
type: 'absolute'
- attributes: 'MaxTotal'
type: 'absolute'
- attributes: 'MaxIdle'
type: 'absolute'
- attributes: 'MinIdle'
type: 'absolute'
通常、configuration.yamlを更新するとInstana は自動的にconfigのreloadを行なってくれるのですが、jmxに関しては明示的にAgentもしくは対象JVMの再起動が必要です。
念の為、Instana Agentの再起動を行います。
sudo systemctl restart instana-agent
configuration.yamlのコメントにJMX is NOT hot-reloaded and needs to be set before a JVM is discovered.という記載があります。
(意訳:JMX はホットリロードされません。JVM が検出される前に設定しておく必要があります)
再検出=JVM が「新規に見つかる」タイミングでJMX設定が適用されるということです。
参考:
https://www.ibm.com/docs/ja/instana-observability/current?topic=technologies-jmx-custom-metrics
実行
では Javaアプリを起動します。
./run.sh
Mavenビルドの後に、以下のような出力が出続ければOKです。数分待ちます。
Dec 18, 2025 2:23:04 AM com.example.db2pool.App main
INFO: Warm-up connection acquired to trigger JMX registration
Dec 18, 2025 2:23:04 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=0 API=0 / NumIdle JMX=2 API=2
Dec 18, 2025 2:23:05 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
Dec 18, 2025 2:23:06 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
Dec 18, 2025 2:23:07 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
Dec 18, 2025 2:23:08 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
Dec 18, 2025 2:23:09 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=7 API=7 / NumIdle JMX=1 API=1
Dec 18, 2025 2:23:10 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=6 API=6 / NumIdle JMX=2 API=2
Dec 18, 2025 2:23:11 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=5 API=5 / NumIdle JMX=3 API=3
Dec 18, 2025 2:23:12 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=6 API=6 / NumIdle JMX=2 API=2
Dec 18, 2025 2:23:13 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
Dec 18, 2025 2:23:14 AM com.example.db2pool.App lambda$startMetricsLogger$1
INFO: [Pool] NumActive JMX=8 API=8 / NumIdle JMX=0 API=0
上記を見る限り、NumActive(ConnectionPoolの該当時間の利用数)とNumIdle(ConnectionPoolの該当時間の空き数)が変動していることがわかりますね。
これがInstana でも表示されて欲しいですね。
Instana での可視化
まずはインフラストラクチャーを確認します。
JVMがちゃんと認識されました。

com.example.db2pool.Appをクリックしてダッシュボードを開くを選択します。
JVMのスレッド数やヒープ・メモリーなどを見ることができる画面に遷移しました。これらはデフォルトで参照可能です。

JMXを設定すると、「カスタムJMXメトリクス」という項目がこの画面の一番下に追加されます。
今回、Instana Agentのconfigration.yamlで設定した5つのattributeがカスタムJMXメトリクスとして取得できていることがわかります。
NumActiveとNumIdleについては、変動している様子をグラフで簡単に見ることができますね!

ということで、JMXを使ってApache Commons DBCP2 の ConnectionPoolのメトリクスをInstanaで可視化できることがわかりました。
カスタムダッシュボード
カスタムJMXメトリクスを表示できることはわかったものの、見るためにはJVMの奥まで遷移しなければならず、面倒ですよね。
そういった場合はカスタムダッシュボードを使うとより容易にカスタムJMXメトリクスを参照することができます。
以下は完成例です。
JMXで出力したConnection PoolのActive数、Idle数、最大数(MaxTotal)、そしてDb2側のConnection数を1つのグラフで見ることが可能です。
(10秒間の平均で描画するため少しなまってしまうのが弱点...)

カスタムJMXメトリクスはカスタムダッシュボードでメトリックとして選択肢に表示されるため、他のメトリックと同様にGUIから選択することができます。


最後に
JMXを使って、Instanaが対応していない任意のメトリクスをInstana上で可視化し、さらにカスタムダッシュボードを使って簡単に見れるようにしました。
もちろん他のメトリクスも同様に可視化できますので、「あ、Instanaではこのライブラリのこのメトリクス取れないんだ…」となりましたら、ぜひJMXをお試しください!