java debug 体系-JVMTI

1 jvmti 构成、介绍

      JVMTI(JVM Tool Interface) 位于jpda 最底层, 是Java 虚拟机所提供的native编程接口。 JVMTI可以提供性能分析、debug、内存管理、线程分析等功能。

2 如何使用jvmti

       JVMTI是一套本地编程接口,因此使用JVMTI,需要与c/c++ 以及JNI打交道。事实上,开发时一般采用建立一个Agent的方式来使用JVMTI,Agent使用jvmti函数,设置一些回调函数,并从Java虚拟机中得到当前的运行态信息,并作出自己的判断, 最后还可能操作虚拟机的运行态。把Agent编译成一个动态链接库之后,可以再Java程序启动时来加载它(比如IDE调试时使用的libjdwp.so 就是采用这种方式),当然也可以通过Attach方式,中途加入(比如jmap, jps,jstack 等等)。

3 Agent的加载、设置回调、卸载流程

具体可以参考我之前的文章 jvmti agent的加载与回调函数的执行分析

4 JVMTI的环境

      使用 JVMTI 的过程,主要是设置 JVMTI 环境,监听虚拟机所产生的事件,以及在某些事件上加上我们所希望的回调函数。
      可以通过操作 jvmtiCapabilities 来查询、增加、修改 JVMTI 的环境参数。
      另外,虚拟机有自己的一些功能,一开始并未被启动,那么增加或修改 jvmtiCapabilities 也是可能的,但不同的虚拟机对这个功能的处理也不太一样,多数的虚拟机允许增改,但是有一定的限制,比如仅支持在 Agent_OnLoad 时,即虚拟机启动时作出,它某种程度上反映了虚拟机本身的构架。

5 jvmti 基本能力

      JVMTI 的功能非常丰富,包含了虚拟机中线程、内存 / 堆 / 栈,类 / 方法 / 变量,事件 / 定时器处理等等 20 多类功能。比如: 事件处理和回调函数、内存控制和对象获取、线程和锁、调试功能。具体的使用方法可以见参考文章 JVMTI 和 Agent 实现

6 event 机制介绍

      jvmti 的event管理后续研究一下,再补上。

7 一个agent demo

一个agent 会经历环境初始化、参数解析、注册功能、注册事件响应这几个步骤。运行过程如下:

agent时序图

代码如下:

7.1 Main.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;
}

7.2 MethodTraceAgent.cpp
#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() << "]";
    }
}
7.3 MethodTraceAgent.h
#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;
};

7.4 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()");
    }
}
7.6 编译
g++ -w -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/linux 
MethodTraceAgent.cpp Main.cpp -fPIC -shared -o libagent.so
7.7 默认运行java
javac MethodTraceTest.java
java MethodTraceTest 

结果如下:


默认运行
7.8 加入我们的agent
java -agentpath:/home/xxx/libagent.so=first MethodTraceTest” 。 //其中的first是个参数,可以透传给jvmti。

运行结果如下:


加入agent之后的结果

参考文献

1、JVMTI 和 Agent 实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 1.造氢气 初中的时候做化学试验上瘾了,有个试验是造氢气来着。用的是高锰酸钾和稀盐酸。。我玩的上瘾。就从实验室偷了...
    自由枫无忌阅读 697评论 2 4
  • [我的100个成长感悟]@kiki-067 2015年5月6日 5月6日思考:最近有没有很触动你的一件事情发生?它...
    吉吉kiki阅读 335评论 0 1
  • 这几天翻看自己的笔记,看到关于钙片的笔记,现在人们的生活水平变好了,大家有更多的时间和能力去关注自己,所以补钙这事...
    爱昔阅读 194评论 0 0