JVMTI开发教程之一个简单的Agent

概述

JVM TI是JDK提供的一套用于开发JVM监控, 问题定位与性能调优工具的通用编程接口(API)。
通过JVMTI,我们可以开发各式各样的JVMTI Agent。这个Agent的表现形式是一个以c/c++语言编写的动态共享库。

JVMTI Agent原理: java启动或运行时,动态加载一个外部基于JVM TI编写的dynamic module到Java进程内,然后触发JVM源生线程Attach Listener来执行这个dynamic module的回调函数。在函数体内,你可以获取各种各样的VM级信息,注册感兴趣的VM事件,甚至控制VM的行为。

JVMTI从功能上大致可以分为4类:

1. Heap
获取所有类的信息,对象信息,对象引用关系,Full GC开始/结束,对象回收事件等。

2. 线程与堆栈
获取所有线程的信息,线程组信息,控制线程(start,suspend,resume,interrupt…), Thread Monitor(Lock),得到线程堆栈,控制出栈,方法强制返回,方法栈本地变量等。

3. Class & Object & Method & Field 元信息
class信息,符号表,方法表,redefine class(hotswap), retransform class,object信息,fields信息,method信息等。

4. 工具类
线程cpu消耗,classloader路径修改,系统属性获取等。

开发jvm ti agent,简单的来讲,就是开发一个c/c++的共享库。在windows下后缀是dll,linux/unix下是so,mac下就是dylib。所以我们创建工程和编译环境的时候,记得以共享库(share library)的形式来构建。

JVMTI**的启动方式**

JVMTI有两种启动方式,第一种是随java进程启动时,自动载入共享库,下文简称方式A。另一种方式是,java运行时,通过attach api动态载入,下文简称方式B。

方式A的实现方式是通过在java启动时传递一个特殊的option,例子如下:

java -agentlib:<agent-lib-name>=<options> Sample
注意,这里的共享库路径是环境变量路径,例如 java -agentlib:foo=opt1,opt2,java启动时会从linux的LD_LIBRARY_PATH或windows的PATH环境变量定义的路径处装载foo.so或foo.dll,找不到则抛异常

java -agentpath:<path-to-agent>=<options> Sample
这是以绝对路径的方式装载共享库,例如 java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2

方式B的实现方式是通过attach api,这是一套纯java的api,它负责动态地将dynamic module attach到指定进程id的java进程内并触发回调。例子如下:

import java.io.IOException;

import com.sun.tools.attach.VirtualMachine;

public class VMAttacher {

public static void main(String[] args) throws Exception {

// args[0]为java进程id

VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]);

// args[1]为共享库路径,args[2]为传递给agent的参数

virtualMachine.loadAgentPath(args[1], args[2]);

virtualMachine.detach();

}

}

Attach API位于$JAVA_HOME/lib/tools.jar,所以在编译时,需要将这个jar放入classpath。例如

javac -cp $JAVA_HOME/lib/tools.jar VMAttacher.java

准备开发JVMTI Agent

1. jvmti**头文件搜索路径**
c/c++应用程序都会用到头文件,为了让共享库编译时能找到jvmti的头文件,我们需要添加2个路径到Includes。
一个指向到$JAVA_HOME/include,另一个指向到$JAVA_HOME/include/linux,windows下为$JAVA_HOME/include/windows
例如:

g++ -I/home/kenwu/jdk1.6.0_24/include -I/home/kenwu/jdk1.6.0_24/include/linux
上面是以命令行方式编译,如果你使用的是IDE,请在相应位置添加Includes.

2. **源码引入jvmti.h头文件**
jvmti.h头文件里包含了所有jvm ti要用到的数据结构和回调函数定义。

#include <jvmti.h>
3. **确定JVMTI的启动方式,引入回调函数**
方式A引入的回调函数如下:
JNIEXPORT jint JNICALL

Agent_OnLoad(JavaVM vm, char options, void *reserved)
方式B引入的回调函数如下:
JNIEXPORT jint JNICALL

Agent_OnAttach(JavaVM jvm, char options, void reserved) {
卸载共享库时的回调函数(通用):
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM 
vm)
我们可以看到,方式A和方式B提供的回调函数,参数是一样的。

