JMX ile Java Uygulamalarını İzlemek
https://github.com/idylmz/JMXExampleProject

Yıllar önce resilience üzerine çalışan bir ekibe katıldığımda, Java ile geliştirilmiş bir ürünü APM ajanı olmadan izlememiz istenmişti. Ekipteki herkesin kafasında tek bir soru vardı:
“Agent kurmadan bu uygulama içinde neler olup bittiğini nasıl göreceğiz?”
Ürün dış bir ekibin geliştirmesi olduğu için, izleme konusunu onlara danıştık. Aldığımız cevap ise kısaydı:
“JMX ile bağlanabilirsiniz.”
O dönem ne Java’yı biliyordum, ne de JMX’in ne işe yaradığını… Bize söylenen komutları uyguladık, izlememiz gerekenleri izledik ve iş tamamlandı.
Ama zaman geçtikçe bu mekanizmanın arka planda nasıl çalıştığını merak etmeye başladım.
Bunun üzerine JMX’i detaylıca öğrenmek için çalıştım ve edindiğim deneyimi de bu yazıda paylaşmak istedim.
JMX, Java dünyasının yıllardır kullandığı güçlü bir yönetim ve izleme altyapısı.
Sadece JVM metriklerini değil, uygulamanın içindeki her şeyi izleyebileceğiniz bir API.
Bu yazıda:
- JMX’in nasıl çalıştığını,
- Custom MBean yazmayı,
- JMX’i RMI ile dışarı açmayı,
- Ve JMC (Java Mission Control) üzerinde canlı olarak nasıl izleyebileceğimizi
adım adım göstereceğim.
Hiç Java bilmeden bile bu yazıyı takip edebilirsiniz. Ben mesela Dotnetçi halimle bu yazı kadarlık Java öğrendim. 😄
Keyifli okumalar diliyorum.
👀 JMX Nedir?
JMX (Java Management Extensions), Java uygulamalarını çalışır durumdayken izlemek ve yönetmek için kullanılan yerleşik bir teknolojidir.
JMX sayesinde bir Java uygulamasının içinden:
- Metalar (metrics) okunabilir. Örn: kaç istek geldi, kaç işlem tamamlandı, kuyruk boyu, vs.
- Yapılandırma ayarlarını değiştirebilir. Örn: log seviyesini runtime’da değiştirmek
- Operasyon çağırabilir. Örn: bir cache’i temizlemek, bir servisi resetlemek
Kısacası:
JMX = Java uygulamasının içini canlı canlı kontrol edebildiğin pencere.
🧱 JMX’in Temel Yapı Taşları
MBean => Yönetilebilir Nesne.
MBeanServer => MBean’leri kayıt eden ve dışarıya servis eden yapı
Connector => MBeanServer’a erişim yöntemi
JMX Client => MBeanleri görüntüleyen araç. Bir GUI aracı (JConsole, Java Mission Control) olabilir. Ya da hem client hem de veriyi dış sistemlere aktaran köprü araçlar da olabilir. (Prometheus JMX Exporter, Logstash JMX plugini gibi)
🫘 MBean nedir? (Hayır fasulye değil)
MBean (Managed Bean), JMX’in izlenecek ve yönetilecek nesnesi.
Bir MBean ile:
✔ Değerleri okuyabilirsin
✔ Değerleri değiştirebilirsin
✔ Metotlarını çalıştırabilirsin (ör. restart(), clearCache())
Örn:
public interface AppMetricsMBean {
long getRequestCount();
void incrementRequestCount();
}
🌍 JMX’e nasıl erişilir?
İki senaryo var:
Birincisinin üzerinde çok durmayacağım. Çünkü büyük ölçekli sistemlerde uygulanabilirliği yok. Localdeki uygulamanızın MBeanServer’ına erişmek isterseniz direkt GUI olan bir araç ile izleyebilirsiniz. Dediğim gibi, üzerinde çok durmayacağım.
İkinci senaryo ise şu şekilde. Remote izleme. Burada da hayatımıza Remote Method Invocation (RMI) kavramı giriyor. İlgili sunucunun bir X portundan servis edilen JMX metriklerini incelemek için kullanılabilir. Bu yazının ana konusu bu. Çünkü açıkçası büyük sistemlerde tek tek bütün sunuculara Java Mission Control (JMC) ile bağlanmak ne gerçekçi ne de pratik. Günün sonunda biz bunları görselleştirmek, metrikleştirmek ve alarmlaştırmak isteyeceğiz. Ama hangi aracı kullanırsak kullanalım, protokolümüz daima RMI.
service:jmx:rmi:///jndi/rmi://<host>:9999/jmxrmi
🎯 JMX’in bugün hâlâ kritik olmasının nedenleri
Modern observability araçlarının çoğu JMX’ten besleniyor
Prometheus, Grafana, Elastic APM, Dynatrace ve daha fazlası…
Prometheus JMX Exporter diye bir şeyin olmasının nedeni bu 😊
Ayrıca:
- No-agent izleme opsiyonu sunuyor
- JVM’in içini en iyi anlatan kaynak yine JVM’in kendisi
- CI/CD ve container dünyasında remote kontrol noktası
Üstelik tüm bunları, Java’nın kendi mekanizmalarını kullanarak neredeyse sıfır ek yük ile yapabiliyoruz.
🧩 JMX MBean Nasıl Oluşturulur?
JMX dünyasında yönetilebilir nesnelere MBean (Managed Bean) denir.
Bir MBean oluşturmak için aslında iki adıma ihtiyacımız var:
1️⃣ Bir MBean interface’i tanımlamak
2️⃣ Bu interface’i implemente eden bir sınıf oluşturmak
JMX’in bizi zorladığı tek kural:
Interface adı SomethingMBean olmalı.
Sınıf adı ise Something — yani interface’in sonundaki MBean yazılmıyor.
🔹 1. Adım — Interface’i oluşturalım
Aşağıdaki interface, /hello endpoint’ine yapılan istek sayısını tutacak:
public interface AppMetricsMBean {
long getRequestCount();
void incrementRequestCount();
}
Burada:
- getRequestCount() → JMX üzerinden value olarak okunabilir
- incrementRequestCount() → JMX üzerinden operation olarak çağrılabilir
(İleride JMC’den elle tıklayıp göreceğiz.)
🔹 2. Adım — Uygulayan sınıf
public class AppMetrics implements AppMetricsMBean {
private long requestCount = 0;
@Override
public synchronized long getRequestCount() {
return requestCount;
}
@Override
public synchronized void incrementRequestCount() {
requestCount++;
}
}
📝 Neden synchronized?
MBean’e aynı anda birden fazla thread erişebileceği için veri bütünlüğünü korumak adına.
🎯 Bu noktada neredeyiz?
✔ Kendi metriğimizi tutan bir MBean yazdık
❌ Ama henüz JVM’e kaydettirmedik
📌 Yani şu anda MBean var, fakat kimse onu bilmiyor.
JMX’in çalışabilmesi için bu MBean’in:
- JVM’in MBeanServer’ına register edilmesi
- Gerekirse dış dünyaya RMI ile açılması gerekiyor
İşte bir sonraki bölümde bunu yapacağız 👇
🔧 MBean’i Spring Boot’e Tanıtmak (MBeanServer Register)
JMX’in MBeanServer’ı aslında JVM içinde zaten çalışıyor.
Biz sadece kendi MBean’imizi o sunucuya bildiriyoruz — yani:
“Benim AppMetrics diye izlenecek yeni bir nesnem var!” demek.
Bunu uygulama başlarken otomatik olarak yapmak için bir @Configuration sınıfı kullanacağız.
src/main/java/…/JmxConfig.java:
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
@Configuration
public class JmxConfig {
@Bean
public AppMetrics appMetricsMBean() throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name =
new ObjectName("com.example.demo:type=ApplesAndOranges");
AppMetrics mbean = new AppMetrics();
if (!mbs.isRegistered(name)) {
mbs.registerMBean(mbean, name);
}
return mbean;
}
@Bean(initMethod = "start", destroyMethod = "stop")
public JmxRmiServer jmxRmiServer() {
return new JmxRmiServer(9999); // 9999: JMX RMI port
}
public static class JmxRmiServer {
private final int port;
private JMXConnectorServer connectorServer;
public JmxRmiServer(int port) {
this.port = port;
}
public void start() throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
LocateRegistry.createRegistry(port);
String urlString = String.format(
"service:jmx:rmi:///jndi/rmi://0.0.0.0:%d/jmxrmi", port);
JMXServiceURL url = new JMXServiceURL(urlString);
connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
connectorServer.start();
System.out.println("JMX RMI connector started on: " + urlString);
}
public void stop() throws Exception {
if (connectorServer != null) {
connectorServer.stop();
}
}
}
}
Artık bu sınıfımızı MBean olarak tanımladık. Sonradan JConsole’da göreceğimiz üzere ApplesAndOranges etiketi altında buradaki counter değerini okuyabileceğiz.
Her şeyi hallettik ama değeri nasıl arttıracağımızı konuşmadık. Evet dışarıdan da arttırılabiliyor değer. Ancak gerçekçi bir senaryoda ben uygulamanın hesapladığı değerleri okumak isteyeceğim.
📈 Bir HTTP Endpoint’inden MBean Güncellemek
Şimdi MBean’i gerçekten “yaşatalım”.
Yani /hello endpoint’i her çağrıldığında:
- AppMetrics.incrementRequestCount() çalışsın
- JMX metrik değeri artsın
- Biz de bunu JMC’den izleyelim
1️⃣ CounterService
CounterService’i bu yazıda net gözükecek şekilde şöyle kurgulayabilirsin:
package com.example.demo;
import org.springframework.stereotype.Service;
@Service
public class CounterService {
private final AppMetrics appMetrics;
public CounterService(AppMetrics appMetrics) {
this.appMetrics = appMetrics;
}
public void increment() {
appMetrics.incrementRequestCount();
}
}
2️⃣ HelloController
/hello’ya her istek geldiğinde bu servisi kullanalım:
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
private final CounterService counterService;
public HelloController(CounterService counterService) {
this.counterService = counterService;
}
@GetMapping("/hello")
public String hello() {
counterService.increment();
return "Hello from JMX!";
}
}
Artık akış şöyle:
- Kullanıcı /hello’ya istek gönderiyor
- HelloController → CounterService.increment() çağırıyor
- CounterService → AppMetrics.incrementRequestCount() çağırıyor
- AppMetrics içindeki requestCount artıyor
- Bu değer JMX MBean attribute’u olarak dışarıdan okunabilir durumda
👀 JMC Üzerinden Canlı İzlemek
Artık elinde:
- Çalışan bir Spring Boot uygulaması
- /hello endpoint’i
- JMX + RMI ile açılmış bir MBean
Şimdi bunu görselleştirelim.
Uygulamayı çalıştır:
./mvnw spring-boot:run
Komut ile jconsole’u çalıştır.
jconsole

