- 浏览: 101200 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
damonbird:
这套思路有对应的实现吗?
基于组件的开发思路 -
blackproof:
你好,age后边的两个数,是啥意思Desired surviv ...
性能监控/优化系列——JVM监控/调优 -
辛星0913:
vim编程常用命令 -
leexiaodong2009:
请问要怎么样才可以在web项目中用,比如tomcat里面配置了 ...
用JVM工具接口创建调试和分析代理
用JVM工具接口创建调试和分析代理
引自:http://www.zhujiangroad.com/html/soft/s2395.html
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 签名如下所示:
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 */
}
使用 JVMTI 分析线程
本节介绍如何获取关于在 JVM 中运行的用户线程的信息。如前所述,启动 JVM 时,JVMTI 代理库中的启动函数 Agent_OnLoad 将被调用。在 VM 初始化过程中,JVMTI_EVENT_VM_INIT 类型的 JVMTI Event 将生成并被发送到代理代码的 callbackVMInit 例程中。一旦 VM 初始化事件被接收(即 调用VMInit 回调),代理即可结束其初始化。现在,此代理可以自由调用任何 Java Native Interface (JNI) 或 JVMTI 函数。此时,我们已经处于活动阶段,将启用本 VMInit 回调例程中的 Exception 事件(JVMTI_EVENT_EXCEPTION)。
error = (*jvmti)->SetEventNotificationMode
(jvmti, JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);
无论何时,只要在 Java 编程语言方法中首次探测到异常,就会生成 Exception 事件。此异常可能由 Java 编程语言抛出,也可能由本地方法抛出;但是如果由本地方法抛出,直到 Java 编程语言方法首次发现此异常时该事件才会生成。如果异常已被处理并清除,则异常事件不会生成。
出于演示目的,下面给出了所用的示例 Java 应用程序。主线程创建了 5 个线程,这 5 个线程退出前各自抛出一个异常。一旦启动 JVM,JVMTI_EVENT_VM_INIT 将生成并被发送到代理代码中进行处理,因为我们已经在代理代码中启用了 VMInit 和 Exception 事件。随后,当 Java 线程抛出一个异常时,JVMTI_EVENT_EXCEPTION 将被发送到代理代码中。然后,代理代码 会分析此线程信息并显示当前线程名、它所属的线程组、此线程所拥有的监视器、线程状态、线程堆栈跟踪及 JVM 中的所有用户线程。
public class SimpleThread {
static MyThread t;
public static void main(String args[]) throws Throwable{
t = new MyThread();
System.out.println("Creating and running 10 threads...");
for(int i = 0; i < 5; i++) {
Thread thr = new Thread(t,"MyThread"+i);
thr.start();
try {
thr.join();
} catch (Throwable t) {
}
}
}
}
class MyThread implements Runnable {
Thread t;
public MyThread() {}
public void run() {
/* NO-OP */
try {
"a".getBytes("ASCII");
throwException();
Thread.sleep(1000);
} catch (java.lang.InterruptedException e){
e.printStackTrace();
} catch (Throwable t) {
}
}
public void throwException() throws Throwable{
throw new Exception("Thread Exception from MyThread");
}
}
我们来看一下 Java 应用程序内部抛出一个异常时 JVMTI 代理代码的执行情况。
throw new Exception("Thread Exception from MyThread");
JVMTI 异常事件生成后将被发送到代理代码的 Exception 回调例程中。代理必须添加 can_generate_exception_events 功能才能启用异常事件。我们使用 JVMTI GetMethodName 接口来显示生成异常的方法名和例程签名。
err3 = (*jvmti)->GetMethodName(jvmti, method, &name, &sig, &gsig);
printf("Exception in Method:%s%s\n", name, sig);
我们使用 JVMTI GetThreadInfo 和 GetThreadGroupInfo 接口来显示当前线程和组详细信息。
err = (*jvmti)->GetThreadInfo(jvmti, thr, &info);
if (err == JVMTI_ERROR_NONE) {
err1 = (*jvmti)->GetThreadGroupInfo(jvmti,info.thread_group, &groupInfo);
...
if ((err == JVMTI_ERROR_NONE) && (err1 == JVMTI_ERROR_NONE ))
{
printf("Got Exception event, Current Thread is : %s and Thread Group is: %s\n",
((info.name==NULL) ? "" : info.name), groupInfo.name);
}
}
这将在您的终端上产生以下输出:
Got Exception event, Current Thread is : MyThread0 and Thread Group is: main
使用 JVMTI GetOwnedMonitorInfo 接口可以获取关于指定线程所拥有的监视器的信息。此函数 不要求挂起线程。
err = (*jvmti)->GetOwnedMonitorInfo(jvmti, thr, νm_monitors, &arr_monitors);
printf("Number of Monitors returned : %d\n", num_monitors);
使用 JVMTI GetThreadState 接口可以获取线程的状态信息。
线程状态可以为以下值之一:
线程已终止
线程活动
线程可运行
线程休眠
线程在等待通知
线程处于对象等待状态
线程为本地状态
线程已挂起
线程已中断
err = (*jvmti)->GetThreadState(jvmti, thr, &thr_st_ptr);
if ( thr_st_ptr & JVMTI_THREAD_STATE_RUNNABLE ) {
printf("Thread: %s is Runnable\n", ((info.name==NULL) ? "" : info.name));
flag = 1;
}
使用 JVMTI 显示 JVM 中的所有用户线程
JVMTI 函数 GetAllThreads 用于显示 JVM 已知的所有活动线程。这些线程是关联到 VM 的 Java 编程语言线程。
以下代码对此进行了说明:
/* Get All Threads */
err = (*jvmti)->GetAllThreads(jvmti, &thr_count, &thr_ptr);
if (err != JVMTI_ERROR_NONE) {
printf("(GetAllThreads) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
if (err == JVMTI_ERROR_NONE && thr_count >= 1) {
int i = 0;
printf("Thread Count: %d\n", thr_count);
for ( i=0; i < thr_count; i++) {
/* Make sure the stack variables are garbage free */
(void)memset(&info1,0, sizeof(info1));
err1 = (*jvmti)->GetThreadInfo(jvmti, thr_ptr[i], &info1);
if (err1 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err1);
describe(err1);
printf("\n");
}
printf("Running Thread#%d: %s, Priority: %d, context class loader:%s\n", i+1,info1.name,
info1.priority,(info1.context_class_loader == NULL ? ": NULL" : "Not Null"));
/* Every string allocated by JVMTI needs to be freed */
err2 = (*jvmti)->Deallocate(jvmti, (void*)info1.name);
if (err2 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err2);
describe(err2);
printf("\n");
}
}
}
这将在您的终端上产生以下输出:
Thread Count: 5
Running Thread#1: MyThread4, Priority: 5, context class loader:Not Null
Running Thread#2: Signal Dispatcher, Priority: 10, context class loader:Not Null
Running Thread#3: Finalizer, Priority: 8, context class loader:: NULL
Running Thread#4: Reference Handler, Priority: 10, context class loader:: NULL
Running Thread#5: main, Priority: 5, context class loader:Not Null
获取 JVM 线程堆栈跟踪
JVMTI 接口 GetStackTrace 可用于获取关于线程堆栈的信息。如果 max_count 小于堆栈的深度,最深框架的 max_count 数将返回,否则返回整个堆栈。调用此函数无需挂起线程。
下例产生至多 5 个最深框架。如果存在任何框架,则还将输出当前执行的方法名。
/* Get Stack Trace */
err = (*jvmti)->GetStackTrace(jvmti, thr, 0, 5, &frames, &count);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
printf("Number of records filled: %d\n", count);
if (err == JVMTI_ERROR_NONE && count >=1) {
char *methodName;
methodName = "yet_to_call()";
char *declaringClassName;
jclass declaring_class;
int i=0;
printf("Exception Stack Trace\n");
printf("=====================\n");
printf("Stack Trace Depth: %d\n", count);
for ( i=0; i < count; i++) {
err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s() in class %s\n", methodName, declaringClassName);
}
}
}
}
这将使您的终端产生以下输出:
Number of records filled: 3
Thread Stack Trace
=====================
Stack Trace Depth: 3
at method throwException() in class LmyThread;
at method run() in class LMyThread;
at method run() in class Ljava/lang/Thread;
使用 JVMTI 分析堆
本节介绍如何获取关于使用堆的信息的示例代码。例如,我们已经按“代理初始化”一节中所述为 VM Object Allocation 事件进行了注册。当 JVM 分配了 Java 编程语言可见但其他工具机制不能探测到的对象时,我们将得到通知。这一点与 JVMPI 截然不同,JVMPI 在分配任何对象时都将发送事件。在 JVMTI 中,针对用户分配的对象不会发送任何事件,因为它期望使用的是字节码工具。例如,在 SimpleThread.java 程序中,分配 MyThread 或 Thread 对象时,我们是不会得到通知的。以后将单独发表一篇文章,描写如何使用字节码工具获取此信息。
VM Object Allocation 事件对于确定有关由 JVM 分配的对象的信息十分有用。在 Agent_OnLoad 方法中,我们将 callbackVMObjectAlloc 注册为发送 VM Object Allocation 事件时调用的函数。回调函数参数包含关于已分配对象的信息,如对象类和对象大小的 JNI 本地参考。借助于 jclass 参数 object_klass,我们可以使用 GetClassSignature 函数获取关于类名的信息。我们可以把下面给出的对象类及其大小打印出来。注意避免过多的输出,我们仅需输出超过 50 个字节的对象信息就行了。
/* Callback function for VM Object Allocation events */
static void JNICALL callbackVMObjectAlloc
(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread,
jobject object, jclass object_klass, jlong size) {
...
char *className;
...
if (size > 50) {
err = (*jvmti)->GetClassSignature(jvmti, object_klass, &className, NULL);
if (className != NULL) {
printf("\ntype %s object allocated with size %d\n", className, (jint)size);
}
...
我们使用上面所介绍的 GetStackTrace 方法来输出正在分配该对象的线程的堆栈跟踪。我们依照该节所述获取指定深度的 框架。这些框架将作为 jvmtiFrameInfo 结构返回,这些结构包含每个框架的 jmethodID(即 frames[x].method)。GetMethodName 函数可以将 jmethodID 映射到特殊的方法名中。在此示例的最后部分,我们还将使用 GetMethodDeclaringClass 和 GetClassSignature 函数获取从其中调用过此方法的类的名称。
char *methodName;
char *declaringClassName;
jclass declaring_class;
jvmtiError err;
//print stack trace
jvmtiFrameInfo frames[5];
jint count;
int i;
err = (*jvmti)->GetStackTrace(jvmti, NULL, 0, 5, &frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1) {
for (i = 0; i < count; i++) {
err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s in class %s\n", methodName, declaringClassName);
}
}
}
}
...
注意,完成任务时应释放由这些函数分配给 char 数组的内存:
err = (*jvmti)->Deallocate(jvmti, (void*)className);
err = (*jvmti)->Deallocate(jvmti, (void*)methodName);
err = (*jvmti)->Deallocate(jvmti, (void*)declaringClassName);
...
此代码的输出如下所示:
type Ljava/lang/reflect/Constructor; object allocated with size 64
at method getDeclaredConstructors0 in class Ljava/lang/Class;
at method privateGetDeclaredConstructors in class Ljava/lang/Class;
at method getConstructor0 in class Ljava/lang/Class;
at method getDeclaredConstructor in class Ljava/lang/Class;
at method run in class Ljava/util/zip/ZipFile$1;
原始类的返回名称是相应原始类型的签名字符类型。例如,java.lang.Integer.TYPE 为“I”。
在 VM Object Allocation 的回调方法中,我们仍将使用 IterateOverObjectsReachableFromObject 函数演示如何获取关于堆的附加信息。在此示例中,我们将 JNI 参考作为一个参数传递给刚刚分配的对象,该函数将在此新分配对象所能直接或间接到达的所有对象中迭代。对于每个可到达的对象,另外还有一个定义的回调函数可对其进行描述。在此示例中,传递到 IterateOverObjectsReachableFromObject 的回调函数名为 reference_object:
err = (*jvmti)->IterateOverObjectsReachableFromObject
(jvmti, object, &reference_object, NULL);
if ( err != JVMTI_ERROR_NONE ) {
printf("Cannot iterate over reachable objects\n");
}
...
reference_object 函数定义如下:
/* JVMTI callback function. */
static jvmtiIterationControl JNICALL
reference_object(jvmtiObjectReferenceKind reference_kind,
jlong class_tag, jlong size, jlong* tag_ptr,
jlong referrer_tag, jint referrer_index, void *user_data)
{
...
return JVMTI_ITERATION_CONTINUE;
}
...
在此示例中,我们使用 IterateOverObjectsReachableFromObject 函数计算新分配对象所能到达的所有对象的 总的大小,以及它们的对象类型。对象类型可以从 reference_kind 参数中确定。然后打印此信息以接收如下输出:
This object has references to objects of combined size 21232
This includes 45 classes, 9 fields, 1 arrays, 0 classloaders, 0 signers arrays,
0 protection domains, 19 interfaces, 13 static fields, and 2 constant pools.
注意,位于 JVMTI 中的类似迭代函数允许迭代的对象有:整个堆(可到达的和不可到达的);根目录对象和根目录对象所能直接或间接到达的所有对象;堆中 是指定类的实例的所有对象。使用这些函数的技巧和前面所介绍的类似。在执行这些函数期间,堆的状态没有任何变化:没有分配任何对象,没有对任何对象进行垃圾收集,并且对象的状态(包括堆值)也没有任何变化。结果,执行 Java 编程语言代码的线程、尝试恢复执行 Java 编程语言代码的线程和尝试执行 JNI 函数的线程都完全停了下来。所以,在对象参考回调函数中,不能使用任何 JNI 函数;在没有特别允许的情况下,也不允许使用任何 JVMTI 函数。
编译和执行示例代码
要编译并运行这里描述的示例应用程序的代码,请按以下步骤操作:
设置 JDK_PATH 为指向 J2SE 1.5 发行版
JDK_PATH="/home/xyz/j2sdk1.5.0/bin"
使用 C 语言编译器构建共享库。我们使用的是 Sun Studio 8 C 编译器。
CC="/net/compilers/S1Studio_8.0/SUNWspro/bin/cc"
echo "...creating liba.so"
${CC} -G -KPIC -o liba.so
-I${JDK_PATH}/include -I${JDK_PATH}/include/solaris a.c
要加载并运行代理库,请在 VM 启动过程中使用下面的命令行参数之一。
-agentlib:<jvmti-agent-library-name>
-agentpath:/home/foo/jvmti/<jvmti-agent-library-name>
然后如下运行示例 Java 应用程序:
echo "...creating SimpleThread.class"
${JDK_PATH}/bin/javac -g -d . SimpleThread.java
echo "...running SimpleThread.class"
LD_LIBRARY_PATH=. CLASSPATH=. ${JDK_PATH}/bin/java
-showversion -agentlib:a SimpleThread
注意:此示例代理代码是在 Solaris 9 Operating System 上构建和测试的。
结束语
在本文中,我们演示了 JVMTI 提供用于监控和管理 JVM 的一些接口。JVMTI 规范 (JSR-163) 旨在为需要访问 VM 状态的广泛的工具提供一个 VM 接口,这些工具包括但不限于:分析、调试、监控、线程分析和覆盖率分析工具。
建议开发人员不要使用 JVMPI 接口开发工具或调试实用工具,因为 JVMPI 是一种不受支持的实验技术。应考虑使用 JVMTI 编写 Java 虚拟机的所有分析和管理工具。
引自:http://www.zhujiangroad.com/html/soft/s2395.html
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 签名如下所示:
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 */
}
使用 JVMTI 分析线程
本节介绍如何获取关于在 JVM 中运行的用户线程的信息。如前所述,启动 JVM 时,JVMTI 代理库中的启动函数 Agent_OnLoad 将被调用。在 VM 初始化过程中,JVMTI_EVENT_VM_INIT 类型的 JVMTI Event 将生成并被发送到代理代码的 callbackVMInit 例程中。一旦 VM 初始化事件被接收(即 调用VMInit 回调),代理即可结束其初始化。现在,此代理可以自由调用任何 Java Native Interface (JNI) 或 JVMTI 函数。此时,我们已经处于活动阶段,将启用本 VMInit 回调例程中的 Exception 事件(JVMTI_EVENT_EXCEPTION)。
error = (*jvmti)->SetEventNotificationMode
(jvmti, JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);
无论何时,只要在 Java 编程语言方法中首次探测到异常,就会生成 Exception 事件。此异常可能由 Java 编程语言抛出,也可能由本地方法抛出;但是如果由本地方法抛出,直到 Java 编程语言方法首次发现此异常时该事件才会生成。如果异常已被处理并清除,则异常事件不会生成。
出于演示目的,下面给出了所用的示例 Java 应用程序。主线程创建了 5 个线程,这 5 个线程退出前各自抛出一个异常。一旦启动 JVM,JVMTI_EVENT_VM_INIT 将生成并被发送到代理代码中进行处理,因为我们已经在代理代码中启用了 VMInit 和 Exception 事件。随后,当 Java 线程抛出一个异常时,JVMTI_EVENT_EXCEPTION 将被发送到代理代码中。然后,代理代码 会分析此线程信息并显示当前线程名、它所属的线程组、此线程所拥有的监视器、线程状态、线程堆栈跟踪及 JVM 中的所有用户线程。
public class SimpleThread {
static MyThread t;
public static void main(String args[]) throws Throwable{
t = new MyThread();
System.out.println("Creating and running 10 threads...");
for(int i = 0; i < 5; i++) {
Thread thr = new Thread(t,"MyThread"+i);
thr.start();
try {
thr.join();
} catch (Throwable t) {
}
}
}
}
class MyThread implements Runnable {
Thread t;
public MyThread() {}
public void run() {
/* NO-OP */
try {
"a".getBytes("ASCII");
throwException();
Thread.sleep(1000);
} catch (java.lang.InterruptedException e){
e.printStackTrace();
} catch (Throwable t) {
}
}
public void throwException() throws Throwable{
throw new Exception("Thread Exception from MyThread");
}
}
我们来看一下 Java 应用程序内部抛出一个异常时 JVMTI 代理代码的执行情况。
throw new Exception("Thread Exception from MyThread");
JVMTI 异常事件生成后将被发送到代理代码的 Exception 回调例程中。代理必须添加 can_generate_exception_events 功能才能启用异常事件。我们使用 JVMTI GetMethodName 接口来显示生成异常的方法名和例程签名。
err3 = (*jvmti)->GetMethodName(jvmti, method, &name, &sig, &gsig);
printf("Exception in Method:%s%s\n", name, sig);
我们使用 JVMTI GetThreadInfo 和 GetThreadGroupInfo 接口来显示当前线程和组详细信息。
err = (*jvmti)->GetThreadInfo(jvmti, thr, &info);
if (err == JVMTI_ERROR_NONE) {
err1 = (*jvmti)->GetThreadGroupInfo(jvmti,info.thread_group, &groupInfo);
...
if ((err == JVMTI_ERROR_NONE) && (err1 == JVMTI_ERROR_NONE ))
{
printf("Got Exception event, Current Thread is : %s and Thread Group is: %s\n",
((info.name==NULL) ? "" : info.name), groupInfo.name);
}
}
这将在您的终端上产生以下输出:
Got Exception event, Current Thread is : MyThread0 and Thread Group is: main
使用 JVMTI GetOwnedMonitorInfo 接口可以获取关于指定线程所拥有的监视器的信息。此函数 不要求挂起线程。
err = (*jvmti)->GetOwnedMonitorInfo(jvmti, thr, νm_monitors, &arr_monitors);
printf("Number of Monitors returned : %d\n", num_monitors);
使用 JVMTI GetThreadState 接口可以获取线程的状态信息。
线程状态可以为以下值之一:
线程已终止
线程活动
线程可运行
线程休眠
线程在等待通知
线程处于对象等待状态
线程为本地状态
线程已挂起
线程已中断
err = (*jvmti)->GetThreadState(jvmti, thr, &thr_st_ptr);
if ( thr_st_ptr & JVMTI_THREAD_STATE_RUNNABLE ) {
printf("Thread: %s is Runnable\n", ((info.name==NULL) ? "" : info.name));
flag = 1;
}
使用 JVMTI 显示 JVM 中的所有用户线程
JVMTI 函数 GetAllThreads 用于显示 JVM 已知的所有活动线程。这些线程是关联到 VM 的 Java 编程语言线程。
以下代码对此进行了说明:
/* Get All Threads */
err = (*jvmti)->GetAllThreads(jvmti, &thr_count, &thr_ptr);
if (err != JVMTI_ERROR_NONE) {
printf("(GetAllThreads) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
if (err == JVMTI_ERROR_NONE && thr_count >= 1) {
int i = 0;
printf("Thread Count: %d\n", thr_count);
for ( i=0; i < thr_count; i++) {
/* Make sure the stack variables are garbage free */
(void)memset(&info1,0, sizeof(info1));
err1 = (*jvmti)->GetThreadInfo(jvmti, thr_ptr[i], &info1);
if (err1 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err1);
describe(err1);
printf("\n");
}
printf("Running Thread#%d: %s, Priority: %d, context class loader:%s\n", i+1,info1.name,
info1.priority,(info1.context_class_loader == NULL ? ": NULL" : "Not Null"));
/* Every string allocated by JVMTI needs to be freed */
err2 = (*jvmti)->Deallocate(jvmti, (void*)info1.name);
if (err2 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err2);
describe(err2);
printf("\n");
}
}
}
这将在您的终端上产生以下输出:
Thread Count: 5
Running Thread#1: MyThread4, Priority: 5, context class loader:Not Null
Running Thread#2: Signal Dispatcher, Priority: 10, context class loader:Not Null
Running Thread#3: Finalizer, Priority: 8, context class loader:: NULL
Running Thread#4: Reference Handler, Priority: 10, context class loader:: NULL
Running Thread#5: main, Priority: 5, context class loader:Not Null
获取 JVM 线程堆栈跟踪
JVMTI 接口 GetStackTrace 可用于获取关于线程堆栈的信息。如果 max_count 小于堆栈的深度,最深框架的 max_count 数将返回,否则返回整个堆栈。调用此函数无需挂起线程。
下例产生至多 5 个最深框架。如果存在任何框架,则还将输出当前执行的方法名。
/* Get Stack Trace */
err = (*jvmti)->GetStackTrace(jvmti, thr, 0, 5, &frames, &count);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
printf("Number of records filled: %d\n", count);
if (err == JVMTI_ERROR_NONE && count >=1) {
char *methodName;
methodName = "yet_to_call()";
char *declaringClassName;
jclass declaring_class;
int i=0;
printf("Exception Stack Trace\n");
printf("=====================\n");
printf("Stack Trace Depth: %d\n", count);
for ( i=0; i < count; i++) {
err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s() in class %s\n", methodName, declaringClassName);
}
}
}
}
这将使您的终端产生以下输出:
Number of records filled: 3
Thread Stack Trace
=====================
Stack Trace Depth: 3
at method throwException() in class LmyThread;
at method run() in class LMyThread;
at method run() in class Ljava/lang/Thread;
使用 JVMTI 分析堆
本节介绍如何获取关于使用堆的信息的示例代码。例如,我们已经按“代理初始化”一节中所述为 VM Object Allocation 事件进行了注册。当 JVM 分配了 Java 编程语言可见但其他工具机制不能探测到的对象时,我们将得到通知。这一点与 JVMPI 截然不同,JVMPI 在分配任何对象时都将发送事件。在 JVMTI 中,针对用户分配的对象不会发送任何事件,因为它期望使用的是字节码工具。例如,在 SimpleThread.java 程序中,分配 MyThread 或 Thread 对象时,我们是不会得到通知的。以后将单独发表一篇文章,描写如何使用字节码工具获取此信息。
VM Object Allocation 事件对于确定有关由 JVM 分配的对象的信息十分有用。在 Agent_OnLoad 方法中,我们将 callbackVMObjectAlloc 注册为发送 VM Object Allocation 事件时调用的函数。回调函数参数包含关于已分配对象的信息,如对象类和对象大小的 JNI 本地参考。借助于 jclass 参数 object_klass,我们可以使用 GetClassSignature 函数获取关于类名的信息。我们可以把下面给出的对象类及其大小打印出来。注意避免过多的输出,我们仅需输出超过 50 个字节的对象信息就行了。
/* Callback function for VM Object Allocation events */
static void JNICALL callbackVMObjectAlloc
(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread,
jobject object, jclass object_klass, jlong size) {
...
char *className;
...
if (size > 50) {
err = (*jvmti)->GetClassSignature(jvmti, object_klass, &className, NULL);
if (className != NULL) {
printf("\ntype %s object allocated with size %d\n", className, (jint)size);
}
...
我们使用上面所介绍的 GetStackTrace 方法来输出正在分配该对象的线程的堆栈跟踪。我们依照该节所述获取指定深度的 框架。这些框架将作为 jvmtiFrameInfo 结构返回,这些结构包含每个框架的 jmethodID(即 frames[x].method)。GetMethodName 函数可以将 jmethodID 映射到特殊的方法名中。在此示例的最后部分,我们还将使用 GetMethodDeclaringClass 和 GetClassSignature 函数获取从其中调用过此方法的类的名称。
char *methodName;
char *declaringClassName;
jclass declaring_class;
jvmtiError err;
//print stack trace
jvmtiFrameInfo frames[5];
jint count;
int i;
err = (*jvmti)->GetStackTrace(jvmti, NULL, 0, 5, &frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1) {
for (i = 0; i < count; i++) {
err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class);
err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s in class %s\n", methodName, declaringClassName);
}
}
}
}
...
注意,完成任务时应释放由这些函数分配给 char 数组的内存:
err = (*jvmti)->Deallocate(jvmti, (void*)className);
err = (*jvmti)->Deallocate(jvmti, (void*)methodName);
err = (*jvmti)->Deallocate(jvmti, (void*)declaringClassName);
...
此代码的输出如下所示:
type Ljava/lang/reflect/Constructor; object allocated with size 64
at method getDeclaredConstructors0 in class Ljava/lang/Class;
at method privateGetDeclaredConstructors in class Ljava/lang/Class;
at method getConstructor0 in class Ljava/lang/Class;
at method getDeclaredConstructor in class Ljava/lang/Class;
at method run in class Ljava/util/zip/ZipFile$1;
原始类的返回名称是相应原始类型的签名字符类型。例如,java.lang.Integer.TYPE 为“I”。
在 VM Object Allocation 的回调方法中,我们仍将使用 IterateOverObjectsReachableFromObject 函数演示如何获取关于堆的附加信息。在此示例中,我们将 JNI 参考作为一个参数传递给刚刚分配的对象,该函数将在此新分配对象所能直接或间接到达的所有对象中迭代。对于每个可到达的对象,另外还有一个定义的回调函数可对其进行描述。在此示例中,传递到 IterateOverObjectsReachableFromObject 的回调函数名为 reference_object:
err = (*jvmti)->IterateOverObjectsReachableFromObject
(jvmti, object, &reference_object, NULL);
if ( err != JVMTI_ERROR_NONE ) {
printf("Cannot iterate over reachable objects\n");
}
...
reference_object 函数定义如下:
/* JVMTI callback function. */
static jvmtiIterationControl JNICALL
reference_object(jvmtiObjectReferenceKind reference_kind,
jlong class_tag, jlong size, jlong* tag_ptr,
jlong referrer_tag, jint referrer_index, void *user_data)
{
...
return JVMTI_ITERATION_CONTINUE;
}
...
在此示例中,我们使用 IterateOverObjectsReachableFromObject 函数计算新分配对象所能到达的所有对象的 总的大小,以及它们的对象类型。对象类型可以从 reference_kind 参数中确定。然后打印此信息以接收如下输出:
This object has references to objects of combined size 21232
This includes 45 classes, 9 fields, 1 arrays, 0 classloaders, 0 signers arrays,
0 protection domains, 19 interfaces, 13 static fields, and 2 constant pools.
注意,位于 JVMTI 中的类似迭代函数允许迭代的对象有:整个堆(可到达的和不可到达的);根目录对象和根目录对象所能直接或间接到达的所有对象;堆中 是指定类的实例的所有对象。使用这些函数的技巧和前面所介绍的类似。在执行这些函数期间,堆的状态没有任何变化:没有分配任何对象,没有对任何对象进行垃圾收集,并且对象的状态(包括堆值)也没有任何变化。结果,执行 Java 编程语言代码的线程、尝试恢复执行 Java 编程语言代码的线程和尝试执行 JNI 函数的线程都完全停了下来。所以,在对象参考回调函数中,不能使用任何 JNI 函数;在没有特别允许的情况下,也不允许使用任何 JVMTI 函数。
编译和执行示例代码
要编译并运行这里描述的示例应用程序的代码,请按以下步骤操作:
设置 JDK_PATH 为指向 J2SE 1.5 发行版
JDK_PATH="/home/xyz/j2sdk1.5.0/bin"
使用 C 语言编译器构建共享库。我们使用的是 Sun Studio 8 C 编译器。
CC="/net/compilers/S1Studio_8.0/SUNWspro/bin/cc"
echo "...creating liba.so"
${CC} -G -KPIC -o liba.so
-I${JDK_PATH}/include -I${JDK_PATH}/include/solaris a.c
要加载并运行代理库,请在 VM 启动过程中使用下面的命令行参数之一。
-agentlib:<jvmti-agent-library-name>
-agentpath:/home/foo/jvmti/<jvmti-agent-library-name>
然后如下运行示例 Java 应用程序:
echo "...creating SimpleThread.class"
${JDK_PATH}/bin/javac -g -d . SimpleThread.java
echo "...running SimpleThread.class"
LD_LIBRARY_PATH=. CLASSPATH=. ${JDK_PATH}/bin/java
-showversion -agentlib:a SimpleThread
注意:此示例代理代码是在 Solaris 9 Operating System 上构建和测试的。
结束语
在本文中,我们演示了 JVMTI 提供用于监控和管理 JVM 的一些接口。JVMTI 规范 (JSR-163) 旨在为需要访问 VM 状态的广泛的工具提供一个 VM 接口,这些工具包括但不限于:分析、调试、监控、线程分析和覆盖率分析工具。
建议开发人员不要使用 JVMPI 接口开发工具或调试实用工具,因为 JVMPI 是一种不受支持的实验技术。应考虑使用 JVMTI 编写 Java 虚拟机的所有分析和管理工具。
评论
1 楼
leexiaodong2009
2012-04-04
请问要怎么样才可以在web项目中用,比如tomcat里面配置了,但一样的报错,还有spring也解不了....
帮我搞一下可以不?我都弄了好几天了,一直搞不好!!!头都大了啊....
帮我搞一下可以不?我都弄了好几天了,一直搞不好!!!头都大了啊....
相关推荐
Java虚拟机工具接口(JVMTI,Java Virtual Machine Tool Interface)是Java开发工具包(Java SDK)的一个核心组件,自Java 2 SDK Standard Edition 1.5.0版本起引入,用于替代实验性的JVMPI(Java Virtual Machine ...
**JVM TI (Java Virtual Machine Tool Interface)** 是Java虚拟机工具接口,它是Java平台标准版(Java SE)的一部分,提供了一种方式来实现对Java虚拟机的低级别监控和调试。JVM TI允许开发人员创建工具,如内存分析...
Java虚拟机工具接口(JVM Tool Interface,简称JVM TI)是Java平台提供的一种标准机制,允许外部工具监视和管理正在运行的Java虚拟机。它为开发者提供了丰富的API,可以用来实现诸如性能分析、内存检测、调试等高级...
在IT行业中,JDK(Java Development Kit)是Java开发的核心工具集,它包含了编译、运行、调试Java程序所需的所有工具。本主题将深入探讨JDK中的重要工具、JVM(Java Virtual Machine)的垃圾回收机制以及23种经典的...
Java远程调试主要依赖于Java虚拟机(JVM)的调试接口——Java Platform Debugger Architecture(JPDA)。通过JPDA,我们可以在远程服务器上运行的JVM实例中插入断点、单步执行、查看变量值等,就像在本地开发环境中...
Fiddler是一款流行的HTTP代理服务器,主要用于调试和分析网络流量,它可以捕获、修改和重新发送HTTP请求和响应。然而,Fiddler主要专注于前端HTTP交互,对于复杂的后端服务模拟,其功能可能相对有限。Mock平台则专门...
Java代理是Java反射API的一部分,它允许我们创建一个代理类,该类可以实现一组指定的接口,并在调用这些接口的方法时插入自定义的行为。通过这种方式,我们可以实现在运行时对对象行为的拦截和增强。 Coldswap是...
9. **Java动态代理(java.lang.reflect.Proxy)**:在运行时创建接口的动态实现,常用于AOP(面向切面编程)和测试。 10. **类型推断(Type Inference)**:Java 8引入的Lambda表达式支持类型推断,简化了代码,...
- **外部监控集成**:可以与JVM监控工具(如VisualVM)、系统监控工具(如JMX、Prometheus)集成,获取更全面的资源使用情况。 5. **脚本录制与回放** - **代理服务器模式**:JMeter可以作为代理服务器,录制用户...
4. **调试和分析**:ASM也可用于分析类结构,帮助开发者理解和调试应用程序。 **CGLib库**: CGLib是基于ASM库的一个扩展,主要用于为Java类生成子类,从而实现对目标类的代理。CGLib的主要应用场景有: 1. **动态...
《AsmTools与Java .class文件的生成与验证》 AsmTools是OpenJDK项目中的一个重要工具集,主要用于生成和分析Java字节码(....通过熟练掌握AsmTools,开发者可以在面对复杂的代码问题时,拥有更强大的分析和解决能力。
MF00904-Java行为分析系统源码是一个针对Java应用程序进行性能监控和行为分析的工具。这个系统的源码提供了深入理解Java程序运行时行为的机会,有助于开发者优化代码、定位性能瓶颈以及发现潜在的问题。下面我们将...
10. **Java动态代理**:JDK1.8进一步完善了动态代理,使得在运行时创建代理类和接口实现更加便捷。 11. **Nashorn JavaScript引擎**:从JDK8开始,Java引入了Nashorn JavaScript引擎,可以直接在Java环境中执行...
3. **字节码解析**:Javassist提供了对字节码的解析能力,可以方便地读取和分析已存在的类和方法的字节码,这在调试、性能分析和代码理解和优化时非常有用。 4. **类转换**:通过Javassist,开发者可以在类被加载到...
这段代码会告诉Java虚拟机(JVM)在启动时不要加载默认的代理(-Xnoagent),开启调试模式(-Xdebug),并使用指定的参数(-Xrunjdwp)启动JDWP,允许远程调试器连接到地址8002,服务器模式(server=y)且不暂停...
通过这种方式,我们可以在不修改Spring Boot应用源代码的前提下,利用`javaagent`和`premain`方法实现对应用的动态增强,这对于开发工具、性能分析、调试等场景非常有用。需要注意的是,由于`premain`是在JVM启动时...
8. JVM内部机制:通过分析JVM内存模型和字节码,提升程序性能和调试能力。 通过研读这两卷的源代码,开发者可以全面掌握Java编程的核心概念和技术,提升自己的编程技能,并能够解决实际项目中遇到的问题。这些源...
"jdk.jdwp.agent"是Java调试协议(JDWP)的代理部分,它使得调试器能够连接到JVM,进行远程调试。JDWP定义了一套消息协议,让调试器能够获取程序状态,设置断点等。 "jdk.jcmd"是一个命令行工具,提供了多种诊断和...
在JSP开发过程中,常常需要使用到调试工具来确保代码的正确性和性能优化。本文将详细介绍如何在Eclipse中开发一个用于调试JSP的插件,并探讨其中涉及的关键技术点。 #### 二、开发环境 本文介绍的插件开发及调试...
ASM-3.2是这个工具的一个版本,主要用于动态程序分析和字节码操纵。这个框架广泛应用于许多领域,如代码混淆、性能监控、动态代理以及字节码级别的编程。 **ASM的概述** ASM是一个低级别的库,它可以解析、生成和...