JVM远程监控

Demon.Lee 2021年01月31日 1,583次浏览

声明:相关运行样例均使用JDK 8

JMX

JMX(Java Management Extension)在官网的介绍是:

The JMX technology provides the tools for building distributed, Web-based, modular and dynamic solutions for managing and monitoring devices, applications, and service-driven networks. By design, this standard is suitable for adapting legacy systems, implementing new management and monitoring solutions, and plugging into those of the future.

这里我们可以简单理解为:JMX是Java平台为设备、应用程序、服务等植入监控和管理功能的一个标准框架。其中一个比较基础的概念就是MBean(Management Bean),它是JavaBean的一种,用于管理Java Object。JMX不是这篇文章的重点,回头我再写一些基础文章继续介绍。

JVM监控

我们知道,jdk中自带了很多jvm的监控工具,比如jstat,jvisualvm,jconsole等。

如果是本地程序,直接运行对应的命令就可以观察了,比如:

➜  2021 >jps
21248
21857 Launcher
21864 Jps
21855 MainTest
➜  2021 >jvisualvm --openpid 21855
2021-01-29 08:35:34.783 java[22253:518950] *** WARNING: Textured window <AWTWindow_Normal: 0x7fcbf1fa3ba0> is getting an implicitly transparent titlebar. This will break when linking against newer SDKs. Use NSWindow's -titlebarAppearsTransparent=YES instead.
➜  2021 >
➜  2021 >jconsole 21855
➜  2021 >

但如果我们需要观察测试或生产等远程环境的Java进程时,就需要在启动java进程时启动JVM参数,示例如下:

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=<port> -Djava.rmi.server.hostname=<IPAddress> -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar xxx.jar

然后就可以通过终端访问了,比如:

# 当然也可以直接使用jconsole不带参数,然后在可视化窗口中填入对应的地址。
➜  2021 >jconsole 192.168.1.22:9001

官网上对相关配置参数有详细的说明,我这里做了一个简单的复制:

option description default value example
com.sun.management.jmxremote 允许远程JMX client 访问本机jvm true -Dcom.sun.management.jmxremote
com.sun.management.jmxremote.port 允许远程JMX client 访问本机jvm的端口 -Dcom.sun.management.jmxremote.port=9009
com.sun.management.jmxremote.authenticate 是否放开权限校验 -Dcom.sun.management.jmxremote.authenticate=false
com.sun.management.jmxremote.ssl 是否开启ssl校验 true -Dcom.sun.management.jmxremote.ssl=false
com.sun.management.jmxremote.login.config JMX agent to use the specified JAAS configuration entry, LDAP权限登录配置 -Dcom.sun.management.jmxremote.login.config=ExampleCompanyConfig
java.security.auth.login.config specifies the path to the JAAS configuration file, LDAP权限登录配置 -Djava.security.auth.login.config=ldap.config
com.sun.management.jmxremote.password.file 基于文件密码的登录校验配置, 设置访问用户的用户名和密码 默认路径JRE_HOME/lib/management/ jmxremote.password -Dcom.sun.management.jmxremote.password.file=pwFilePath
com.sun.management.jmxremote. registry.ssl Binds the RMI connector stub to an RMI registry protected by SSL false -Dcom.sun.management.jmxremote. registry.ssl=true
com.sun.management.jmxremote. ssl.enabled.protocols A comma-delimited list of SSL/TLS protocol versions to enable. Used in conjunction with com.sun.management.jmxremote.ssl
com.sun.management.jmxremote. ssl.enabled.cipher.suites A comma-delimited list of SSL/TLS cipher suites to enable. Used in conjunction with com.sun.management.jmxremote.ssl
com.sun.management.jmxremote. ssl.need.client.auth If this property is true and the property com.sun.management.jmxremote.ssl is also true, then client authentication will be performed.It is recommended that you set this property to true false
com.sun.management.jmxremote.access.file Specifies location for the access file,对访问用户的权限授权的文件的路径 默认路径JRE_HOME/lib/management/jmxremote.access

最近我在监控测试环境JVM的性能时,通过jconsole访问时,总是连接不上,Google之后找到了相关文章,才知道:

The initial port you define with 'com.sun.management.jmxremote.port' is called a registry port and is only used to start negotiation and determine next port(s) to use for "real" communication. Java RMI mechanism uses dynamically allocated ports and in general is not compatible with firewalls.

也就是说,上面配置的 com.sun.management.jmxremote.port= 只是给JMX client端服务注册使用,而具体的数据传输,会随机分配一个新端口,很有可能,这个端口被防火墙拦截了。而针对我的具体场景,我需要监控的java进程运行在docker容器中,这个随机分配的端口由于没有暴露出来,所以也就无法访问了。

还好从Java 7开始,官方给出了一个配置,将这个随机端口给固定住,并且建议其值与 com.sun.management.jmxremote.port 配置的值相同。

-Dcom.sun.management.jmxremote.rmi.port=<port>

果然,配置之后,问题就解决,完整示例如下:

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.rmi.port=<port> -Djava.rmi.server.hostname=<IPAddress>  -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar xxx.jar

如果是监控tomcat,则可以将上面的配置放入setenv.sh(位于${TomcatPath}/bin/)中:

export CATALINA_OPTS="${CATALINA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.rmi.port=<port> -Djava.rmi.server.hostname=<IPAddress> -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

相关资料中说 java.rmi.server.hostname 这项配置不是必须的,经过测试,确实可以省略。另外com.sun.management.jmxremote的默认值就是true,所以也可以省略,我测试了一个不考虑防火墙拦截的简化版例子如下:

java -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar xxx.jar

参考资料

  1. Spring: Cannot connect to a JMX Server using RMI from behind a firewall
  2. tomcat监控
  3. Why Java opens 3 ports when JMX is configured?
  4. How to access JMX interface in docker from outside?
  5. Monitoring and Management Using JMX Technology