用JVM工具接口创建调试和分析代理

发表于:2007-06-22来源:作者:点击数: 标签:
下一页 1 2 3 4 5 Java 虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)提供了一种编程接口,允许软件 开发 人员创建软件代理以监视和控制 Java 编程语言应用程序。JVMTI 是 Java 2 Software Development Kit (SDK), Standard Edition, 版本

下一页 1 2 3 4 5 

   
  Java 虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)提供了一种编程接口,允许软件开发人员创建软件代理以监视和控制 Java 编程语言应用程序。JVMTI 是 Java 2 Software Development Kit (SDK), Standard Edition, 版本 1.5.0 中的一种新增功能。

它取代了 Java Virtual Machine Profiling Interface (JVMPI),从版本 1.1 起即作为 Java 2 SDK 的一种实验功能包括在内。在 JSR-163 中对 JVMTI 进行了有关说明。

  本文阐述如何使用 JVMTI 创建 Java 应用程序的调试和分析工具。这种工具(也称作代理)在应用程序中发生事件时,能够使用该接口提供的功能对事件通知进行注册,并查询和控制该应用程序。这里提供了 JVMTI 的文档资料。JVMTI 代理对于调试和调优应用程序十分有用。它可以对应用程序的各个方面予以说明,如内存分配情况、CPU 利用情况及锁争夺情况。

  尽管 JVMPI 现在仍处于实验阶段,很多 Java 技术开发人员已经在使用它了,而且已经把它应用到多种市场上提供的 Java 应用程序 Profiler。请注意,极力鼓励开发人员使用 JVMTI 而不使用 JVMPI。JVMPI 在不久的将来将被废止。

  JVMTI 在多个方面改进了 JVMPI 的功能和性能。例如:

  1) JVMTI 依赖于每个事件的回调。这比 JVMPI 设计使用需要编组和取消编组的事件结构更有效。

  2) JVMTI 包含四倍于 JVMPI 的函数(包括用于获取关于变量、字段、方法和类的信息的更多函数)。有关 JVMTI 函数的完整索引,请参见函数索引页。

  3) JVMTI 比 JVMPI 提供更多类型的事件通知,包括异常事件、字段访问和修改事件、断点和单步骤事件等。

  有些从未被充分利用的 JVMPI 事件,如 Arena 的 new 和 delete,或者通过字节码工具很容易就能获得的内容,或者 JVMTI 函数本身(如 heap dump 和 object allocation)往往被 丢掉。 对这些事件的描述位于事件索引页。

  JVMTI 是基于功能的,而 JVMPI 对于相应性能影响却是“要么全有,要么全无”。

  JVMPI 堆功能不可伸缩。

  JVMPI 没有错误返回信息。

  JVMPI 在 VM 实现方面具有很强的侵入性,容易导致维护问题和性能受损。

  JVMPI 是个实验产品,不久将废止。

  在本文的以下部分,我们介绍一个简单代理,它使用 JVMTI 函数从 Java 应用程序提取信息。 代理的编写必须使用本地代码。这里给出的示例代理是使用 C 语言编写的。您可以于此下载完整的示例代理代码。下面几段介绍如何初始化一个代理,以及代理如何使用 JVMTI 函数提取关于 Java 应用程序的信息,以及如何编译和运行代理。此示例代码和编译步骤特定于 UNIX 环境,但是经过修改后也可用于 Windows。这里介绍的代理可用于在任何 Java 应用程序中分析线程和确定 JVM 内存使用情况。

  这里包含一个用 Java 语言编写的简单程序,称作 SimpleThread.java,并可从这里下载。我们使用 ThreadSample.java 演示此代理的预期输出。

  JVMTI 的功能很多,在此无法详述;但本文中的代码可以提供一个出发点,让您去开发符合自己特定需求的分析工具。

  代理初始化

  本节介绍用于初始化代理的代码。首先,代理必须包括 jvmti.h 文件,语句为 #include <jvmti.h>。

  另外,代理必须包含一个名为 Agent_OnLoad 的函数,加载库时要调用这一函数。Agent_OnLoad 函数用于在初始化 Java virtual machine (JVM) 之前设置所需的功能。Agent_OnLoad 签名如下所示:

clearcase/" target="_blank" >cc66 width="90%" align=center bgColor=#c8c7b9 border=1>
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
...