三个参数分别是 jvm, options, reserved.
jvm变量是JVM传递给共享库的上下文。通过jvm变量,我们可以创建jvmti环境上下文。需要注意的是,jvmti上下文不同于jvm上下文,这从jvm变量类型定义的函数上可以看出。
options是外部传入的参数。比如前面例子里给的 opt1, opt2,它仅仅是一个字符串。
reserved,这是一个预留参数,我们不必关心它。

开发一个简单的JVMTI Agent

下面,给大家演示开发一个的简单Agent.
功能是打印jvm内所有已经装载成功的class签名。

Agent代码:

/*

* Just a simple agent using JVMTI.



* Created on: 2011-3-3

* Author: kenwu

*/

#include <jvmti.h>

#include <string>

#include <cstring>

#include <iostream>

#include <list>

#include <map>

#include <set>

#include <stdlib.h>

#include <jni_md.h>

JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char options,

void *reserved) {

jvmtiEnv *jvmti;

jint result = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);

if (result != JNI_OK) {

printf(“ERROR: Unable to access JVMTI!n“);

}

jvmtiError err = (jvmtiError) 0;

jclass *classes;

jint count;

err = jvmti->GetLoadedClasses(&count, &classes);

if (err) {

printf(“ERROR: JVMTI GetLoadedClasses failed!n“);

}

for (int i = 0; i < count; i++) {

char *sig;

jvmti->GetClassSignature(classes[i], &sig, NULL);

printf(“cls sig=%sn“, sig);

}

return err;

}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {

// nothing to do

}
完成Agent源码的编写后,我们来编译它。
BASE_HOME=`**pwd**`

INCLUDES=”-I$JAVA_HOME/include -I$JAVA_HOME/include/linux”

g++ $BASE_HOME/src/agent.cpp $INCLUDES -Wall -Wno-deprecated -fPIC –share -o $BASE_HOME/libtestagent.so
例子中的 BASE_HOME 为笔者的工程根目录,请自行替换。

编译完后,我们需要准备一个java进程的Attach工具类。最简单的实现方式,就是直接写一个带main的java application。
笔者将上文中提到的VMAttacher例子稍做了一下修改:

import java.io.IOException;

import com.sun.tools.attach.VirtualMachine;

public class TestAgentVMAttacher {

public static void main(String[] args) throws Exception {

String pid = “12345”; // 12345改成你想attach的java进程id

String agentPath = “/path_to_agent”; // path_to_agent为你编译的agent的路径

VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(pid);

virtualMachine.loadAgentPath(agentPath, null);

virtualMachine.detach();

}

}
最后,运行这个java application。如果你的console能看到如下输出,说明agent load并运行成功:
cls sig=Lsun/jkernel/DownloadManager$1;
cls sig=Lsun/nio/cs/StandardCharsets$Cache;
cls sig=Lsun/misc/URLClassPath;
cls sig=Ljava/nio/HeapCharBuffer;
cls sig=Lsun/nio/ByteBuffered;
cls sig=Ljava/lang/StringCoding$StringDecoder;
cls sig=Ljava/lang/reflect/Modifier;
cls sig=Ljava/util/Collections;
….
PS:灵活使用Attach API,还可以实现更多高级的功能,在本文中不再赘述。

总结

通过本文的学习,你了解了JVMTI,学会了如何配置JVMTI开发环境,并在自己搭建的环境中开发了一个简单的打印所有已装载class签名的agent。

本文来源于"阿里中间件团队播客",原文发表时间" 2011-03-15"

时间: 2024-08-31 14:06:42

JVMTI开发教程之一个简单的Agent的相关文章

安卓UI设计与开发教程 顶部标题栏(一)ActionBar详细概述和简单示例

一.ActionBar介绍 在Android 3.0中 除了我们重点讲解的Fragment外,Action Bar也是一个非常重要的交互元素,Action Bar取代了传统的tittle bar和menu,在程序运行中一直置于顶部,对于Android平板设备来说屏幕更大它的标题使用Action Bar来设计 可以展示更多丰富的内容,方便操控. 二.ActionBar的功能 用图的方式来讲解它的功能 开发教程 顶部标题栏(一)ActionBar详细概述和简单示例-actionbar隐藏标题栏"&g

微信公众帐号开发教程(十) 解析接口中的消息创建时间CreateTime

从微信公众平台的消息接口指南中可以看出,每种类型的消息定义中,都包含有CreateTime参数,它表示 消息的创建时间,如下图所示: 开发教程(十) 解析接口中的消息创建时间CreateTime-create time"> 上图是消息接口指南中4.1-文本消息的定义.注意CreateTime的描述:消息创建时间(整型),重点在于 这是一个整型的时间,而不是我们大家所熟悉的类似于"yyyy-MM-dd HH:mm:ss"的标准格式时间. 本文主要想介绍的就是微信消息接口中