Şu aşamada şuna değinmekte fayda var. Biz aslında 1 metrik register ettik. Ancak MBeanServer’a kayıtlı olan JVM’in kendisi ile ilgili olan metrikler de var. CPU tüketimi, memory tüketimi vs. gibi temel infra metrikleri halihazırda sunuyor. İzlemek istediğiniz uygulamanızda register edilmiş iş açısından kritik metrikler varsa, bunları da izleyebiliyorsunuz.

MBean’e register edilmiş metrikleri görüntülemek için MBeans sekmesine geçiyoruz.

Görüldüğü üzere RequestCount metriğimiz gelmiş. Şu an istek olmadığı için 0 geliyor. Hemen bir istek atalım.
curl http://localhost:8080/hello

Görüldüğü gibi gelen istekle birlikte sayacım bir arttı. Peki biz yazının ortalarında JConsole üzerinden de bir şeyler tetikleyebildiğimizi görmüştük, bunun için bir method yazmıştık. Bu ne olacak?
Şimdi de operations altında bu methodu görüntüleyeceğiz.

Buradan dışarıya açtığımız methodu tetikleyebiliyoruz. Tekrardan countera baktığımızda, değerin artmış olduğunu göreceğiz. Tabi biz bunları istek sayılarına şike katmak için değil, cache temizliği gibi önemli işlemler için kullanacağız. 😊

Bu yazıda JMX’in temellerini kurduk:
Custom MBean oluşturduk, RMI ile dışarı açtık ve JMC üzerinden canlı izledik.
Bir sonraki yazıda bu metrikleri Prometheus ve Grafana ile modern observability hattına bağlayacağız.
Takipte kalın!
