探究基础命令JPS
javaer的世界中, jps是常用的命令。
sudo -u admin /opt/taobao/java/bin/jps -mlvV
输出如下:
很方便。我们可以知道当前用户下正在跑的java进程有哪些。但是这个jps是如何拿到进程数据的呢? 我们来一探究竟。
0X01 从哪里读进程数据?
首要的是拿到jps命令的源码。
这个很多人都知道。相关的代码被编译打包到了tools.jar中。但是我们当然还是想看这部分的源码。所以有必要去 http://openjdk.java.net/ 下载一份。
来看sun.tools.jps.Jps:
注意这一句 monitoredHost.activeVms()
。 经过debug发现这里已经拿到了实际的进程列表。实际负责获取进程数据的是sun.jvmstat.perfdata.monitor.protocol.local.LocalVmManager
。
那就进去看看sun.jvmstat.perfdata.monitor.protocol.local.LocalVmManager#activeVms
:
这里就很明显的可以看到了。jps命令在获取实际的进程ID时, 是去用户的临时目录下去拿进程ID的。具体的文件路径是:
tmp_dir + hsperfdata_user + pid
比如随意找一台日常机器:
mac上临时目录的位置不一样
0X02 谁写的进程数据?
好了,知道了是怎么读取之后,下一个要考虑的问题是,谁在什么时候把进程数据写到这里了?
我们已知现在写入的文件夹名称叫 hsperfdata_xxx。所以google一把。发现了以下的信息。
见https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
jvm中有如下命令
可以看出UsePerfData是用来控制是否生成这个perf文件的。默认会打开,当打开后便会生成hsperfdata_userid目录。所以现在的问题是, jvm是如何处理这个选项的?
见hotspot/src/share/vm/runtime/perfMemory.cpp
OK。 我们可以继续看看。create_memory_region方法在不同的操作系统中会有不同的实现。所以我们这里开看看linux对应的实现,在/hotspot/src/os/linux/vm/perfMemory_linux.cpp
这里的第一行PerfDisableSharedMem涉及到另外一个jvm的命令XX:+PerfDisableSharedMem
, 用来控制perfdata的这部分内存是否可以被共享, 默认关闭。如果打开的话后果和把-XX:+UsePerfData
关闭差不多,诸如jps,jstat,jmap等命令就没法用了。 直接看create_shared_memory()
:
OK, 继续
到这里情况差不多要明了。
int vmid = os::current_process_id(); 这里获取了当前的进程id。
get_user_name(geteuid()); 获取用户名
get_user_tmp_dir() 获取临时目录
get_sharedmem_filename() 获取对应的进程文件名, 文件名就是进程id
再来看看最后一步的create_sharedmem_resources方法, 其实大家看到fd这个变量就应该感知到这里是实际创建文件的地方了-_-
make_user_tmp_dir
确保用户临时目录存在::open(filename, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE)
C++自身的open函数来操作文件, 如果该文件不存在, 那就新建一个
好了。到这里基本上整个链路就很清楚了。最后顺便梳理一下关于写这个文件的c++部分的调用关系:
0X03 总结
hsperfdata_xxx文件夹很重要。存储了当前用户的进程数据。
要保证当前操作系统的临时文件目录对jvm用户可读可写。这包含两层意思。1).权限要match, 2).空间要够用
hsperfdata_xxx中的进程id文件也很重要。
A用户是否能看B用户的进程数据, sun在代码层面没有做控制。只是简单的判断了对方的进程文件是否有权限可读
file.canRead()
缺少了hsperfdata文件其实也没什么大不了的。但是会导致很多工具无法使用. 比如jps, jdb, jstat, jconsole等等。这些可都是排查问题的利器啊。貌似只有JMX是可以用。