JVMTI example

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "jvmti.h"
#include "jni.h"
#pragma GCC diagnostic ignored "-Wwrite-strings"
#pragma GCC diagnostic ignored "-Wmissing-declarations"

typedef struct {
    /* JVMTI Environment */
    jvmtiEnv *jvmti;
    JNIEnv * jni;
    jboolean vm_is_started;
    jboolean vmDead;

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

struct Field;
/**
 * We will need to cache information about each java class to be able to reference back to their fields later
 */
typedef struct Clazz {
    char *name;
    jclass clazz;
    Field **fields;
    int nFields;
    bool fieldsCalculated;
    Clazz * super;
    Clazz ** intfcs;
    int nIntfc;
    int fieldOffsetFromParent;
    int fieldOffsetFromInterfaces;
    bool enqueued;
} Clazz;
typedef struct Field {
    Clazz *clazz;
    char *name;
    jfieldID fieldID;
};
typedef struct FieldList {
    Field * car;
    struct FieldList * cdr;
} FieldList;
struct Tag;
typedef struct PointedToList {
    Tag * car;
    struct PointedToList * cdr;
};
/**
 * The datastructure that we will associate each object with
 */
typedef struct Tag {
    FieldList * directFieldList;
    PointedToList * pointedTo;
    Clazz * clazz;
    bool visited;
};
typedef struct ClassList {
    Clazz * car;
    struct ClassList *cdr;
} ClassList;
static Clazz ** classCache;

static int sizeOfClassCache;
static GlobalAgentData *gdata;

void fatal_error(const char * format, ...) {
    va_list ap;

    va_start(ap, format);
    (void) vfprintf(stderr, format, ap);
    (void) fflush(stderr);
    va_end(ap);
    exit(3);
}

static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum,
        const char *str) {
    if (errnum != JVMTI_ERROR_NONE) {
        char *errnum_str;

        errnum_str = NULL;
        (void) jvmti->GetErrorName(errnum, &errnum_str);

        printf("ERROR: JVMTI: %d(%s): %s\n", errnum,
                (errnum_str == NULL ? "Unknown" : errnum_str),
                (str == NULL ? "" : str));
    }
}
/* Enter a critical section by doing a JVMTI Raw Monitor Enter */
static void enter_critical_section(jvmtiEnv *jvmti) {
    jvmtiError error;

    error = jvmti->RawMonitorEnter(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(gdata->lock);
    check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
}
/**
 * Compute the offset of a class' fields from its super class,
 * following the spec here: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField
 */
static int computeFieldOffsetsFromParent(Clazz * c) {
    if (!c->super) {
        if (c->fieldOffsetFromParent < 0)
            c->fieldOffsetFromParent = 0;
        return c->fieldOffsetFromParent;
    }
    if (c->fieldOffsetFromParent < 0) {
        c->fieldOffsetFromParent = computeFieldOffsetsFromParent(c->super);
        return c->fieldOffsetFromParent + c->nFields;
    }
    return c->fieldOffsetFromParent + c->nFields;
}
/**
 * Collect all of the interfaces that a class implements (including super interfaces, parent classes, etc)
 * Needed to calculate field offsets.
 */
static void collectAllInterfaces(Clazz *c, ClassList *lst) {
    int i;
    for (i = 0; i < c->nIntfc; i++) {
        if (c->intfcs[i] && !c->intfcs[i]->enqueued) {
            c->intfcs[i]->enqueued = true;
            lst->cdr = new ClassList();
            lst->cdr->car = c->intfcs[i];
            lst->cdr->cdr = NULL;
            collectAllInterfaces(c->intfcs[i], lst);
        }
    }
    if (c->super)
        collectAllInterfaces(c->super, lst);
}
/**
 * Compute the offset of a class' fields from its parent interfaces,
 * following the spec here: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField
 */
static void computeFieldOffsetsFromInterfaces(Clazz * c) {
    if (c->fieldOffsetFromInterfaces < 0) {
        //Collect all interfaces
        ClassList lst;
        lst.cdr = NULL;
        collectAllInterfaces(c, &lst);
        ClassList * p;
        p = lst.cdr;
        c->fieldOffsetFromInterfaces = 0;
        while (p && p->car) {
            c->fieldOffsetFromInterfaces += p->car->nFields;
            p->car->enqueued = false;
            p = p->cdr;
        }
    }
}
/**
 * Given a field index (as returned as a jvmtiHeapReferenceInfoField), find the actual field that it represents
 * See http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfoField
 */
static Field* getField(Clazz * c, int rawIdx) {
    if (c->fieldOffsetFromInterfaces < 0)
        computeFieldOffsetsFromParent(c);
    if (c->fieldOffsetFromInterfaces < 0)
        computeFieldOffsetsFromInterfaces(c);
    int idx = rawIdx - c->fieldOffsetFromInterfaces;
    if (idx >= c->fieldOffsetFromParent)
        idx = idx - c->fieldOffsetFromParent;
    else
        return getField(c->super, rawIdx);
    if (idx < 0 || idx >= c->nFields)
        return NULL;
    return c->fields[idx];
}
/**
 * Free the tag structure that we associate with objects.
 * Typically will just delete pointer information, unless freeFully is specified, in which case
 * the tag will be freed completely.
 */
static void freeTag(Tag *tag, bool freeFully) {
    if (tag) {
        tag->visited = 0;
        if (tag->clazz && freeFully) {
            Clazz * c = tag->clazz;
            if (c->name)
                gdata->jvmti->Deallocate((unsigned char*) (void*) c->name);
            if (c->fields) {
                int i;
                for (i = 0; i < c->nFields; i++) {
                    if (c->fields[i]->name) {
                        gdata->jvmti->Deallocate(
                                (unsigned char*) (void*) c->fields[i]->name);
                    }
                    delete (c->fields[i]);
                }
                delete (c->fields);
            }
            if (c->intfcs)
                delete (c->intfcs);
        }
        if (tag->directFieldList) {
            FieldList *t;
            while (tag->directFieldList) {
                t = tag->directFieldList->cdr;
                free(tag->directFieldList);
                tag->directFieldList = t;
            }
        }
        if (tag->pointedTo) {
            PointedToList *t;
            while (tag->pointedTo) {
                t = tag->pointedTo->cdr;
                free(tag->pointedTo);
                tag->pointedTo = t;
            }
        }
        if (freeFully)
            free(tag);
    }
}
/**
 * Callback for when an object is freed - we need to delete our tag on the object too.
 * Note that because our tag is just malloc'ed (and not a java object), we can trivially free
 * it directly within this callback.
 */
static void JNICALL
cbObjectFree(jvmtiEnv *jvmti_env, jlong tag) {
    if (gdata->vmDead) {
        return;
    }
    jvmtiError error;
    if (tag) {
        freeTag((Tag*) tag, true);
    }
}
/**
 * Update our cache of all of the loaded classes and their fields. We will need this information
 * to understand field relationships, which require calculating super classes, super interfaces, etc.
 */
static void updateClassCache(JNIEnv *env) {
    jvmtiError err;
    jint nClasses;
    jclass* classes;

    err = gdata->jvmti->GetLoadedClasses(&nClasses, &classes);
    check_jvmti_error(gdata->jvmti, err, "Cannot get classes");
    if (classCache != NULL) {
        free(classCache);
    }
    classCache = new Clazz*[nClasses];
    memset(classCache, 0, sizeof(Clazz*) * nClasses);
    sizeOfClassCache = nClasses;

    Clazz *c;
    int i;
    jlong tag;
    jint status;
    Tag *t;
    for (i = 0; i < nClasses; i++) {
        err = gdata->jvmti->GetTag(classes[i], &tag);
        check_jvmti_error(gdata->jvmti, err, "Unable to get class tag");
        if (tag) //We already built this class info object - and the class info object is in the tag of the class object
        {
            classCache[i] = ((Tag*) tag)->clazz;
            continue;
        }
        err = gdata->jvmti->GetClassStatus(classes[i], &status);
        check_jvmti_error(gdata->jvmti, err, "Cannot get class status");
        if ((status & JVMTI_CLASS_STATUS_PREPARED) == 0) {
            classCache[i] = NULL;
            continue;
        }
        c = new Clazz();
        t = new Tag();
        t->visited = false;
        t->clazz = c;
        t->directFieldList = NULL;
        t->pointedTo = NULL;
        c->fieldOffsetFromParent = -1;
        c->fieldOffsetFromInterfaces = -1;
        c->fieldsCalculated = false;
        c->clazz = (jclass) env->NewGlobalRef(classes[i]);
        gdata->jvmti->GetClassSignature(classes[i], &c->name, NULL);
        classCache[i] = c;
        err = gdata->jvmti->SetTag(classes[i], (ptrdiff_t) (void*) t);
        check_jvmti_error(gdata->jvmti, err, "Cannot set class tag");
    }
    jclass *intfcs;
    jfieldID *fields;
    int j;
    jclass super;
    //Now that we've built info on each class, we will make sure that for each one,
    //we also have pointers to its super class, interfaces, etc.
    for (i = 0; i < nClasses; i++) {
        if (classCache[i] && !classCache[i]->fieldsCalculated) {
            c = classCache[i];

            super = env->GetSuperclass(classes[i]);
            if (super) {
                err = gdata->jvmti->GetTag(super, &tag);
                check_jvmti_error(gdata->jvmti, err, "Cannot get super class");
                if (tag)
                    c->super = ((Tag*) (ptrdiff_t) tag)->clazz;
            }

            //Get the fields
            err = gdata->jvmti->GetClassFields(c->clazz, &(c->nFields),
                    &fields);
            check_jvmti_error(gdata->jvmti, err, "Cannot get class fields");
            c->fields = new Field*[c->nFields];
            for (j = 0; j < c->nFields; j++) {
                c->fields[j] = new Field();
                c->fields[j]->clazz = c;
                c->fields[j]->fieldID = fields[j];
                err = gdata->jvmti->GetFieldName(c->clazz, fields[j],
                        &(c->fields[j]->name), NULL, NULL);
                check_jvmti_error(gdata->jvmti, err, "Can't get field name");

            }
            gdata->jvmti->Deallocate((unsigned char *) (void*) fields);

            //Get the interfaces
            err = gdata->jvmti->GetImplementedInterfaces(classes[i],
                    &(c->nIntfc), &intfcs);
            check_jvmti_error(gdata->jvmti, err, "Cannot get interface info");
            c->intfcs = new Clazz*[c->nIntfc];
            for (j = 0; j < c->nIntfc; j++) {
                err = gdata->jvmti->GetTag(intfcs[j], &tag);
                check_jvmti_error(gdata->jvmti, err,
                        "Cannot get interface info");
                if (tag) {
                    c->intfcs[j] = ((Tag*) tag)->clazz;
                } else
                    c->intfcs[j] = NULL;
            }
            gdata->jvmti->Deallocate((unsigned char*) (void*) intfcs);
            classCache[i]->fieldsCalculated = 1;
        }
    }
}
/**
 * Callback for heap reference following. Propogates points-to through tags.
 * For objects that are directly pointed to by a static field, it will also
 * note which static fields point to them.
 */
JNIEXPORT static int cbHeapReference(jvmtiHeapReferenceKind reference_kind,
        const jvmtiHeapReferenceInfo* reference_info, jlong class_tag,
        jlong referrer_class_tag, jlong size, jlong* tag_ptr,
        jlong* referrer_tag_ptr, jint length, void* user_data) {
    jvmtiError err;
    if (reference_kind == JVMTI_HEAP_REFERENCE_CONSTANT_POOL)
        return 0;
    if (reference_kind != JVMTI_HEAP_REFERENCE_FIELD
            && reference_kind != JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT
            && reference_kind != JVMTI_HEAP_REFERENCE_STATIC_FIELD) {
        //We won't bother propogating pointers along other kinds of references
        //(e.g. from a class to its classloader - see http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceKind )
        return JVMTI_VISIT_OBJECTS;
    }

    if (reference_kind == JVMTI_HEAP_REFERENCE_STATIC_FIELD) {
        //We are visiting a static field directly.
        if (referrer_tag_ptr > 0) {
            Clazz* c = ((Tag*) (*referrer_tag_ptr))->clazz;
            Field* f = getField(c, reference_info->field.index);
            if (f) {
                if (*tag_ptr == 0) {
                    FieldList * fl = new FieldList();
                    fl->car = f;
                    fl->cdr = NULL;
                    Tag * t = new Tag();
                    t->visited = false;
                    t->clazz = NULL;
                    t->directFieldList = fl;
                    t->pointedTo = NULL;
                    *tag_ptr = (ptrdiff_t) (void*) t;
                }
                if (*tag_ptr) {
                    //Something is already pointing to this object
                    FieldList * fl = ((Tag*) *tag_ptr)->directFieldList;
                    if (!fl) {
                        fl = new FieldList();
                        fl->car = f;
                        fl->cdr = NULL;
                        ((Tag*) *tag_ptr)->directFieldList = fl;
                        return JVMTI_VISIT_OBJECTS;
                    }
                    FieldList *p = fl;

                    while (fl && fl->car) {
                        if (fl->car == f) {
                            return JVMTI_VISIT_OBJECTS;
                        }
                        if (!fl->cdr)
                            break;
                        fl = fl->cdr;
                    }

                    fl->cdr = new FieldList();
                    fl->cdr->car = f;
                    fl->cdr->cdr = NULL;
                }
            }
        }
    }

    //Not directly pointed to by an SF.
    if (*referrer_tag_ptr) {
        if (!*tag_ptr) {
            Tag * t = new Tag();
            t->visited = false;
            t->clazz = NULL;
            t->directFieldList = NULL;
            t->pointedTo = NULL;
            *tag_ptr = (ptrdiff_t) (void*) t;
        }
        PointedToList * t = new PointedToList();
        t->car = (Tag*) *referrer_tag_ptr;
        t->cdr = ((Tag*) *tag_ptr)->pointedTo;
        ((Tag*) *tag_ptr)->pointedTo = t;
    }

    return JVMTI_VISIT_OBJECTS;
}
/**
 * Append all fields in the "toAppendList" to the "appendTo" list, making
 * sure not to add duplicates
 */
static void appendFields(FieldList * toAppend, FieldList ** appendTo) {
    FieldList * p;
    bool found;
    while (toAppend && toAppend->car) {
        p = *appendTo;
        found = false;
        if (!p) {
            *appendTo = new FieldList();
            (*appendTo)->car = toAppend->car;
            (*appendTo)->cdr = NULL;
            found = true;
        } else
            while (p && p->car) {
                if (p->car == toAppend->car) {
                    found = true;
                    break;
                }
                if (p->cdr == NULL)
                    break;
                p = p->cdr;
            }
        if (!found) {
            p->cdr = new FieldList();
            p->cdr->car = toAppend->car;
            p->cdr->cdr = NULL;
        }
        toAppend = toAppend->cdr;
    }
}
/**
 * For a given object represented by tag t, make its list of static field
 * roots complete, by recursively appending all of the static fields that
 * point to things that point to this.
 */
static FieldList* visitPointsTo(Tag * t) {
    if (t->visited)
        return t->directFieldList;
    PointedToList * p = t->pointedTo;
    t->visited = true;
    while (p && p->car) {
        appendFields(visitPointsTo(p->car), &t->directFieldList);
        p = p->cdr;
    }
    return t->directFieldList;
}
/*
 * Implementation of _getObjRoots JNI function.
 * Uses visitPointsTo, then simply builds a java array of the result to return.
 */
JNIEXPORT static jobjectArray JNICALL getObjRoots(JNIEnv *env, jclass klass, jobject o) {
    if (gdata->vmDead) {
        return NULL;
    }
    if (!o) {
        return NULL;
    }
    jvmtiError error;
    jlong tag;

    error = gdata->jvmti->GetTag(o, &tag);
    check_jvmti_error(gdata->jvmti, error, "Cannot get object tag");
    visitPointsTo((Tag *) tag);

    FieldList * fl = ((Tag *) tag)->directFieldList;
    int nObjs = 0;
    FieldList * t = fl;
    while (t && t->car) {
        nObjs++;
        t = t->cdr;
    }
    jclass fieldClass = env->FindClass("java/lang/reflect/Field");
    if(fieldClass == NULL)
        fatal_error("Unable to find 'field' class!");
    jobjectArray ret = (jobjectArray) env->NewObjectArray(nObjs,
            fieldClass, NULL);
    jobject _o;
    int i = 0;
    while (fl && fl->car) {
        _o = env->ToReflectedField(fl->car->clazz->clazz,fl->car->fieldID,true);
//                env->NewObject(gdata->staticFieldClass,
//                gdata->staticFieldConstructor, fl->car->clazz->clazz,
//                env->NewStringUTF(fl->car->name));
        env->SetObjectArrayElement(ret, i, _o);
        fl = fl->cdr;
        i++;
    }
    return ret;
}
/**
 * Callback that we use to make sure that points-to data gets cleared up before
 * we begin building it again. Needed so that we can invoke crawlHeap() multiple times
 * in different JVM states and get different results :)
 */
jvmtiIterationControl cbHeapCleanup(jvmtiObjectReferenceKind reference_kind,
        jlong class_tag, jlong size, jlong* tag_ptr, jlong referrer_tag,
        jint referrer_index, void* user_data) {
    if (*tag_ptr) {
        Tag *t = (Tag*) *tag_ptr;
        freeTag(t, false);
    }
    return JVMTI_ITERATION_CONTINUE;
}
/**
 * Implementation of JNI _crawlHeap function. First, it makes sure that we know about
 * all of the currently loaded classes.
 * Then, it clears any existing points-to data cached.
 * Finally, it will follow references through the heap and build a reverse points-to graph
 */
JNIEXPORT static void JNICALL crawlHeap(JNIEnv *env, jclass klass) {
    if (gdata->vmDead) {
        return;
    }
    updateClassCache(env);
    gdata->jvmti->IterateOverReachableObjects(NULL, NULL, &cbHeapCleanup,
            NULL);

    jvmtiHeapCallbacks * cb = new jvmtiHeapCallbacks();
    memset(cb, 0, sizeof(jvmtiHeapCallbacks));
    cb->heap_reference_callback = &cbHeapReference;
    gdata->jvmti->FollowReferences(0,NULL, NULL, cb, NULL);
}

/*
 * Callback we receive when the JVM terminates - no more functions can be called after this
 */
static void JNICALL callbackVMDeath(jvmtiEnv * jvmti_env, JNIEnv * jni_env)
{
    gdata->vmDead = JNI_TRUE;
}

/*
 * Callback we get when the JVM starts up, but before its initialized.
 * Sets up the JNI calls.
 */
static void JNICALL cbVMStart(jvmtiEnv * jvmti, JNIEnv * env)
{

    enter_critical_section (jvmti);
    {
        jclass klass;
        jfieldID field;
        jint rc;
        static JNINativeMethod registry[2] =
        {    {    "_crawl", "()V", (void*) &crawlHeap},
            {    "_getRoots",
                "(Ljava/lang/Object;)[Ljava/lang/reflect/Field;",
                (void*) &getObjRoots}};
        /* Register Natives for class whose methods we use */
        klass = env->FindClass(
                "org/vatuse/monitor/heap/runtime/HeapWalker");
        if (klass == NULL) {
            fatal_error(
                    "ERROR: JNI: Cannot find JNI Helper with FindClass\n");
        }

        rc = env->RegisterNatives(klass, registry, 2);
        if (rc != 0) {
            fatal_error("ERROR: JNI: Cannot register natives\n");
        }
        /* Engage calls. */
        field = env->GetStaticFieldID(klass, "engaged", "I");
        if (field == NULL) {
            fatal_error("ERROR: JNI: Cannot get field\n");
        }
        env->SetStaticIntField(klass, field, 1);

        gdata->vm_is_started = JNI_TRUE;

    }
    exit_critical_section(jvmti);
}

/*
 * Callback that is notified when our agent is loaded. Registers for event
 * notifications.
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
    static GlobalAgentData data;
    jvmtiError error;
    jint res;
    jvmtiEventCallbacks callbacks;
    jvmtiEnv *jvmti = NULL;
    jvmtiCapabilities capa;

    (void) memset((void*) &data, 0, sizeof(data));
    gdata = &data;
//save jvmti for later
    gdata->jvm = jvm;
    res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0);
    gdata->jvmti = jvmti;

    if (res != JNI_OK || jvmti == NULL) {
        /* This means that the VM was unable to obtain this version of the
         *   JVMTI interface, this is a fatal error.
         */
        printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
                " is your J2SE a 1.5 or newer version?"
                " JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, res);

    }

