windows下开发jvmti agent详细教程

目录

  • 目录
  • JAVA虚拟机(JVM:Java Virtual Machine)简介
  • JVMTI以及agent简介
    • 1.JVMTI(Java Virtual Machine Tool Interface)
    • 2.agent
  • 平台环境以及软件安装
  • 实现
      • 第一步:创建java源文件
      • 第二步:实现C/C++动态链接库
      • 第三步:调用动态链接库


JAVA虚拟机(JVM:Java Virtual Machine)简介

  • 从进程角度解释JVM,虚拟机是运行在操作系统中的,而操作系统的执行单位是进程。所以可以理解为:当JVM运行的时候,它是操作系统中的进程实例。当其没有运行的时候,作为可执行文件放在文件系统中,可以称之为程序。
    —— [ 摘自 ]https://blog.csdn.net/zhangjg_blog/article/details/20380971

以HelloWorld为例:

 public class HelloWorld{
    public static void main(String args[]){
         System.out.println("hello world!");
    }
 }

第一步:编译

  • javac HelloWorld.java
  • 结果生成两个文件:
    HelloWorld.class
    HelloWorld.java

第二步:执行

  • java HelloWorld
  • 之所以不直接运行helloWorld.class,是因为class文件并不能被操所系统识别为可执行文件。首先敲入java命令,说明最先启动的是一个叫做java的程序:而这个运行起来的java程序,其实就是一个java虚拟机。java虚拟机读取参数HelloWorld。把它当做初始类加载到内存,对这个类进行初始化和动态链接,然后从类的main方法开始执行。
  • 也就是说:.class文件并非直接被系统加载后在CPU上运行,而是被java虚拟机这个进程托管运行。
    —— [ 摘自 ]https://blog.csdn.net/zhangjg_blog/article/details/20380971

JVMTI以及agent简介

1.JVMTI(Java Virtual Machine Tool Interface)

  • 开发人员需要对Java程序进行诊断和调试,比如掌握程序运行状况,提高程序执行效率,分析程序出现的问题。而就如前面所说,java程序是托管运行在java虚拟机上的,所以要做到这些,就需要和虚拟机打交道。利用虚拟机提供的本地代码接口(Java Virtual Machine Tool Interface)来获取并且返回当前虚拟机的状态,或者转发控制命令。
    —— [ 摘自 ]https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/

2.agent

  • agent就是JVMTI客户端。
    agent是基于JVMTI开发的,以c/c++语言编写的动态共享库的形式存在。当JAVA启动或者运行的时候,JVM会动态加载一个外部的agent,然后触发JVM源生线程Attach Listener执行agent中的回调函数。在函数体内,可以获取各种各样的VM信息,注册感兴趣的VM时间,控制VM的行为。
    ——[ 摘自 ] https://www.jianshu.com/p/df17a9b0c45c

平台环境以及软件安装

  • win10 x86_64
  • JDK
  • eclipse Oxygen 4.7.3
  • CodeBlocks(用于编写agent)

tips:两个软件的位数必须和系统保持一致!!
JDK就在ORACLE官网下载即可,安装后记得将路径添加到环境变量。
由于eclipse的安装很简单,所以这里主要说明怎么安装CodeBlocks及相应配置。

  • 首先下载CodeBlocks的安装包(注意位数是32位还是64位)

    由于CodeBlocks只是一个集成开发环境,本身并不提供编译功能,所以需要自己安装第三方编译代码。官方的集成编译器为mingw。如果系统是32位的可以直接安装mingw,但是由于我的系统和eclipse都是64位的,所以不安装mingw,而是安装mingw-w64。


CodeBlock下载地址 : http://www.codeblocks.org/downloads
  • 安装mingw-w64

    • 非常重要!!!
      CodeBlocks默认编译器mingw编译出来的动态链接库dll是32位的,无法被64位的java程序调用!所以一定要安装mingw-w64

    mingw-w64下载地址 : https://sourceforge.net/projects/mingw-w64/
    点击安装,在如下界面中architechture根据自己的系统选择x86_64。
    windows下开发jvmti agent详细教程_第1张图片

  • 配置CodeBlocks

    • 打开CodeBlocks,点击顶部菜单栏中的settings——>compiler——>ToolChain executables
    • 在如下界面中,编辑Compiler’s installation directory(位置选择:mingw-w64的安装路径\bin)
    • 依次将C compiler ,C++ compiler,Linker for dynamic libs,Linker for static libs,Resource compiler,Make program依次改成“mingw-w64的安装路径\bin”中对应的程序,如图所示,然后选择OK保存更改。
      windows下开发jvmti agent详细教程_第2张图片
    • 再次选择settings——>Debugger
    • 在GDB/CDB Debugger中点击Create Config
      windows下开发jvmti agent详细教程_第3张图片
    • 在所弹出的框中填写gdb64,然后选择OK,在接下来的界面中,编辑gdb64,将Executable path改成之前的“mingw-w64的安装路径\bin”,然后选择OK
      windows下开发jvmti agent详细教程_第4张图片
    • 继续点击顶部菜单栏中的settings——>compiler——>ToolChain executables,将Debugger设置为刚刚编辑好的gbd64,如下图所示。
      配置详情

