visualVM远程调试JMX的最新正确方法,踩坑无数终于可以了 visualVMJMX
- 创建时间:2025-01-10 / 最新修改时间:2025-01-10 11:00:15
- 13
- 0
VisualVM 远程调试最新正确的方法
最近一个项目发现内存溢出,这个项目是在docker环境下运行的。由于使用jmap不太方便,想到用visualVM,于是想着开启JMX进行远程监控。但是网上搜索很多文章,都是寥寥几句说添加几个参数就行。结果折腾了2天都不行。于是墙外找帮助,终于则腾出来了。
在这里先贴出几个重点:
-Dcom.sun.management.jmxremote.host 写docker容器IP地址,这里最好docker在启动容器时指定IP地址
-Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.rmi.port=5000
不要以为端口只设置第一条就行,第二条也要,端口要一样!
-Djava.rmi.server.hostname 这里是关键!!这里写在docker下hostname 查看到的名称,最好也是在启动容器的时候用--hostname=xxx指定! 如果这里不正确,则dockers里面连监控端口都会没有正常打开!
运行visualVM的机器要设置一下host!
跟jdk版本无关!
跟springboot是否开启jmx无关!
跟docker容器的系统无关!
docker 容器只需要映射 JMX 的一个监听端口就行,并没有所谓随机端口存在!
visualVM的日志不可信!jconsole.exe -debug 更加可信!
先发成功后的截图:
步骤
dockers容器配置
新建docker网络
创建容器
docker run -d --name TEST -e "TZ=Asia/Shanghai" --restart=always \
--hostname=JMXTEST \
-p 18099:18083 \
-p 5000:5000 \
-v /home/data_convert2:/usr/src/ \
--log-opt max-size=10m --log-opt max-file=5 \
--net mynet --ip 172.21.0.2 \
openjdk11:nettool java \
-Dcom.sun.management.jmxremote.host=172.21.0.2 \
-Dcom.sun.management.jmxremote.port=5000 \
-Dcom.sun.management.jmxremote.rmi.port=5000 \
-Djava.rmi.server.hostname=JMXTEST \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar /usr/src/app.jar -Dspring.jmx.enabled --spring.profiles.active=prd \
注意上面的 hostname 和ip地址和端口映射问题!
因为openjdk的镜像默认不带net-tolls包,我自己创了一个容器带net-tools的,方便用
netstat -na
查看端口情况。
容器运行后,进去容器查看端口情况
5000 端口是成功打开了。如果没有到这个端口,检查 Dcom.sun.management.jmxremote.host
、-Dcom.sun.management.jmxremote.port
、-Dcom.sun.management.jmxremote.rmi.port
的正确性!
尝试连接
按网上的教程,一般到这这一步就让你直接用visualVM连接就可以了。于是你会发现:
查看日志文件C:\Users\Administrator\AppData\Roaming\VisualVM\2.1.10\var\log
你会看到这样的报错:
FINE [org.graalvm.visualvm.jmx.impl.ProxyClient]: connect(service:jmx:rmi:///jndi/rmi://10.10.20.31:5000/jmxrmi)
java.io.EOFException: SSL peer shut down incorrectly
at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:480)
at sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:469)
at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:159)
at sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1320)
Caused: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1511)
at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1328)
at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1233)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:417)
at sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:837)
at sun.security.ssl.SSLSocketImpl.access$200(SSLSocketImpl.java:76)
at sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1138)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at java.io.DataOutputStream.flush(DataOutputStream.java:123)
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:229)
Caused: java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is:
javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:307)
at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)
at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:342)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:116)
at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:132)
Caused: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is:
javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake]
at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:136)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:217)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at javax.management.remote.rmi.RMIConnector.findRMIServerJNDI(RMIConnector.java:1955)
at javax.management.remote.rmi.RMIConnector.findRMIServer(RMIConnector.java:1922)
at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:287)
Caused: java.io.IOException: Failed to retrieve RMIServer stub
at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:369)
at org.graalvm.visualvm.jmx.impl.ProxyClient.tryConnect(ProxyClient.java:296)
报错重点SSL peer shut down incorrectly
,意思是ssl连接失败,但是我们明明配置了禁用ssl。我们在看看docker容器的端口情况
好像是连接上了,是不是好奇怪!不急,重点就在这里,其实这个日志提示不准确!我们用jconsole看看。
用命令行端口 jconsole
jconsole.exe -debug
点击连接后一会弹出box,再点击使用不安全连接 ,然后看debug窗口
java.rmi.UnknownHostException: Unknown host: JMXTEST; nested exception is:
java.net.UnknownHostException: JMXTEST
at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:616)
at java.rmi/sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:209)
at java.rmi/sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:196)
at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:129)
at java.management.rmi/javax.management.remote.rmi.RMIServerImpl_Stub.newClient(Unknown Source)
at java.management.rmi/javax.management.remote.rmi.RMIConnector.getConnection(RMIConnector.java:2105)
at java.management.rmi/javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:321)
at jdk.jconsole/sun.tools.jconsole.ProxyClient.tryConnect(ProxyClient.java:355)
at jdk.jconsole/sun.tools.jconsole.ProxyClient.connect(ProxyClient.java:313)
at jdk.jconsole/sun.tools.jconsole.VMPanel$2.run(VMPanel.java:296)
重点来了,原来rmi时通过hostname连接的。我猜原理是,先通过ip:5000连接到容器,然后服务端返回hostname,然后监控端再根据hostname连接过去!
那么问题就在这里了,我们监控机压根不知道这个hostname。那么我们自己改下host就可以了
#把这个添加到host文件上ip地址改成docker宿主地址
xxx.xxx.xxx.xxx JMXTEST
添加完后,先再用jconsole试试
成功!
再用visualVM连接
至此!折腾到这里完事!
更新内容:
后来经过测试,docker运行参数这样也可以,好处就是不用指定ip不用创建docker网络
docker run -d --name TEST -e "TZ=Asia/Shanghai" --restart=always \
--hostname=JMXTEST \
-p 18099:18083 \
-p 5000:5000 \
-v /home/data_convert2:/usr/src/ \
--log-opt max-size=10m --log-opt max-file=5 \
openjdk11:nettool java \
-Dcom.sun.management.jmxremote.host=0.0.0.0 \
-Dcom.sun.management.jmxremote.port=5000 \
-Dcom.sun.management.jmxremote.rmi.port=5000 \
-Djava.rmi.server.hostname=JMXTEST \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar /usr/src/app.jar --spring.profiles.active=prd \