//Register our capabilities
    (void) memset(&capa, 0, sizeof(jvmtiCapabilities));
    capa.can_tag_objects = 1;
    capa.can_generate_object_free_events = 1;

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

//Register callbacks
    (void) memset(&callbacks, 0, sizeof(callbacks));
    callbacks.VMDeath = &callbackVMDeath;
    callbacks.VMStart = &cbVMStart;
    callbacks.ObjectFree = &cbObjectFree;

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

//Register for events
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START,
            (jthread) NULL);
    check_jvmti_error(jvmti, error, "Cannot set event notification");
    check_jvmti_error(jvmti, error, "Cannot set event notification");
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT,
            (jthread) NULL);
    check_jvmti_error(jvmti, error, "Cannot set event notification");
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH,
            (jthread) NULL);
    check_jvmti_error(jvmti, error, "Cannot set event notification");
    error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
            JVMTI_EVENT_OBJECT_FREE, (jthread) NULL);
    check_jvmti_error(jvmti, error, "Cannot set event notification");

//Set up a few locks
    error = jvmti->CreateRawMonitor("agent data", &(gdata->lock));
    check_jvmti_error(jvmti, error, "Cannot create raw monitor");

    return JNI_OK;
}

下面是对应的Makefile,在Linux上安装了GCC 和GCC-C++(g++) 后就可以编译成对应的so文件了.

