Introduction
Apache Tomcat, a widely adopted Java application server, is commonly employed in real-world projects. To ensure applications run smoothly, it is important to monitor metrics such as memory usage, thread count, and the behavior of the garbage collector (GC).
To collect these metrics, you can use JMX (Java Management Extensions).
This article explains how to enable JMX on Tomcat and how to connect to it for monitoring.
What Is JMX?
JMX (Java Management Extensions) is a framework that allows you to monitor and manage the resources and behavior of Java applications.
With JMX, you can obtain information such as:
- Memory details (heap/non-heap usage, GC count and time)
- Thread information (current thread count, deadlock detection)
- Class loading statistics
By integrating JMX with monitoring tools such as JConsole, VisualVM, Datadog, or Zabbix, you can view real-time metrics of your application.
You can also use MBeans (Managed Beans) to access or control application internals externally, though this article focuses mainly on monitoring setup.
Enabling JMX in Tomcat
Let’s go through the steps to configure JMX in Tomcat.
Configure setenv.sh
To enable JMX when starting Tomcat, add JMX-related options to the CATALINA_OPTS environment variable.
In Linux, create or edit $CATALINA_HOME/bin/setenv.sh and add the following lines:
[root@quiz ~]# vi /opt/tomcat/bin/setenv.sh
[root@quiz ~]# cat /opt/tomcat/bin/setenv.sh
export JAVA_HOME=/opt/java/jdk-21.0.2
export JRE_HOME=$JAVA_HOME
export PATH=$JAVA_HOME/bin:$PATH
# JMX setting
export JMX_KEYSTORE_PASS='XXXXXXXXXXX'
# JMX setting
CATALINA_OPTS="$CATALINA_OPTS \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.rmi.port=9011 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.access.file=/opt/tomcat/conf/jmxremote.access \
-Dcom.sun.management.jmxremote.password.file=/opt/tomcat/conf/jmxremote.password \
-Dcom.sun.management.jmxremote.registry.ssl=true \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false \
-Dcom.sun.management.jmxremote.local.only=false \
-Djavax.net.ssl.keyStore=/opt/tomcat/conf/jmxkeystore.jks \
-Djavax.net.ssl.keyStorePassword=$JMX_KEYSTORE_PASS \
-Djava.rmi.server.hostname=quiz.eeengineer.com"
[root@quiz ~]#Explanation of each setting
export JMX_KEYSTORE_PASS='XXXXXXXXXXX'
Defines the keystore password as an environment variable for SSL encryption in JMX communication.
It’s later referenced by-Djavax.net.ssl.keyStorePassword.
-Dcom.sun.management.jmxremote
Enables remote management via JMX.
-Dcom.sun.management.jmxremote.port=9010
Port for accepting remote JMX connections.
-Dcom.sun.management.jmxremote.rmi.port=9011
Port used for actual RMI communication.
(RMI stands for Remote Method Invocation, allowing a Java process to call methods on another process as if they were local.)
-Dcom.sun.management.jmxremote.authenticate=true
Requires username/password authentication for JMX access.
-Dcom.sun.management.jmxremote.access.file-Dcom.sun.management.jmxremote.password.file
Specify the access control and password files. These define users and their permissions.
-Dcom.sun.management.jmxremote.registry.ssl=true
Enables SSL encryption for the RMI registry.
-Dcom.sun.management.jmxremote.ssl=true
Enables SSL encryption for JMX communication.
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false
Disables client-side certificate authentication (optional).
-Dcom.sun.management.jmxremote.local.only=false
Allows remote (non-localhost) connections.
-Djavax.net.ssl.keyStoreand-Djavax.net.ssl.keyStorePassword
Specify the location and password of the SSL keystore.
-Djava.rmi.server.hostname=quiz.eeengineer.com
Defines the hostname used for client connections (should be accessible externally).
Creating Authentication Files
Create the authentication files specified in setenv.sh:
jmxremote.access and jmxremote.password.
・jmxremote.access
Defines each user’s permission level.
[root@quiz ~]# vi /opt/tomcat/conf/jmxremote.access
[root@quiz ~]# cat /opt/tomcat/conf/jmxremote.access
monitorRole readonly
adminRole readwrite
[root@quiz ~]#Set ownership and permissions.
[root@quiz ~]# chown tomcat:tomcat /opt/tomcat/conf/jmxremote.access
[root@quiz ~]# chmod 600 /opt/tomcat/conf/jmxremote.access
[root@quiz ~]# ls -l /opt/tomcat/conf/jmxremote.access
-rw------- 1 tomcat tomcat 25 Oct 7 06:44 /opt/tomcat/conf/jmxremote.access
[root@quiz ~]#・jmxremote.password
Defines the passwords for each role.
[root@appserver ~]# vi /opt/tomcat/conf/jmxremote.password
[root@appserver ~]# cat /opt/tomcat/conf/jmxremote.password
monitorRole YYYYYYYYYYY
adminRole ZZZZZZZZZZSet secure file permissions.
[root@quiz ~]# chown tomcat:tomcat /opt/tomcat/conf/jmxremote.password
[root@quiz ~]# chmod 600 /opt/tomcat/conf/jmxremote.password
[root@quiz ~]# ls -l /opt/tomcat/conf/jmxremote.password
-rw------- 1 tomcat tomcat 25 Oct 7 06:44 /opt/tomcat/conf/jmxremote.password
[root@quiz ~]Creating an SSL Certificate
This time, we will show how to create a self-signed certificate. For production environments, it is recommended to use a certificate issued by a trusted Certificate Authority (CA).
As shown below, create the certificate using the keytool command and deploy it to Tomcat.
[root@quiz ~]# keytool -genkeypair \
-alias jmx \
-keyalg RSA \
-keysize 2048 \
-validity 3650 \
-keystore /opt/tomcat/conf/jmxkeystore.jks \
-storepass AAAAAAAAAAA\
-keypass AAAAAAAAAAA\
-dname "CN=quiz.eeengineer.com, OU=IT, O=eeengineer company, L=Tokyo, ST=Tokyo, C=JP"
Generating 2,048 bit RSA key pair and self-signed certificate (SHA384withRSA) with a validity of 3,650 days
for: CN=quiz.eeengineer.com, OU=IT, O=eeengineer company, L=Tokyo, ST=Tokyo, C=JP
[root@appserver ~]#Set correct permissions.
[root@quiz ~]# chown tomcat:tomcat /opt/tomcat/conf/jmxkeystore.jks
[root@quiz ~]# chmod 600 /opt/tomcat/conf/jmxkeystore.jks
[root@quiz ~]# ls -l /opt/tomcat/conf/jmxkeystore.jks
-rw------- 1 tomcat tomcat 2738 Oct 9 06:43 /opt/tomcat/conf/jmxkeystore.jks
[root@quiz ~]#Applying the Configuration
Restart Tomcat to apply the new configuration.
[root@quiz ~]# systemctl restart tomcat
[root@quiz ~]# systemctl status tomcat
● tomcat.service - Apache Tomcat 10
Loaded: loaded (/etc/systemd/system/tomcat.service; enabled; preset: disabled)
Active: active (exited) since Thu 2025-10-09 06:46:46 JST; 6s ago
Process: 105743 ExecStart=/opt/tomcat/bin/startup.sh (code=exited, status=0/SUCCESS)
Main PID: 105743 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 23143)
Memory: 4.5M
CPU: 3.745s
CGroup: /system.slice/tomcat.service
Oct 09 06:46:46 quiz systemd[1]: Starting Apache Tomcat 10...
Oct 09 06:46:46 quiz [startup.sh](http://startup.sh/)[105743]: Tomcat started.
Oct 09 06:46:46 quiz systemd[1]: Finished Apache Tomcat 10.
[root@quiz ~]#Verify that ports 9010 and 9011 are open.
[root@quiz ~]# ss -tulnp | grep 9010
tcp LISTEN 0 50 *:9010 *:* users:(("java",pid=103023,fd=12))
[root@quiz ~]#
[root@quiz ~]# ss -tulnp | grep 9011
tcp LISTEN 0 50 *:9011 *:* users:(("java",pid=103023,fd=10))
[root@quiz ~]#Certificate deployment to clients
I will explain the procedure for installing the certificate on the client that connects to the server.
Export the server certificate:
[root@quiz ~]# keytool -exportcert \
-alias jmx \
-keystore /opt/tomcat/conf/jmxkeystore.jks \
-file /opt/tomcat/conf/jmx.cer
Enter keystore password:
Certificate stored in file </opt/tomcat/conf/jmx.cer>After downloading the stored certificate to the client using WinSCP or a similar tool, add the certificate to the client’s keystore.
C:\Windows\System32>keytool -importcert -trustcacerts -alias quiz.eeengineer.com -file "C:\Users\user\Desktop\jmx.cer" -keystore "C:\Program Files (x86)\jdk-21\lib\security\cacerts" -storepass changeit
Trust this certificate? [no]: y
Certificate was added to keystoreAs shown below, verify that the certificate has been successfully imported into the keystore.
C:\Windows\System32> keytool -list -cacerts -storepass changeit | findstr quiz.eeengineer.com
quiz.eeengineer.com,2025/10/12, trustedCertEntry,Network Control
If you allow external (internet) access to JMX, relying solely on Tomcat’s password authentication is extremely dangerous. If the password is leaked or subjected to a brute-force attack, the risk of unauthorized access increases significantly.
Therefore, it is strongly recommended to combine password authentication with network-level controls. Specifically, use firewalls or security groups to allow access only from specific client IP addresses.
As an example, you can enable the firewall settings on a Linux server and configure it to allow connections only from authorized clients. By implementing such layered security measures, you can greatly reduce the risks associated with using JMX.
[root@quiz ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="106.42.143.133" port protocol="tcp" port="9010" accept'
success
[root@quiz ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="106.42.143.133" port protocol="tcp" port="9011" accept'
success
[root@quiz ~]# firewall-cmd --reload
success
[root@quiz ~]#Collecting information with jConsole
You can connect to Tomcat using jConsole, which is included with the JDK. Launch jConsole from the command prompt.
Microsoft Windows [Version 10.0.26100.6584]
(c) Microsoft Corporation. All rights reserved.
C:\Users\user>jconsole -J-Duser.language=en -J-Duser.country=US
C:\Users\user>If jConsole starts successfully as shown below, enter the required connection information under Remote Process and click Connect.

If you can see various resource metrics displayed as shown below, the connection has been established successfully.

It’s interesting to try putting some load on Tomcat and observe how the resource usage changes.
If you’re curious, check out my guide on how to perform load testing with JMeter:
How to Use Apache JMeter for Load Testing: Complete Beginner’s Guide
Note: If the JDK versions on the server and client differ, you may not be able to connect via jConsole. If possible, align the JDK versions on both sides.
Note: When connecting with a read-only user, some MBean information may not be accessible. In that case, try reconnecting with a user that has read-write permissions.
Periodic Output of Information Collected via JMX
With JMX, you can not only check information using GUI tools like jConsole, but also periodically collect data from the command line and output it to a file.
In this section, we’ll introduce how to use JMXTerm, a command-line tool that connects to Java applications via JMX to retrieve and manipulate MBean information.
Configuration for JMXTerm
Currently, JMXTerm does not appear to support SSL communication.
If SSL is enabled, you won’t be able to access JMX. Please disable the SSL configuration as shown below.
- Change
-Dcom.sun.management.jmxremote.registry.ssl=false \
-Dcom.sun.management.jmxremote.ssl=false \ - Delete
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false \
-Djavax.net.ssl.keyStore=… \
-Djavax.net.ssl.keyStorePassword=…
[root@quiz ~]# cat /opt/tomcat/bin/setenv.sh
export JAVA_HOME=/opt/java/jdk-21.0.2
export JRE_HOME=$JAVA_HOME
export PATH=$JAVA_HOME/bin:$PATH
#JMX setting
export JMX_KEYSTORE_PASS='JhB8MV2zyVpS'
# JMX setting
CATALINA_OPTS="$CATALINA_OPTS \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.rmi.port=9011 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.access.file=/opt/tomcat/conf/jmxremote.access \
-Dcom.sun.management.jmxremote.password.file=/opt/tomcat/conf/jmxremote.password \
-Dcom.sun.management.jmxremote.registry.ssl=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.local.only=false \
-Djava.rmi.server.hostname=quiz.eeengineer.com"
[root@quiz ~]#
[root@quiz ~]# systemctl restart tomcat
[root@quiz ~]# Downloading and Verifying JMXTerm
Create an appropriate directory and download the JMXTerm JAR file.
[root@quiz ~]# mkdir -p /opt/tomcat/tool
[root@quiz ~]# cd /opt/tomcat/tool/
[root@quiz tool]# wget https://github.com/jiaqi/jmxterm/releases/download/v1.0.0/jmxterm-1.0.0-uber.jar
--2025-10-15 06:01:01-- https://github.com/jiaqi/jmxterm/releases/download/v1.0.0/jmxterm-1.0.0-uber.jar
Resolving github.com (github.com)... 140.82.121.3
Connecting to github.com (github.com)|140.82.121.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://release-assets.githubusercontent.com/github-production-release-asset/1848246/442e150e-73c7-11e7-80ef-736971bc0400?sp=r&sv=2018-11-09&sr=b&spr=https&se=2025-10-14T21%3A53%3A54Z&rscd=attachment%3B+filename%3Djmxterm-1.0.0-uber.jar&rsct=application%2Foctet-stream&skoid=96c2d410-5711-43a1-aedd-ab1947aa7ab0&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skt=2025-10-14T20%3A53%3A18Z&ske=2025-10-14T21%3A53%3A54Z&sks=b&skv=2018-11-09&sig=93LJ0%2BRENCbRq0iGRplUw%2F57rIW9mvv7fPS0J52yANk%3D&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmVsZWFzZS1hc3NldHMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwia2V5Ijoia2V5MSIsImV4cCI6MTc2MDQ3NTk2MiwibmJmIjoxNzYwNDc1NjYyLCJwYXRoIjoicmVsZWFzZWFzc2V0cHJvZHVjdGlvbi5ibG9iLmNvcmUud2luZG93cy5uZXQifQ.SkugKOim30eOHxq2krHbkACsNZa_GJPYjERrUfH3DBY&response-content-disposition=attachment%3B%20filename%3Djmxterm-1.0.0-uber.jar&response-content-type=application%2Foctet-stream [following]
--2025-10-15 06:01:02-- https://release-assets.githubusercontent.com/github-production-release-asset/1848246/442e150e-73c7-11e7-80ef-736971bc0400?sp=r&sv=2018-11-09&sr=b&spr=https&se=2025-10-14T21%3A53%3A54Z&rscd=attachment%3B+filename%3Djmxterm-1.0.0-uber.jar&rsct=application%2Foctet-stream&skoid=96c2d410-5711-43a1-aedd-ab1947aa7ab0&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skt=2025-10-14T20%3A53%3A18Z&ske=2025-10-14T21%3A53%3A54Z&sks=b&skv=2018-11-09&sig=93LJ0%2BRENCbRq0iGRplUw%2F57rIW9mvv7fPS0J52yANk%3D&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmVsZWFzZS1hc3NldHMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwia2V5Ijoia2V5MSIsImV4cCI6MTc2MDQ3NTk2MiwibmJmIjoxNzYwNDc1NjYyLCJwYXRoIjoicmVsZWFzZWFzc2V0cHJvZHVjdGlvbi5ibG9iLmNvcmUud2luZG93cy5uZXQifQ.SkugKOim30eOHxq2krHbkACsNZa_GJPYjERrUfH3DBY&response-content-disposition=attachment%3B%20filename%3Djmxterm-1.0.0-uber.jar&response-content-type=application%2Foctet-stream
Resolving release-assets.githubusercontent.com (release-assets.githubusercontent.com)... 185.199.111.133, 185.199.108.133, 185.199.110.133, ...
Connecting to release-assets.githubusercontent.com (release-assets.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2069780 (2.0M) [application/octet-stream]
Saving to: ‘jmxterm-1.0.0-uber.jar’
jmxterm-1.0.0-uber.jar 100%[=====================================================================================================================================>] 1.97M --.-KB/s in 0.09s
2025-10-15 06:01:02 (22.5 MB/s) - ‘jmxterm-1.0.0-uber.jar’ saved [2069780/2069780]
[root@quiz tool]# ll jmxterm-1.0.0-uber.jar
-rw-r--r-- 1 root root 2069780 Dec 7 2021 jmxterm-1.0.0-uber.jar
[root@quiz tool]#Run the JAR file using the java -jar command.
[root@quiz tool]# java -jar jmxterm-1.0.0-uber.jar
Welcome to JMX terminal. Type "help" for available commands.
$>Specify the port number configured in the previous JMXTerm setup, and access JMX as shown below.
$>open 127.0.0.1:9012 -u adminRole -p 'vYd$Rd-M34mf'
#Connection to 127.0.0.1:9012 is opened
$>By executing the beans command, you can display a list of all available MBean information.
$>beans
#domain = Catalina:
Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,j2eeType=Filter,name=Tomcat WebSocket (JSR356) Filter
Catalina:J2EEApplication=none,J2EEServer=none,WebModule=//localhost/,j2eeType=Filter,name=characterEncodingFilter
... [output truncated] ...
Catalina:type=StringCache
Catalina:type=UtilityExecutor
#domain = JMImplementation:
JMImplementation:type=MBeanServerDelegate
#domain = Users:
Users:database=UserDatabase,type=UserDatabase
#domain = com.sun.management:
com.sun.management:type=DiagnosticCommand
com.sun.management:type=HotSpotDiagnostic
#domain = java.lang:
java.lang:name=CodeCacheManager,type=MemoryManager
java.lang:name=CodeHeap 'non-nmethods',type=MemoryPool
java.lang:name=CodeHeap 'non-profiled nmethods',type=MemoryPool
java.lang:name=CodeHeap 'profiled nmethods',type=MemoryPool
java.lang:name=Compressed Class Space,type=MemoryPool
java.lang:name=G1 Concurrent GC,type=GarbageCollector
java.lang:name=G1 Eden Space,type=MemoryPool
java.lang:name=G1 Old Gen,type=MemoryPool
java.lang:name=G1 Old Generation,type=GarbageCollector
java.lang:name=G1 Survivor Space,type=MemoryPool
java.lang:name=G1 Young Generation,type=GarbageCollector
java.lang:name=Metaspace Manager,type=MemoryManager
java.lang:name=Metaspace,type=MemoryPool
java.lang:type=ClassLoading
java.lang:type=Compilation
java.lang:type=Memory
java.lang:type=OperatingSystem
java.lang:type=Runtime
java.lang:type=Threading
#domain = java.nio:
java.nio:name=direct,type=BufferPool
java.nio:name=mapped - 'non-volatile memory',type=BufferPool
java.nio:name=mapped,type=BufferPool
#domain = java.util.logging:
java.util.logging:type=Logging
#domain = jdk.management.jfr:
jdk.management.jfr:type=FlightRecorder
$>
As an example, let’s collect information about the heap memory.
>$>domain java.lang
#domain is set to java.lang
$>bean java.lang:type=Memory
#bean is set to java.lang:type=Memory
$>get HeapMemoryUsage
#mbean = java.lang:type=Memory:
HeapMemoryUsage = {
committed = 114294784;
init = 60817408;
max = 958398464;
used = 58177952;
};
$>You should be able to obtain the following details:
- init: The amount of heap memory allocated when the JVM starts
- used: The amount of heap memory currently in use
- committed: The amount of memory reserved by the JVM from the OS
(used ≤ committed ≤ max) - max: The maximum heap size available to the JVM
Periodic Script Execution
By running JMXTerm periodically through a shell script, you can output information such as memory usage and thread count to a file at regular intervals.
The following example retrieves memory usage every minute and appends it to a log file.
[root@quiz ~]# mkdir -p /opt/scripts
[root@quiz ~]# mkdir -p /var/log/jmx
[root@quiz ~]# vi /opt/scripts/jmx_heap_memory.sh
[root@quiz ~]# cat /opt/scripts/jmx_heap_memory.sh
#!/bin/bash
LOG_FILE="/var/log/jmx/jmx_metrics.log"
JMXTERM="/opt/tomcat/tool/jmxterm-1.0.0-uber.jar"
echo "===== $(date) =====" >> "$LOG_FILE"
java -jar "$JMXTERM" -l quiz.eeengineer.com:9010 \
-u adminRole -p 'vYd$Rd-M34mf' -n -v silent <<EOF >> "$LOG_FILE"
sleep 3
bean java.lang:type=Memory
get HeapMemoryUsage
exit
EOF
echo "" >> "$LOG_FILE"
[root@quiz ~]#
[root@quiz ~]# chmod +x /opt/scripts/jmx_heap_memory.sh
[root@quiz ~]# ls -l /opt/scripts/jmx_heap_memory.sh
-rwxr-xr-x 1 root root 306 Oct 16 06:03 /opt/scripts/jmx_heap_memory.sh
[root@quiz ~]#Next, edit the crontab to schedule the script to run every minute.
[root@quiz ~]# crontab -e
no crontab for root - using an empty one
crontab: installing new crontab
[root@quiz ~]# crontab -l
* * * * * /opt/scripts/jmx_heap_memory.sh
[root@quiz ~]#Finally, verify that the information is being successfully written to the log.
[root@quiz ~]# tail -f /var/log/jmx/jmx_metrics.log
===== Fri Oct 17 06:52:01 AM JST 2025 =====
HeapMemoryUsage = {
committed = 99614720;
init = 60817408;
max = 958398464;
used = 89295816;
};
===== Fri Oct 17 06:53:01 AM JST 2025 =====
HeapMemoryUsage = {
committed = 99614720;
init = 60817408;
max = 958398464;
used = 89295816;
};Summary
In this article, I explained how to enable JMX on Apache Tomcat and perform remote monitoring as well as periodic data collection. The key points are as follows:
Enabling JMX
- Set
CATALINA_OPTSinsetenv.shto enable remote management - Configure ports, authentication, RMI, and SSL appropriately
Authentication and Security
- Manage access permissions and users via
jmxremote.accessandjmxremote.password - Use a firewall to allow access only from specific IP addresses
SSL Certificates
- Encrypt communication using either self-signed or CA-issued certificates
- Ensure secure connections between the server and clients
Monitoring and Periodic Collection
- Use jConsole or JMXTerm to check memory, thread, and GC status
- Periodically log heap usage and other metrics using scripts and cron
By following these steps, you can safely monitor Tomcat’s operational status and leverage it for early detection of issues and improved operational efficiency.


コメント