linux下jcmd无法获取jvmdump

现象:

前两天在linux上的服务出现莫名其妙的内存溢出.却发现无法用jcmd连接jvm获取dump.现象:

[root@host-12.131.14.15 bin]# ./jcmd 19652 GC.heap_dump 
19652:
com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
 at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106)
 at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63)
 at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208)
 at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:147)
 at sun.tools.jcmd.JCmd.main(JCmd.java:131)

解决方案:

如果启动用户不是同一个,切换成同一用户.
使用命令如下:
sudo -u [userid] /jcmd 19652 GC.heap_dump 
 
如果启动用户已经是同一个还报错,则去/usr/lib/systemd/system/   服务位置 ,观察服务对应的PrivateTmp属性是否为true.具体如下:
-----------------------
[unit]
description=xxx
[Service]
Type=forking
ExecStartPre=/
ExecStart=
ExecStop=
PrivateTmp=true
[Install]
WantedBy=multi-user.target
-------------------------
 
若PrivateTmp为true,改为false,并使用如下命令刷新服务即可.
--------------------------
systemctl daemon-reload
systemctl restart [servicename]
systemctl status [servicename]
---------------------------

原理解析:

jcmd原理

  • 当使用此命令dump内存时.在连接对应java进程的pid之前,将会jcmd会生成一个.attach_pid在目标程序的工作目录或者/tmp.
  • 然后jcmd发送SIGQUIT到目标进程.当虚拟机获取到这个信号并且发现了.attache_pid,将会开启一个AttachListener 进程.
  • AttachListener 进程使用UNIX 的socket/tmp/.java_pid去和jcmd工具打交道
  • 考虑到安全原因,当一个连接(从jcmd发出的)被接收后,虚拟机会检查socket连接的创建的用户是否和jvm进程的euid或egid一致.这是为jcmd在不同用户的情况不工作的原因.(root的情况也不可使用)
  • jcmd连接上socket后,将会收到dumpheap.

privateTmp

  • 本问题排查时,其实用户已经是同一个用户,但是获取不到,是因为服务的privateTmp机制.当service unit中的privateTmp设置为true时,service会将$tmp_file放在linux的tmp/systemd-private-xxxxx-[servicename].service/xxx中.
  • privateTmp用于设置是否使用私有的tmp目录,那么只要设置使用这个属性的service,都会使用私有的tmp目录。 比如说:   nginx会有一个systemd-private-xxx-nginx.service/tmp目录 
  • 默认的/tmp目录一般所有用户的所有service共享的,对于所有用户及用户运行的程序来说来说,都会有读和写的权限.会存在一些安全性问题.把各个service的tmp目录隔离开的话,可以保证一定的安全性.
  • 对于这个jcmd无法heapdump的问题,在能确认服务器安全的情况下,完全可以考虑关闭掉该配置项.当然,如果服务器安全得不到保障的情况下,或者应用在跑不能重启的情况下,可以通过更改service unit中的execStart execStop对应中的脚本来解决.
例如,我们服务的命令如下:
-----------------------
[unit]
description=xxx
[Service]
Type=forking
ExecStartPre=/
ExecStart=/opt/app/start.sh
ExecStop=/opt/app/stop.sh
PrivateTmp=true
[Install]
WantedBy=multi-user.target
-------------------------
可以通过修改/opt/app/stop.sh脚本,通过stop.sh中的
./jcmd 19652 GC.heap_dump /opt/xxx/servicedump.hprof 来获取dump.

参考说明:

  1. privateTmp介绍 https://www.cnblogs.com/lihuobao/p/5624071.html
  2. https://lists.centos.org/pipermail/centos/2015-April/151589.html
  3. http://0pointer.de/blog/projects/security.html
  4. https://access.redhat.com/blogs/766093/posts/1976243

 

 

posted @ 2018-11-04 21:16  Alcc  阅读(1930)  评论(0编辑  收藏  举报