UNAME := $(shell uname)
CCFLAGS = -o target/walker.o -I${JAVA_HOME}/include -c -fPIC -fpermissive
ifeq ($(UNAME), Linux)
    CCFLAGS += -I${JAVA_HOME}/include/linux 
    LINKFLAGS = -z defs -static-libgcc -shared -o target/walker.so -lc 
endif
ifeq ($(UNAME), Darwin)
    CCFLAGS += -I${JAVA_HOME}/include/darwin
    LINKFLAGS += -dynamiclib -o target/walker.so
endif

build:
        rm -rf target/walker.o target/walker.so
        gcc ${CCFLAGS} nativeC++/walker.cpp
        g++ ${LINKFLAGS} target/walker.o
clean:
        rm -rf target/walker.o  target/walker.so

 下面是对应的Java代码,里面的_craw1()和_getRoots方法由上面的Navtie方法提供了实现。

package org.vatuse.monitor.heap.runtime;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;

import sun.misc.Launcher;

public class HeapWalker {
    /**
     * Flag set by the JVMTI agent to indicate that it was successfully loaded
     */
    public static int engaged = 0;

    private static native Field[] _getRoots(Object obj);

    private static native void _crawl();

    /**
     * Get the static field roots that point to obj.
     * The data is accurate as of the last time you call the "crawl" function, which captures the pointers.
     * @param obj
     * @return
     */
    public static Field[] getRoots(Object obj) {
        if (engaged == 0)
            throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded");
        if (obj == null)
            return null;
        return filter(_getRoots(obj));
    }