/* We return JNI_OK to signify success */
return JNI_OK;
}

  在我们的示例代码中,我们必须为将要使用的 JVMTI 函数和事件启用多种功能。一般情况下均需(在某些情况下必须)将这些功能添加到 Agent_OnLoad 函数中。有关每种函数或事件所需的功能的说明,参见 Java 虚拟机工具接口页。例如,要使用 InterruptThread 函数,can_signal_thread 功能必须为 true。我们把示例所需的全部功能都设置为 true,然后使用 AddCapabilities 函数将它们添加到 JVMTI 环境中:

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

...

(void)memset(&capa, 0, sizeof(jvmtiCapabilities));
capa.can_signal_thread = 1;
capa.can_get_owned_monitor_info = 1;
capa.can_generate_method_entry_events = 1;
capa.can_generate_exception_events = 1;
capa.can_generate_vm_object_alloc_events = 1;
capa.can_tag_objects = 1;

error = (*jvmti)->AddCapabilities(jvmti, &capa);
check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities.");
...

  此外,Agent_OnLoad 函数通常用于注册事件通知。在此示例中,我们在使用 SetEventNotificationMode 函数的 Agent_OnLoad 中启用了多个事件,如 VM Initialization Event、VM Death Event 和 VM Object Allocation, 如下所示:

error = (*jvmti)->SetEventNotificationMode
(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread)NULL);
error = (*jvmti)->SetEventNotificationMode
(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, (jthread)NULL);
error = (*jvmti)->SetEventNotificationMode
(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");

...

  注意,在此示例中,NULL 是作为第三个参数传递的,它可以全局地启用事件通知。如果需要,可以为某个特殊线程启用或禁用某些事件。

  我们为其注册的每个事件还都必须具有一个指定的回调函数,当该事件发生时将调用它。例如,如果一个 Exception 类型的 JVMTI Event 发生,示例代理会将其发送到回调方法 callbackException() 中。

  使用 jvmtiEventCallbacks 结构和 SetEventCallbacks 函数可以完成此任务:

jvmtiEventCallbacks callbacks;
...

(void)memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMInit = &callbackVMInit; /* JVMTI_EVENT_VM_INIT */
callbacks.VMDeath = &callbackVMDeath; /* JVMTI_EVENT_VM_DEATH */
callbacks.Exception = &callbackException;/* JVMTI_EVENT_EXCEPTION */
callbacks.VMObjectAlloc = &callbackVMObjectAlloc;/* JVMTI_EVENT_VM_OBJECT_ALLOC */

error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks,(jint)sizeof(callbacks));
check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");

...

  我们还将设置一个全局代理数据区域以在整个代码中使用。

/* Global agent data structure */
typedef struct {

 /* JVMTI Environment */
 jvmtiEnv *jvmti;
 jboolean vm_is_started;

 /* Data access Lock */
 jrawMonitorID lock;
} GlobalAgentData;

static GlobalAgentData *gdata;

  在 Agent_OnLoad 函数中,我们执行以下设置:

/* Setup initial global agent data area
* Use of static/extern data should be handled carefully here.
* We need to make sure that we are able to cleanup after
* ourselves so anything allocated in this library needs to be
* freed in the Agent_OnUnload() function.
*/

static GlobalAgentData data;
(void)memset((void*)&data, 0, sizeof(data));
gdata = &data;
...
/* Here we save the jvmtiEnv* for Agent_OnUnload(). */
gdata->jvmti = jvmti;
...

  我们在 Agent_OnLoad() 中创建一个原始监视器,然后把代码 VM_INIT、VM_DEATH 和 EXCEPTION 包装于 JVMTI RawMonitorEnter() 和 RawMonitorExit() 接口 。

/* Here we create a raw monitor for our use in this agent to
* protect critical sections of code.
*/

error = (*jvmti)->CreateRawMonitor(jvmti, "agent data", &(gdata->lock));

/* Enter a critical section by doing a JVMTI Raw Monitor Enter */

static void
enter_critical_section(jvmtiEnv *jvmti)
{
 jvmtiError error;

 error = (*jvmti)->RawMonitorEnter(jvmti, gdata->lock);
 check_jvmti_error(jvmti, error, "Cannot enter with raw monitor");
}

/* Exit a critical section by doing a JVMTI Raw Monitor Exit */

static void
exit_critical_section(jvmtiEnv *jvmti)
{
 jvmtiError error;
 error = (*jvmti)->RawMonitorExit(jvmti, gdata->lock);
 check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
}

  卸载代理时,VM 将调用 Agent_OnUnload。此函数用于清理在 Agent_OnLoad 期间分配的资源。

/* Agent_OnUnload: This is called immediately before the shared library
* is unloaded. This is the last code executed.
*/

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
 /* Make sure all malloc/calloc/strdup space is freed */
}

原文转自:http://www.ltesting.net