微信公众帐号开发教程(一) 引言

接触微信公众帐号已经有两个多月的时间了,在这期间,除了陆续完善个人公众帐号xiaoqrobot以外,还 带领团队为公司开发了两个企业应用:一个是普通类型的公众帐号,另一个是会议类型的公众帐号.经过这3 个公众帐号的开发,对目前微信公众平台开放的api算是比较熟悉了,像文本消息.图文消息.音乐消息.语 音消息.位置消息等全部用到过,菜单也使用过.所以,就有了写微信公众帐号开发教程的想法,将学习到的 技术经验分享出来,帮助更多需要的朋友,也希望借此认识同行的朋友,共同交流,共同进步! 下面 将对即将

Android简明开发教程十二:引路蜂二维图形库简介及颜色示例

AndroidGraphics2DTutorial定义了应用的主Activity,下面就可以开始写每个具体的二维绘图示例.不同的例子将尽量采用 不同的UI控件:Menu,Content Menu,Dialog,Custom Dialog,Button等等.例子采用了引路蜂二维图形库,引路蜂二维图形 库Graphics 2D API实现了移动平台(Java ME,Blackberry,iPhone,Android,Windows Phone)上图形引擎,它能够以一种统一的方 式处理各种基本图形(S

Android简明开发教程十:数据绑定Data Binding

前面提到AndroidGraphics2DTutorial说过它是ListActivity派生出来的.ListActivity中显示的是ListView,ListView和 Gallery ,Spinner有一个共同点:它们都是AdapterView的子类.AdapterView的显示可以通过数据绑定来实现,数据源可以是 数组或是数据库记录,数据源和AdapterView是通过Adapter作为桥梁.通过Adapter,AdatperView可以显示数据源或处理用户选 取时间,如:选择列表中某项

Android LibGDX游戏引擎开发教程(七) 中文字体的显示和绘制(上)

在字体的显示和绘制中,Libgdx的作者(Mario Zechner,美国人)给我们提供了一个非常好用的工具 --Hiero,那么下面就来看看它具体的使用方法. 一.Hiero工具的使用 1.Hiero工 具的下载地址 开发教程(七) 中文字体的显示和绘制(上)-android 绘制字体"> 2.下载结束后,双击hiero.jar文件打开,我们可以看到Hiero的一些基本功能,相比来说作者做的 还是比较简单易懂的.从界面上知道,它包括很多选项,可以制作特效.改变背景颜色.设置内间距等等,右

安卓LibGDX游戏引擎开发教程(六) 图形图像的绘制(下)图片整合工具的使用

在上一篇文章中,我们提到了图片必须是2的n次方的问题.但是随着Libgdx的不断完善和发展,使用一些 工具就可以很好的解决了这样一个问题,但是它的功能又不仅仅只限于此,那么下面就来让我们看看 TexturePacker-Gui工具的使用,我们又称之它为图片整合工具. 一.TexturePacker-Gui简介 TexturePacker-Gui是一个可视化版本的图片整合工具.这个工具的用途很简单,就是一个将小 图片整合成一张大图片,在把大图片打包成可查找的图片.而且通过TexturePacker

Android LibGDX游戏引擎开发教程(三) 示例代码详细讲解

承接了上一篇文章中关于环境搭建的简单示例,这一篇我会详细讲解FirstGame和HelloGameActivity类中 的代码. 一.ApplicationListener接口详解 1.简单代码示例,FirstGame.java: package com.example.hellolibgdx; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.gra

安卓UI设计与开发教程 滑动菜单栏(一)开源项目SlidingMenu的使用

由于最近的工作确实比较忙的原因,所以这个系列的教程有一段时间没有更新了,也请各位读者见谅. 这期博主要给大家带来的是关于滑动菜单栏的实现效果. 一.SlidingMenu简介 相信大家对SlidingMenu都不陌生了,它是一种比较新的设置界面或配置界面的效果,在主界面左滑或者右滑出现设置界面效果,能方便的进行各种操作.很多优秀的应用都采用了这种界面方案,像facebook.人人网.everynote.Google+等等.如下图所示: Google+界面效果图 开发教程 滑动菜单栏(一)开源项目