    /**
     * Crawl the heap and generate a reverse points-to graph, which we need to suppor the "getRoots" functionality.
     */
    public static void crawl() {
        if (engaged == 0)
            throw new IllegalStateException("Attempting to use JVMTI features, but native agent not loaded");
        _crawl();
    }

    /**
     * Filter some internal static fields out of the returned list
     * @param in
     * @return
     */
    private static Field[] filter(Field[] in)
    {
        ArrayList<Field> ret = new ArrayList<Field>();
        for(Field sf : in)
        {
            if(sf.getDeclaringClass() == ClassLoader.class || sf.getDeclaringClass() == Launcher.class || sf.getDeclaringClass() == Proxy.class)
                continue;
            ret.add(sf);
        }
        return ret.toArray(new Field[ret.size()]);
    }

}

 

把这个单独的class打成一个Jar,这个JAR需要以bootclass的身份启动,因为JVMTI 对应的agent是在启动JVM的时候加载的。

启动的时候加上下面的VM 参数.

-Xbootclasspath/p:/data/sfsf/workspace/trunk/bytecode-examples-master/heapWalking/target/heapWalking.jar
-agentpath:/data/sfsf/workspace/trunk/bytecode-examples-master/heapWalking/target/libwalkingExample.so

 

下面是一个测试代码。

