背景
最近排查一个JMX本地连接问题,记录一下。
我们的启动脚本在应用启动后,会通过JMX来动态检查应用状态,那么这里就需要动态启动JMX功能了。
动态打开Java进程的JMX端口
- 通过下面的代码,可以动态的让目标进程加载
management-agent
- 打开JMX功能后,通过获取
com.sun.management.jmxremote.localConnectorAddress
的Agent Property,可以获取到JMX URL
1 | public MBeanServerConnection connect(String pid) throws IOException { |
为什么JMX连接会失败?
在用上面的代码动态去连接目标进程时,抛出了下面的异常:
1 | java.rmi.ConnectException: Connection refused to host: 11.164.235.11; nested exception is: |
- 检查本机IP是
11.164.234.171
- 为什么rmi连接的是一个外部的IP
11.164.235.11
?
通过调试,发现management-agent
加载成功了,localConnectorAddress
的值是:
1 | jmx:rmi://127.0.0.1/stub/rO0ABXN9AAAAAQAlamF2YXgubWFuYWdlbWVudC5yZW1vdGUucm1pLlJNSVNlcnZlcnhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAC1qYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN0SW52b2NhdGlvbkhhbmRsZXIAAAAAAAAAAgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc4AAtVbmljYXN0UmVmMgAADTExLjE2NC4yMzUuMTEAAIfoCEScYyGQodFlwEdFAAABawK/zE6AAQB4 |
为什么显示的是127.0.0.1
,但实际连接的是11.164.235.11
?是不是在连接时出的问题?
再仔细调试,发现
- jmx是获取到stub后的字符串
- 做base64解密,再通过
ObjectInputStream
解析 readObject
得到RMIServer
对象来连接的。
1 | //javax.management.remote.rmi.RMIConnector.findRMIServer(JMXServiceURL, Map<String, Object>) |
通过代码处理,发现
1 | rO0ABXN9AAAAAQAlamF2YXgubWFuYWdlbWVudC5yZW1vdGUucm1pLlJNSVNlcnZlcnhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAC1qYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN0SW52b2NhdGlvbkhhbmRsZXIAAAAAAAAAAgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc4AAtVbmljYXN0UmVmMgAADTExLjE2NC4yMzUuMTEAAIfoCEScYyGQodFlwEdFAAABawK/zE6AAQB4 |
转换为了:
1 | RMIServerImpl_Stub[UnicastRef2 [liveRef: [endpoint:[11.164.235.11:26449](remote),objID:[-5ddae53d:16b0887d710:-7fff, 7209064096623493021]]]] |
可见RMI Server的IP的确是11.164.235.11
。
那么现在问题变成了:
- 为什么JVM动态加载了
management-agent
,得到的JMX URL是指向外部IP的?
通过heap dump定位IP字符串
但是调试management-agent
的加载过程可能会比较痛苦,于是考虑从别的地方入手。
从上面的调查里,发现management-agent
启动之后,11.164.235.11
这个外部IP就会出现在JVM内存里,那么考虑用heap dump的方式来定位。
通过执行heap dump,再用jvisualvm
来分析。
用OQL来搜索所有包含11.164.235.11
的String:
1 | select s from java.lang.String s where s.toString().equals("11.164.235.11") |
可以发现有好几个结果:
再依次点开,查看引用,发现其中一个引用的字段名是localHost
:
因此可以猜测:是不是localHost域名解析有问题?
定位localHost域名解析问题
执行hostname命令,得到机器名,再ping一下:
1 | $hostname |
发现本机被解析到11.164.235.11
了,但是本机的IP是11.164.234.171
:
1 | $ifconfig |
到这里,大概猜到原因了,检查下 /etc/hosts
文件,果然发现有配置:
1 | 11.164.235.11 web-app201641.we42 |
把这个错误的host配置去掉之后,再执行jmx连接终于成功了。
为什么会有错误的hosts配置呢?据说是机器迁移时遗留的。
总结
动态JMX连接的工作原理:
- 让目标
VirtualMachine
动态加载management-agent
- 从Agent Properties里获取到JMX连接地址:
com.sun.management.jmxremote.localConnectorAddress
- JMX URL里带
stub
的字符串,实际上是base64转换为byte[],再用ObjectInputStream
转换为RMIServer
- JMX实际上是通过RMI来连接的
排查问题的关键:
- 定位错误连接的IP
- heap dump
- 用OQL从heap dump里查找IP字符串,再查看相关的引用来获取信息