实现

在这篇博客中,实现的agent功能是:输出当前JVM中所有加载任意指定类的个数。

第一步:创建java源文件

  • 新建java工程:打开Eclipse ——> File ——> New ——> Java Project
  • 在Package Explorer点击刚刚创建Java Project,并且在src选项下新建class
  • 假设创建的工程名是myAgent,包名是com.agent,类名是MyAgent,功能是输出Thread.class数目
/*这一部分是Java代码*/
package com.agent;

public class MyAgent{
    public static void main(String[] args){
        int a = countInstances(Thread.class);
        System.out.println(a + " instances");
    }
  /*
   *申明native方法countInstances()
   *使用native关键字代表countInstances()是原生函数,这个方法是用C/C++实现的,并且被编译成了DLL供Java调用,不必在Java中声明函数体。
  */
    private native static int countInstances(Class class);
    static{
        System.loadLibrary("MyAgent");
    }
}
  • 打开windows cmd
  • 编译Java源文件:javac MyAgent.java
    (这一步最好在MyAgent.java所在路径下执行,否则会找不到MyAgent类文件)
  • 生成.h文件:javah -jni com.agent.MyAgent
    (这一步在src文件路径下执行,否则会找不到类文件,同时,不要在MyAgent.java所在路径下直接执行javah -jni MyAgent,也会找不到类文件,这是因为Java文件中有包名,具体原因可以google)
  • 可以看见在myAgent工程文件夹下生成了一个com_agent_MyAgent的.h文件(就是你Java工程所在的文件夹中:myAgent/src),在codeBlocks中打开这个文件,可以看到如下内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_agent_MyAgent */

#ifndef _Included_com_agent_MyAgent
#define _Included_com_agent_MyAgent
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_agent_MyAgent
 * Method:    countInstances
 * Signature: (Ljava/lang/Class;)I
 */
JNIEXPORT jint JNICALL Java_com_agent_MyAgent_countInstances
  (JNIEnv *, jclass, jclass);

#ifdef __cplusplus
}
#endif
#endif

第二步:实现C/C++动态链接库

  • 打开CodeBlocks,选择File ——> Projects ——> Dynamic Link Library,然后依据步骤填好工程名(我的工程名是agentTest)
  • 在左边的management框中,可以看到新建的工程,将Sources和Headers中的main.c以及head.h都删除,然后在Sources中新建一个myAgent.c的文件,并在Headers中打开之前生成的com_agent_MyAgent.h
  • 在Sources中,实现agent以及com_agent_MyAgent.h
  • 重要!!!
    记得在CodeBlocks菜单栏中选择Project ——> build options ——> Search directories,选择add添加jvmti.h和jni_md.h以及com_agent_MyAgent.h的搜索路径
    jvmti.h在安装的JDK路径\include里
    jni_md.h在安装的JDK路径\include\win32里
    com_agent_MyAgent.h在Java工程里
    (注:我在添加jni_md.h以及com_agent_MyAgent.h的时候,即使添加了搜索路径也依然搜索不到这些头文件。如果jni_md.h无法搜索到,解决方案是将jni_md.h拷贝到jvmti.h所在的文件夹中;如果com_agent_MyAgent.h无法搜索到,解决方案是将com_agent_MyAgent.h添加到agentTest工程的文件夹中)
/*agentTest.c文件*/

#include 
#include 
#include 
#include 
#include 
#include 

/**< 此处定义结构体GobalAgentData用于保存jvmtiEnv指针 */
typedef struct{
    jvmtiEnv * jvmti;
}GlobalAgentData;

