目录
perf-map-agent会生成一个/tmp/perf-<pid>.map映射文件,里面包含被JIT即时编译器编译过的Java方法的方法名与编译后生成的机器码的地址映射,perf命令在获取未知的内存地址对应的方法名时会默认读取该文件,从而将perf命令采集到热点方法的内存地址转换成对应的方法名。perf-map-agent的代码包含两部分内容,一个是用C写的agent,负责生成映射文件,一个是Java写的jar包,负责将C写的agent attach到Java进程上,下面来详细研究这两部分的实现源码。
1、create-java-perf-map.sh
其主要逻辑是校验入参中pid对应的Java进程是否存在,校验JAVA_HOME是否存在,最后执行Java命令attach到目标进程上。其实现如下:
#!/bin/bash
#设置shell的执行模式,-e表示若指令传回值不等于0,则立即退出shell。
set -e
#set -x
CUR_DIR=`pwd`
PID=$1
OPTIONS=$2
ATTACH_JAR=attach-main.jar
#获取当前脚本所在的具体位置
PERF_MAP_DIR="$(cd "$(dirname "$0")" && pwd -P)"/..
ATTACH_JAR_PATH=$PERF_MAP_DIR/out/$ATTACH_JAR
PERF_MAP_FILE=/tmp/perf-$PID.map
#uname命令获取当前操作系统的类型
if [[ `uname` == 'Linux' ]]; then
LINUX=1;
else
LINUX=2;
fi
if [[ "$LINUX" == "1" ]]; then
#如果是linux系统,判断对应的进程ID是否存在
if [ ! -d /proc/$PID ]; then
echo "PID $PID not found"
exit 1
fi
#获取uid和gid
TARGET_UID=$(awk '/^Uid:/{print $2}' /proc/$PID/status)
TARGET_GID=$(awk '/^Gid:/{print $2}' /proc/$PID/status)
fi
if [ -z "$JAVA_HOME" ]; then
if [[ "$LINUX" == "1" ]]; then
#如果是Linux系统,判断JAVA_HOME是否存在
JAVA_HOME=/usr/lib/jvm/default-java
#如果不存在则指定为
[ -d "$JAVA_HOME" ] || JAVA_HOME=/etc/alternatives/java_sdk
else
JAVA_HOME=`/usr/libexec/java_home -v 1.8`
fi
fi
#校验JAVA_HOME,不存在则打印日志并返回false,退出
[ -d "$JAVA_HOME" ] || (echo "JAVA_HOME directory at '$JAVA_HOME' does not exist." && false)
if [[ "$LINUX" == "1" ]]; then
#移除原来的映射文件
sudo rm $PERF_MAP_FILE -f
#通过Java命令attach到目标进程上,-cp指定查找目标类的文件目录或者jar包
##注意多个命令行参数是作为一个参数传递给java命令的
(cd $PERF_MAP_DIR/out && sudo -u \#$TARGET_UID -g \#$TARGET_GID $JAVA_HOME/bin/java -cp $ATTACH_JAR_PATH:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce $PID "$OPTIONS")
sudo chown root:root $PERF_MAP_FILE
else
#移除原来的映射文件
rm -f $PERF_MAP_FILE
(cd $PERF_MAP_DIR/out && $JAVA_HOME/bin/java -cp $ATTACH_JAR_PATH:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce $PID "$OPTIONS")
fi
2、AttachOnce
AttachOnce是负责attach到目标Java进程的Java类,主要逻辑就是校验通过C编写的agent是否存在,如果存在则通过tools.jar中的VirtualMachine类attach到目标进程上,源码如下:
package net.virtualvoid.perf;
import java.io.File;
import com.sun.tools.attach.VirtualMachine;
import java.lang.management.ManagementFactory;
import java.util.Locale;
public class AttachOnce {
public static void main(String[] args) throws Exception {
String pid = args[0];
String options = "";
if (args.length > 1) options = args[1];
//获取启动参数
loadAgent(pid, options);
}
static void loadAgent(String pid, String options) throws Exception {
//attach到目标进程上
VirtualMachine vm = VirtualMachine.attach(pid);
try {
final File lib;
if (System.getProperty("os.name", "").toLowerCase(Locale.US).contains("os x")) {
//如果是非linux系统
lib = new File("libperfmap.dylib");
} else {
//如果是linux系统,该文件正常是跟jar包在同一个目录下
lib = new File("libperfmap.so");
}
String fullPath = lib.getAbsolutePath();
if (!lib.exists()) {
//如果文件不存在则退出
System.out.printf("Expected %s at '%s' but it didn't exist.\n", lib.getName(), fullPath);
System.exit(1);
}
//如果文件存在则执行特定逻辑
else vm.loadAgentPath(fullPath, options);
} c