package org.vatuse.monitor.test.heap.runtime;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Field;
import java.util.Arrays;

import org.junit.Test;
import org.vatuse.monitor.heap.runtime.HeapWalker;

public class WalkingITCase {
    static class Foo {
        Object[] FOOFIELD;
    }

    static Object bar;
    static Object baz;

    @Test
    public void testCleanupBetweenCrawls() throws Exception {
        Object foo = "foo";
        baz = foo;
        for (int i = 0; i < 10; i++)
            HeapWalker.crawl();

        Field[] ret = HeapWalker.getRoots(foo);
        System.out.println(Arrays.toString(ret));

        assertNotNull(ret);
        assertEquals(1, ret.length);
        assertEquals("org.vatuse.monitor.test.heap.runtime.WalkingITCase", ret[0].getDeclaringClass().getName());
        assertEquals("baz", ret[0].getName());
        bar = null;
        baz = null;
    }

    @Test
    public void testDirectlyReachable() throws Exception {
        Object foo = "foo";
        baz = foo;
        HeapWalker.crawl();
        Field[] ret = HeapWalker.getRoots(foo);
        System.out.println(Arrays.toString(ret));

        assertNotNull(ret);
        assertEquals(1, ret.length);
        assertEquals("org.vatuse.monitor.test.heap.runtime.WalkingITCase", ret[0].getDeclaringClass().getName());
        assertEquals("baz", ret[0].getName());
        bar = null;
        baz = null;
    }

