上一节《浅谈JPDA中JVMTI模块》讲解了JVMTI功能做用,本节咱们将经过一个具体的例子,来阐述如何开发一个简单的 Agent 。Agent主要功能描述:java
经过 C++ 编写,监听 JVMTI_EVENT_METHOD_ENTRY 事件,注册对应的回调函数来响应这个事件,来输出全部被调用函数名;linux
具体实现都在 MethodTraceAgent 这个类里提供。按照顺序,它会处理环境初始化、参数解析、注册功能、注册事件响应,每一个功能都被抽象在一个具体的函数里。ios
MethodTraceAgent.h 代码以下:bash
#include "jvmti.h"
class AgentException
{
public:
AgentException(jvmtiError err) {
m_error = err;
}
char* what() const throw() {
return "AgentException";
}
jvmtiError ErrCode() const throw() {
return m_error;
}
private:
jvmtiError m_error;
};
class MethodTraceAgent
{
public:
MethodTraceAgent() throw(AgentException){}
~MethodTraceAgent() throw(AgentException);
void Init(JavaVM *vm) const throw(AgentException);
void ParseOptions(const char* str) const throw(AgentException);
void AddCapability() const throw(AgentException);
void RegisterEvent() const throw(AgentException);
static void JNICALL HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method);
private:
static void CheckException(jvmtiError error) throw(AgentException)
{
// 能够根据错误类型扩展对应的异常,这里只作简单处理
if (error != JVMTI_ERROR_NONE) {
throw AgentException(error);
}
}
static jvmtiEnv * m_jvmti;
static char* m_filter;
};
复制代码
MethodTraceAgent.cpp 代码以下:jvm
#include <iostream>
#include "MethodTraceAgent.h"
#include "jvmti.h"
using namespace std;
jvmtiEnv* MethodTraceAgent::m_jvmti = 0;
char* MethodTraceAgent::m_filter = 0;
MethodTraceAgent::~MethodTraceAgent() throw(AgentException)
{
// 必须释放内存,防止内存泄露
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(m_filter));
}
void MethodTraceAgent::Init(JavaVM *vm) const throw(AgentException){
jvmtiEnv *jvmti = 0;
jint ret = (vm)->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0);
if (ret != JNI_OK || jvmti == 0) {
throw AgentException(JVMTI_ERROR_INTERNAL);
}
m_jvmti = jvmti;
}
void MethodTraceAgent::ParseOptions(const char* str) const throw(AgentException)
{
if (str == 0)
return;
const size_t len = strlen(str);
if (len == 0)
return;
// 必须作好内存复制工做
jvmtiError error;
error = m_jvmti->Allocate(len + 1,reinterpret_cast<unsigned char**>(&m_filter));
CheckException(error);
strcpy(m_filter, str);
// 能够在这里进行参数解析的工做
// ...
}
void MethodTraceAgent::AddCapability() const throw(AgentException)
{
// 建立一个新的环境
jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_generate_method_entry_events = 1;
// 设置当前环境
jvmtiError error = m_jvmti->AddCapabilities(&caps);
CheckException(error);
}
void MethodTraceAgent::RegisterEvent() const throw(AgentException)
{
// 建立一个新的回调函数
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.MethodEntry = &MethodTraceAgent::HandleMethodEntry;
// 设置回调函数
jvmtiError error;
error = m_jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
CheckException(error);
// 开启事件监听
error = m_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, 0);
CheckException(error);
}
void JNICALL MethodTraceAgent::HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method)
{
try {
jvmtiError error;
jclass clazz;
char* name;
char* signature;
// 得到方法对应的类
error = m_jvmti->GetMethodDeclaringClass(method, &clazz);
CheckException(error);
// 得到类的签名
error = m_jvmti->GetClassSignature(clazz, &signature, 0);
CheckException(error);
// 得到方法名字
error = m_jvmti->GetMethodName(method, &name, NULL, NULL);
CheckException(error);
// 根据参数过滤没必要要的方法
if(m_filter != 0){
if (strcmp(m_filter, name) != 0)
return;
}
cout << signature<< " -> " << name << "(..)"<< endl;
// 必须释放内存,避免内存泄露
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(name));
CheckException(error);
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
CheckException(error);
} catch (AgentException& e) {
cout << "Error when enter HandleMethodEntry: " << e.what() << " [" << e.ErrCode() << "]";
}
}
复制代码
Agent_OnLoad 函数 会在 Agent 被加载的时候建立这个类,并依次调用上述各个方法,从而实现这个 Agent 的功能。Agent.cpp 代码以下:函数
#include <iostream>
#include "MethodTraceAgent.h"
#include "jvmti.h"
using namespace std;
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
cout << "Agent_OnLoad(" << vm << ")" << endl;
try{
MethodTraceAgent* agent = new MethodTraceAgent();
agent->Init(vm);
agent->ParseOptions(options);
agent->AddCapability();
agent->RegisterEvent();
} catch (AgentException& e) {
cout << "Error when enter HandleMethodEntry: " << e.what() << " [" << e.ErrCode() << "]";
return JNI_ERR;
}
return JNI_OK;
}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
cout << "Agent_OnUnload(" << vm << ")" << endl;
}
复制代码
Agent运行过程时序图,如图所示: ui
Agent 编译很是简单,和编译普通的动态连接库没有本质区别,只是须要将 JDK 提供的一些头文件包含进来。spa
Windows:设计
cl /EHsc -I${JAVA_HOME}\include\ -I${JAVA_HOME}\include\win32
-LD MethodTraceAgent.cpp Main.cpp -FeAgent.dll
复制代码
Linux:code
g++ -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/linux
MethodTraceAgent.cpp Agent.cpp -fPIC -shared -o libagent.so
复制代码
提供了一个可运行的 Java 类 MethodTraceTest.java,代码以下:
public class MethodTraceTest {
public static void main(String[] args){
MethodTraceTest test = new MethodTraceTest();
test.first();
test.second();
}
public void first(){
System.out.println("=> Call first()");
}
public void second(){
System.out.println("=> Call second()");
}
}
复制代码
默认运行后输出,结果以下:
如今,运行程序前告诉 Java 先加载编译出来的 Agent:
java -agentlib:Agent=first MethodTraceTest
复制代码
带有Agent运行后输出,结果以下:
当程序运行到到 MethodTraceTest 的 first 方法时,Agent 会输出这个事件。“ first ”是 Agent 运行的参数,若是不指定话,全部的进入方法的触发的事件都会被输出,若是把这个参数去掉再运行的话,会发如今运行 main 函数前,已经有很是基本的类库函数被调用了。