static GlobalAgentData * gdata;
/** JNIEXPORT JNICALL均定义在jni_md.h中,是用于标识函数用途的两个宏
 *  在jni_md.h中,
 *  #define JNIEXPORT _declspec(dllexport)
 *  #define JNICALL _stdcall
 * JNIEXPORT:如果动态库中的函数要被外部调用,需要在函数声明中添加_declspec(dllexport)标识
 * JNICALL : 用于约束函数入栈顺序和栈堆清理的规则
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char * options, void *reserved){

    jvmtiEnv * jvmti = NULL;
    jvmtiCapabilities capa;
    jvmtiError error;

    jint result = (*jvm)->GetEnv(jvm,(void**)&jvmti, JVMTI_VERSION_1_1);
    if(result != JNI_OK){
        printf("ERROR : Unable to access JVMTI! \n");
    }

    (void)memset(&capa,0,sizeof(jvmtiCapabilities));

    /**< can_tag_objects : can set and get tags, as described in the Heap category
     *  通过将capa_can_tag_objects设置为1,代表enable this capability
    */
    capa.can_tag_objects = 1;

    /** \jvmtiError AddCapabilities(jvmtiEnv* env, const jvmtiCapabilities* capabilities_ptr)
     *
     * 设置新的capabilities,之前的capabilities也会被保留
     *
     */
    error = (*jvmti)->AddCapabilities(jvmti,&capa);

    /**< store jvmtiEnv in a global data */
    gdata = (GlobalAgentData*)malloc(sizeof(GlobalAgentData));
    gdata->jvmti = jvmti;
    return JNI_OK;
    }


    /*< JVMTI_VISIT_OBJECTS是Heap Visit Control Flag,由heap回调函数返回
    *    如果访问object并且FllowReferences已经初始化callback函数,并且遍历了object的引用
    *
    *    class_tag : 访问的object的class的tag(之前提到过:tag是用来唯一标识一个object的value)
    *    size : object的size
    *    tag_ptr : object的tag是存放在别的地方,object经过tag_ptr得到这个tag
    *    length : 如果object是个array,那么length就代表这个array的长度,否则均为-1
    *    user_date : the user supplied data that was passed into the iteration function
    */
    JNICALL jint objectCountingCallback(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data){

        int* count = (int*) user_data;
        *count += 1;
        return JVMTI_VISIT_OBJECTS;
    }
        /**< 函数的命名规则:Java_packagename_classname_methodname */
    JNIEXPORT jint JNICALL Java_com_agent_MyAgent_countInstances(JNIEnv * env, jclass thisClass, jclass klass){

        int count = 0;

        /**< jvmtiHeapCallbacks : heap callback function structure */
        jvmtiHeapCallbacks callbacks;
        (void)memset(&callbacks, 0, sizeof(callbacks));

        /**< heap_iteration_callback是用于描述heap中object的回调函数,used by the iterateThroughHeap function
         * 这一步如果看不懂,可以去了解一下函数指针的概念
         */
        callbacks.heap_iteration_callback = &objectCountingCallback;

        /**< jvmtiError IterateThroughHeap(jvmtiEnv* env, jint heap_filter, jclass klass, const jvmtiHeapCallbacks* callbacks, const void* user_data)
         *   initiate an iteration over all objects in the heap, including both reachable and unreachable objects
        */
        jvmtiError error = (*(gdata->jvmti))->IterateThroughHeap(gdata->jvmti,0, klass, &callbacks, &count);
        return count;
    }
  • 然后选择build and run,可以在agentTest工程文件夹\bin\Debug中看到生成了三个文件:
    windows下开发jvmti agent详细教程_第5张图片
    其中的agentTest.dll就是我们需要的动态链接库文件

第三步:调用动态链接库

  • 首先,修改刚刚的Java源文件,删除static这一部分
    static{
             System.loadLibrary("MyAgent");
    }
  • 然后打开windows cmd
  • 重新编译java源文件:javac MyAgent.java
  • 调用动态链接库:java -agentpath:agentTest动态链接库路径\agentTest.dll com.agent.MyAgent
    (current directory是java project path/src,如果current directory是java project path/src/com/agent,并且执行java -agentpath:agentTest动态链接库路径\agentTest.dll MyAgent,会出现NoClassDefFoundError)

如果一言不合就出现找不到类文件错误,十有八九是执行路径有问题。如果要保留java源文件中包名package com.agent,这个问题也没办法,只能注意执行路径,还有什么时候用com.agent.MyAgent.java,什么时候直接用MyAgent.java。如果嫌太麻烦,可以直接删除java源文件中package com.agent这一行,这样编译也不会出错。

你可能感兴趣的:(JVMTI)