    @Test
    public void testIndiriectlyReachable() throws Exception {
        Object foo = "foo";
        baz = new Foo();
        ((Foo) baz).FOOFIELD = new Object[100];
        ((Foo) baz).FOOFIELD[0] = foo;
        HeapWalker.crawl();
        Field[] ret = HeapWalker.getRoots(foo);
        System.out.println(Arrays.toString(ret));

        assertNotNull(ret);
        assertEquals(1, ret.length);
        assertEquals("org.vatuse.monitor.test.heap.runtime.WalkingITCase", ret[0].getDeclaringClass().getName());
        assertEquals("baz", ret[0].getName());
        bar = null;
        baz = null;
    }

    @Test
    public void testMultipleReachable() throws Exception {
        Object foo = "foo";
        baz = new Foo();
        ((Foo) baz).FOOFIELD = new Object[100];
        ((Foo) baz).FOOFIELD[0] = foo;
        bar = baz;
        HeapWalker.crawl();
        Field[] ret = HeapWalker.getRoots(foo);
        System.out.println(Arrays.toString(ret));

        assertNotNull(ret);
        assertEquals(2, ret.length);
        assertEquals("org.vatuse.monitor.test.heap.runtime.WalkingITCase", ret[0].getDeclaringClass().getName());
        assertTrue(("baz".equals(ret[0].getName()) && "bar".equals(ret[1].getName())) || ("bar".equals(ret[0].getName()) && "baz".equals(ret[1].getName())));
        bar = null;
        baz = null;
    }
}

bootclasspath的三种情况.

-Xbootclasspath:bootclasspath   让jvm从指定的路径中加载bootclass,用来替换jdk的rt.jar.

-Xbootclasspath/a:path              被指定的文件追加到默认的bootstrap路径中。

-Xbootclasspath/p:path             让jvm优先于默认的bootstrap去加载path中指定的class

posted @ 2016-03-09 18:50  princessd8251  阅读(930)  评论(0)    收藏  举报