blob: 3af21846245b65877d0388885f7c35dc4f3a06b1 [file] [log] [blame]
/*
* Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
#include "stdlib.h"
#include "heapTracker.h"
#include "java_crw_demo.h"
#include "jni.h"
#include "jvmti.h"
#include "agent_util.h"
/* -------------------------------------------------------------------
* Some constant names that tie to Java class/method names.
* We assume the Java class whose static methods we will be calling
* looks like:
*
* public class HeapTracker {
* private static int engaged;
* private static native void _newobj(Object thr, Object o);
* public static void newobj(Object o)
* {
* if ( engaged != 0 ) {
* _newobj(Thread.currentThread(), o);
* }
* }
* private static native void _newarr(Object thr, Object a);
* public static void newarr(Object a)
* {
* if ( engaged != 0 ) {
* _newarr(Thread.currentThread(), a);
* }
* }
* }
*
* The engaged field allows us to inject all classes (even system classes)
* and delay the actual calls to the native code until the VM has reached
* a safe time to call native methods (Past the JVMTI VM_START event).
*
*/
#define HEAP_TRACKER_class HeapTracker /* Name of class we are using */
#define HEAP_TRACKER_newobj newobj /* Name of java init method */
#define HEAP_TRACKER_newarr newarr /* Name of java newarray method */
#define HEAP_TRACKER_native_newobj _newobj /* Name of java newobj native */
#define HEAP_TRACKER_native_newarr _newarr /* Name of java newarray native */
#define HEAP_TRACKER_engaged engaged /* Name of static field switch */
/* C macros to create strings from tokens */
#define _STRING(s) #s
#define STRING(s) _STRING(s)
/* ------------------------------------------------------------------- */
/* Flavors of traces (to separate out stack traces) */
typedef enum {
TRACE_FIRST = 0,
TRACE_USER = 0,
TRACE_BEFORE_VM_START = 1,
TRACE_BEFORE_VM_INIT = 2,
TRACE_VM_OBJECT = 3,
TRACE_MYSTERY = 4,
TRACE_LAST = 4
} TraceFlavor;
static char * flavorDesc[] = {
"",
"before VM_START",
"before VM_INIT",
"VM_OBJECT",
"unknown"
};
/* Trace (Stack Trace) */
#define MAX_FRAMES 6
typedef struct Trace {
/* Number of frames (includes HEAP_TRACKER methods) */
jint nframes;
/* Frames from GetStackTrace() (2 extra for HEAP_TRACKER methods) */
jvmtiFrameInfo frames[MAX_FRAMES+2];
/* Used to make some traces unique */
TraceFlavor flavor;
} Trace;
/* Trace information (more than one object will have this as a tag) */
typedef struct TraceInfo {
/* Trace where this object was allocated from */
Trace trace;
/* 64 bit hash code that attempts to identify this specific trace */
jlong hashCode;
/* Total space taken up by objects allocated from this trace */
jlong totalSpace;
/* Total count of objects ever allocated from this trace */
int totalCount;
/* Total live objects that were allocated from this trace */
int useCount;
/* The next TraceInfo in the hash bucket chain */
struct TraceInfo *next;
} TraceInfo;
/* Global agent data structure */
typedef struct {
/* JVMTI Environment */
jvmtiEnv *jvmti;
/* State of the VM flags */
jboolean vmStarted;
jboolean vmInitialized;
jboolean vmDead;
/* Options */
int maxDump;
/* Data access Lock */
jrawMonitorID lock;
/* Counter on classes where BCI has been applied */
jint ccount;
/* Hash table to lookup TraceInfo's via Trace's */
#define HASH_INDEX_BIT_WIDTH 12 /* 4096 */
#define HASH_BUCKET_COUNT (1<<HASH_INDEX_BIT_WIDTH)
#define HASH_INDEX_MASK (HASH_BUCKET_COUNT-1)
TraceInfo *hashBuckets[HASH_BUCKET_COUNT];
/* Count of TraceInfo's allocated */
int traceInfoCount;
/* Pre-defined traces for the system and mystery situations */
TraceInfo *emptyTrace[TRACE_LAST+1];
} GlobalAgentData;
static GlobalAgentData *gdata;
/* Enter a critical section by doing a JVMTI Raw Monitor Enter */
static void
enterCriticalSection(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
exitCriticalSection(jvmtiEnv *jvmti)
{
jvmtiError error;
error = (*jvmti)->RawMonitorExit(jvmti, gdata->lock);
check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
}
/* Update stats on a TraceInfo */
static TraceInfo *
updateStats(TraceInfo *tinfo)
{
tinfo->totalCount++;
tinfo->useCount++;
return tinfo;
}
/* Get TraceInfo for empty stack */
static TraceInfo *
emptyTrace(TraceFlavor flavor)
{
return updateStats(gdata->emptyTrace[flavor]);
}
/* Allocate new TraceInfo */
static TraceInfo *
newTraceInfo(Trace *trace, jlong hashCode, TraceFlavor flavor)
{
TraceInfo *tinfo;
tinfo = (TraceInfo*)calloc(1, sizeof(TraceInfo));
if ( tinfo == NULL ) {
fatal_error("ERROR: Ran out of malloc() space\n");
} else {
int hashIndex;
tinfo->trace = *trace;
tinfo->trace.flavor = flavor;
tinfo->hashCode = hashCode;
gdata->traceInfoCount++;
hashIndex = (int)(hashCode & HASH_INDEX_MASK);
tinfo->next = gdata->hashBuckets[hashIndex];
gdata->hashBuckets[hashIndex] = tinfo;
}
return tinfo;
}
/* Create hash code for a Trace */
static jlong
hashTrace(Trace *trace)
{
jlong hashCode;
int i;
hashCode = 0;
for ( i = 0 ; i < trace->nframes ; i++ ) {
hashCode = (hashCode << 3) +
(jlong)(ptrdiff_t)(void*)(trace->frames[i].method);
hashCode = (hashCode << 2) +
(jlong)(trace->frames[i].location);
}
hashCode = (hashCode << 3) + trace->nframes;
hashCode += trace->flavor;
return hashCode;
}
/* Lookup or create a new TraceInfo */
static TraceInfo *
lookupOrEnter(jvmtiEnv *jvmti, Trace *trace, TraceFlavor flavor)
{
TraceInfo *tinfo;
jlong hashCode;
/* Calculate hash code (outside critical section to lessen contention) */
hashCode = hashTrace(trace);
/* Do a lookup in the hash table */
enterCriticalSection(jvmti); {
TraceInfo *prev;
int hashIndex;
/* Start with first item in hash buck chain */
prev = NULL;
hashIndex = (int)(hashCode & HASH_INDEX_MASK);
tinfo = gdata->hashBuckets[hashIndex];
while ( tinfo != NULL ) {
if ( tinfo->hashCode == hashCode &&
memcmp(trace, &(tinfo->trace), sizeof(Trace))==0 ) {
/* We found one that matches, move to head of bucket chain */
if ( prev != NULL ) {
/* Remove from list and add to head of list */
prev->next = tinfo->next;
tinfo->next = gdata->hashBuckets[hashIndex];
gdata->hashBuckets[hashIndex] = tinfo;
}
/* Break out */
break;
}
prev = tinfo;
tinfo = tinfo->next;
}
/* If we didn't find anything we need to enter a new entry */
if ( tinfo == NULL ) {
/* Create new hash table element */
tinfo = newTraceInfo(trace, hashCode, flavor);
}
/* Update stats */
(void)updateStats(tinfo);
} exitCriticalSection(jvmti);
return tinfo;
}
/* Get TraceInfo for this allocation */
static TraceInfo *
findTraceInfo(jvmtiEnv *jvmti, jthread thread, TraceFlavor flavor)
{
TraceInfo *tinfo;
jvmtiError error;
tinfo = NULL;
if ( thread != NULL ) {
static Trace empty;
Trace trace;
/* Before VM_INIT thread could be NULL, watch out */
trace = empty;
error = (*jvmti)->GetStackTrace(jvmti, thread, 0, MAX_FRAMES+2,
trace.frames, &(trace.nframes));
/* If we get a PHASE error, the VM isn't ready, or it died */
if ( error == JVMTI_ERROR_WRONG_PHASE ) {
/* It is assumed this is before VM_INIT */
if ( flavor == TRACE_USER ) {
tinfo = emptyTrace(TRACE_BEFORE_VM_INIT);
} else {
tinfo = emptyTrace(flavor);
}
} else {
check_jvmti_error(jvmti, error, "Cannot get stack trace");
/* Lookup this entry */
tinfo = lookupOrEnter(jvmti, &trace, flavor);
}
} else {
/* If thread==NULL, it's assumed this is before VM_START */
if ( flavor == TRACE_USER ) {
tinfo = emptyTrace(TRACE_BEFORE_VM_START);
} else {
tinfo = emptyTrace(flavor);
}
}
return tinfo;
}
/* Tag an object with a TraceInfo pointer. */
static void
tagObjectWithTraceInfo(jvmtiEnv *jvmti, jobject object, TraceInfo *tinfo)
{
jvmtiError error;
jlong tag;
/* Tag this object with this TraceInfo pointer */
tag = (jlong)(ptrdiff_t)(void*)tinfo;
error = (*jvmti)->SetTag(jvmti, object, tag);
check_jvmti_error(jvmti, error, "Cannot tag object");
}
/* Java Native Method for Object.<init> */
static void JNICALL
HEAP_TRACKER_native_newobj(JNIEnv *env, jclass klass, jthread thread, jobject o)
{
TraceInfo *tinfo;
if ( gdata->vmDead ) {
return;
}
tinfo = findTraceInfo(gdata->jvmti, thread, TRACE_USER);
tagObjectWithTraceInfo(gdata->jvmti, o, tinfo);
}
/* Java Native Method for newarray */
static void JNICALL
HEAP_TRACKER_native_newarr(JNIEnv *env, jclass klass, jthread thread, jobject a)
{
TraceInfo *tinfo;
if ( gdata->vmDead ) {
return;
}
tinfo = findTraceInfo(gdata->jvmti, thread, TRACE_USER);
tagObjectWithTraceInfo(gdata->jvmti, a, tinfo);
}
/* Callback for JVMTI_EVENT_VM_START */
static void JNICALL
cbVMStart(jvmtiEnv *jvmti, JNIEnv *env)
{
enterCriticalSection(jvmti); {
jclass klass;
jfieldID field;
jint rc;
/* Java Native Methods for class */
static JNINativeMethod registry[2] = {
{STRING(HEAP_TRACKER_native_newobj), "(Ljava/lang/Object;Ljava/lang/Object;)V",
(void*)&HEAP_TRACKER_native_newobj},
{STRING(HEAP_TRACKER_native_newarr), "(Ljava/lang/Object;Ljava/lang/Object;)V",
(void*)&HEAP_TRACKER_native_newarr}
};
/* Register Natives for class whose methods we use */
klass = (*env)->FindClass(env, STRING(HEAP_TRACKER_class));
if ( klass == NULL ) {
fatal_error("ERROR: JNI: Cannot find %s with FindClass\n",
STRING(HEAP_TRACKER_class));
}
rc = (*env)->RegisterNatives(env, klass, registry, 2);
if ( rc != 0 ) {
fatal_error("ERROR: JNI: Cannot register natives for class %s\n",
STRING(HEAP_TRACKER_class));
}
/* Engage calls. */
field = (*env)->GetStaticFieldID(env, klass, STRING(HEAP_TRACKER_engaged), "I");
if ( field == NULL ) {
fatal_error("ERROR: JNI: Cannot get field from %s\n",
STRING(HEAP_TRACKER_class));
}
(*env)->SetStaticIntField(env, klass, field, 1);
/* Indicate VM has started */
gdata->vmStarted = JNI_TRUE;
} exitCriticalSection(jvmti);
}
/* Iterate Through Heap callback */
static jint JNICALL
cbObjectTagger(jlong class_tag, jlong size, jlong* tag_ptr, jint length,
void *user_data)
{
TraceInfo *tinfo;
tinfo = emptyTrace(TRACE_BEFORE_VM_INIT);
*tag_ptr = (jlong)(ptrdiff_t)(void*)tinfo;
return JVMTI_VISIT_OBJECTS;
}
/* Callback for JVMTI_EVENT_VM_INIT */
static void JNICALL
cbVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread)
{
jvmtiHeapCallbacks heapCallbacks;
jvmtiError error;
/* Iterate through heap, find all untagged objects allocated before this */
(void)memset(&heapCallbacks, 0, sizeof(heapCallbacks));
heapCallbacks.heap_iteration_callback = &cbObjectTagger;
error = (*jvmti)->IterateThroughHeap(jvmti, JVMTI_HEAP_FILTER_TAGGED,
NULL, &heapCallbacks, NULL);
check_jvmti_error(jvmti, error, "Cannot iterate through heap");
enterCriticalSection(jvmti); {
/* Indicate VM is initialized */
gdata->vmInitialized = JNI_TRUE;
} exitCriticalSection(jvmti);
}
/* Iterate Through Heap callback */
static jint JNICALL
cbObjectSpaceCounter(jlong class_tag, jlong size, jlong* tag_ptr, jint length,
void *user_data)
{
TraceInfo *tinfo;
tinfo = (TraceInfo*)(ptrdiff_t)(*tag_ptr);
if ( tinfo == NULL ) {
tinfo = emptyTrace(TRACE_MYSTERY);
*tag_ptr = (jlong)(ptrdiff_t)(void*)tinfo;
}
tinfo->totalSpace += size;
return JVMTI_VISIT_OBJECTS;
}
/* Qsort compare function */
static int
compareInfo(const void *p1, const void *p2)
{
TraceInfo *tinfo1, *tinfo2;
tinfo1 = *((TraceInfo**)p1);
tinfo2 = *((TraceInfo**)p2);
return (int)(tinfo2->totalSpace - tinfo1->totalSpace);
}
/* Frame to text */
static void
frameToString(jvmtiEnv *jvmti, char *buf, int buflen, jvmtiFrameInfo *finfo)
{
jvmtiError error;
jclass klass;
char *signature;
char *methodname;
char *methodsig;
jboolean isNative;
char *filename;
int lineCount;
jvmtiLineNumberEntry*lineTable;
int lineNumber;
/* Initialize defaults */
buf[0] = 0;
klass = NULL;
signature = NULL;
methodname = NULL;
methodsig = NULL;
isNative = JNI_FALSE;
filename = NULL;
lineCount = 0;
lineTable = NULL;
lineNumber = 0;
/* Get jclass object for the jmethodID */
error = (*jvmti)->GetMethodDeclaringClass(jvmti, finfo->method, &klass);
check_jvmti_error(jvmti, error, "Cannot get method's class");
/* Get the class signature */
error = (*jvmti)->GetClassSignature(jvmti, klass, &signature, NULL);
check_jvmti_error(jvmti, error, "Cannot get class signature");
/* Skip all this if it's our own Tracker method */
if ( strcmp(signature, "L" STRING(HEAP_TRACKER_class) ";" ) == 0 ) {
deallocate(jvmti, signature);
return;
}
/* Get the name and signature for the method */
error = (*jvmti)->GetMethodName(jvmti, finfo->method,
&methodname, &methodsig, NULL);
check_jvmti_error(jvmti, error, "Cannot method name");
/* Check to see if it's a native method, which means no lineNumber */
error = (*jvmti)->IsMethodNative(jvmti, finfo->method, &isNative);
check_jvmti_error(jvmti, error, "Cannot get method native status");
/* Get source file name */
error = (*jvmti)->GetSourceFileName(jvmti, klass, &filename);
if ( error != JVMTI_ERROR_NONE && error != JVMTI_ERROR_ABSENT_INFORMATION ) {
check_jvmti_error(jvmti, error, "Cannot get source filename");
}
/* Get lineNumber if we can */
if ( !isNative ) {
int i;
/* Get method line table */
error = (*jvmti)->GetLineNumberTable(jvmti, finfo->method, &lineCount, &lineTable);
if ( error == JVMTI_ERROR_NONE ) {
/* Search for line */
lineNumber = lineTable[0].line_number;
for ( i = 1 ; i < lineCount ; i++ ) {
if ( finfo->location < lineTable[i].start_location ) {
break;
}
lineNumber = lineTable[i].line_number;
}
} else if ( error != JVMTI_ERROR_ABSENT_INFORMATION ) {
check_jvmti_error(jvmti, error, "Cannot get method line table");
}
}
/* Create string for this frame location.
* NOTE: These char* quantities are mUTF (Modified UTF-8) bytes
* and should actually be converted to the default system
* character encoding. Sending them to things like
* printf() without converting them is actually an I18n
* (Internationalization) error.
*/
(void)sprintf(buf, "%s.%s@%d[%s:%d]",
(signature==NULL?"UnknownClass":signature),
(methodname==NULL?"UnknownMethod":methodname),
(int)finfo->location,
(filename==NULL?"UnknownFile":filename),
lineNumber);
/* Free up JVMTI space allocated by the above calls */
deallocate(jvmti, signature);
deallocate(jvmti, methodname);
deallocate(jvmti, methodsig);
deallocate(jvmti, filename);
deallocate(jvmti, lineTable);
}
/* Print the information */
static void
printTraceInfo(jvmtiEnv *jvmti, int index, TraceInfo* tinfo)
{
if ( tinfo == NULL ) {
fatal_error("%d: NULL ENTRY ERROR\n", index);
return;
}
stdout_message("%2d: %7d bytes %5d objects %5d live %s",
index, (int)tinfo->totalSpace, tinfo->totalCount,
tinfo->useCount, flavorDesc[tinfo->trace.flavor]);
if ( tinfo->trace.nframes > 0 ) {
int i;
int fcount;
fcount = 0;
stdout_message(" stack=(");
for ( i = 0 ; i < tinfo->trace.nframes ; i++ ) {
char buf[4096];
frameToString(jvmti, buf, (int)sizeof(buf), tinfo->trace.frames+i);
if ( buf[0] == 0 ) {
continue; /* Skip the ones that are from Tracker class */
}
fcount++;
stdout_message("%s", buf);
if ( i < (tinfo->trace.nframes-1) ) {
stdout_message(",");
}
}
stdout_message(") nframes=%d\n", fcount);
} else {
stdout_message(" stack=<empty>\n");
}
}
/* Callback for JVMTI_EVENT_VM_DEATH */
static void JNICALL
cbVMDeath(jvmtiEnv *jvmti, JNIEnv *env)
{
jvmtiHeapCallbacks heapCallbacks;
jvmtiError error;
/* These are purposely done outside the critical section */
/* Force garbage collection now so we get our ObjectFree calls */
error = (*jvmti)->ForceGarbageCollection(jvmti);
check_jvmti_error(jvmti, error, "Cannot force garbage collection");
/* Iterate through heap and find all objects */
(void)memset(&heapCallbacks, 0, sizeof(heapCallbacks));
heapCallbacks.heap_iteration_callback = &cbObjectSpaceCounter;
error = (*jvmti)->IterateThroughHeap(jvmti, 0, NULL, &heapCallbacks, NULL);
check_jvmti_error(jvmti, error, "Cannot iterate through heap");
/* Process VM Death */
enterCriticalSection(jvmti); {
jclass klass;
jfieldID field;
jvmtiEventCallbacks callbacks;
/* Disengage calls in HEAP_TRACKER_class. */
klass = (*env)->FindClass(env, STRING(HEAP_TRACKER_class));
if ( klass == NULL ) {
fatal_error("ERROR: JNI: Cannot find %s with FindClass\n",
STRING(HEAP_TRACKER_class));
}
field = (*env)->GetStaticFieldID(env, klass, STRING(HEAP_TRACKER_engaged), "I");
if ( field == NULL ) {
fatal_error("ERROR: JNI: Cannot get field from %s\n",
STRING(HEAP_TRACKER_class));
}
(*env)->SetStaticIntField(env, klass, field, 0);
/* The critical section here is important to hold back the VM death
* until all other callbacks have completed.
*/
/* Clear out all callbacks. */
(void)memset(&callbacks,0, sizeof(callbacks));
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks,
(jint)sizeof(callbacks));
check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");
/* Since this critical section could be holding up other threads
* in other event callbacks, we need to indicate that the VM is
* dead so that the other callbacks can short circuit their work.
* We don't expect an further events after VmDeath but we do need
* to be careful that existing threads might be in our own agent
* callback code.
*/
gdata->vmDead = JNI_TRUE;
/* Dump all objects */
if ( gdata->traceInfoCount > 0 ) {
TraceInfo **list;
int count;
int i;
stdout_message("Dumping heap trace information\n");
/* Create single array of pointers to TraceInfo's, sort, and
* print top gdata->maxDump top space users.
*/
list = (TraceInfo**)calloc(gdata->traceInfoCount,
sizeof(TraceInfo*));
if ( list == NULL ) {
fatal_error("ERROR: Ran out of malloc() space\n");
}
count = 0;
for ( i = 0 ; i < HASH_BUCKET_COUNT ; i++ ) {
TraceInfo *tinfo;
tinfo = gdata->hashBuckets[i];
while ( tinfo != NULL ) {
if ( count < gdata->traceInfoCount ) {
list[count++] = tinfo;
}
tinfo = tinfo->next;
}
}
if ( count != gdata->traceInfoCount ) {
fatal_error("ERROR: Count found by iterate doesn't match ours:"
" count=%d != traceInfoCount==%d\n",
count, gdata->traceInfoCount);
}
qsort(list, count, sizeof(TraceInfo*), &compareInfo);
for ( i = 0 ; i < count ; i++ ) {
if ( i >= gdata->maxDump ) {
break;
}
printTraceInfo(jvmti, i+1, list[i]);
}
(void)free(list);
}
} exitCriticalSection(jvmti);
}
/* Callback for JVMTI_EVENT_VM_OBJECT_ALLOC */
static void JNICALL
cbVMObjectAlloc(jvmtiEnv *jvmti, JNIEnv *env, jthread thread,
jobject object, jclass object_klass, jlong size)
{
TraceInfo *tinfo;
if ( gdata->vmDead ) {
return;
}
tinfo = findTraceInfo(jvmti, thread, TRACE_VM_OBJECT);
tagObjectWithTraceInfo(jvmti, object, tinfo);
}
/* Callback for JVMTI_EVENT_OBJECT_FREE */
static void JNICALL
cbObjectFree(jvmtiEnv *jvmti, jlong tag)
{
TraceInfo *tinfo;
if ( gdata->vmDead ) {
return;
}
/* The object tag is actually a pointer to a TraceInfo structure */
tinfo = (TraceInfo*)(void*)(ptrdiff_t)tag;
/* Decrement the use count */
tinfo->useCount--;
}
/* Callback for JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */
static void JNICALL
cbClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* env,
jclass class_being_redefined, jobject loader,
const char* name, jobject protection_domain,
jint class_data_len, const unsigned char* class_data,
jint* new_class_data_len, unsigned char** new_class_data)
{
enterCriticalSection(jvmti); {
/* It's possible we get here right after VmDeath event, be careful */
if ( !gdata->vmDead ) {
const char * classname;
/* Name can be NULL, make sure we avoid SEGV's */
if ( name == NULL ) {
classname = java_crw_demo_classname(class_data, class_data_len,
NULL);
if ( classname == NULL ) {
fatal_error("ERROR: No classname in classfile\n");
}
} else {
classname = strdup(name);
if ( classname == NULL ) {
fatal_error("ERROR: Ran out of malloc() space\n");
}
}
*new_class_data_len = 0;
*new_class_data = NULL;
/* The tracker class itself? */
if ( strcmp(classname, STRING(HEAP_TRACKER_class)) != 0 ) {
jint cnum;
int systemClass;
unsigned char *newImage;
long newLength;
/* Get number for every class file image loaded */
cnum = gdata->ccount++;
/* Is it a system class? If the class load is before VmStart
* then we will consider it a system class that should
* be treated carefully. (See java_crw_demo)
*/
systemClass = 0;
if ( !gdata->vmStarted ) {
systemClass = 1;
}
newImage = NULL;
newLength = 0;
/* Call the class file reader/write demo code */
java_crw_demo(cnum,
classname,
class_data,
class_data_len,
systemClass,
STRING(HEAP_TRACKER_class),
"L" STRING(HEAP_TRACKER_class) ";",
NULL, NULL,
NULL, NULL,
STRING(HEAP_TRACKER_newobj), "(Ljava/lang/Object;)V",
STRING(HEAP_TRACKER_newarr), "(Ljava/lang/Object;)V",
&newImage,
&newLength,
NULL,
NULL);
/* If we got back a new class image, return it back as "the"
* new class image. This must be JVMTI Allocate space.
*/
if ( newLength > 0 ) {
unsigned char *jvmti_space;
jvmti_space = (unsigned char *)allocate(jvmti, (jint)newLength);
(void)memcpy((void*)jvmti_space, (void*)newImage, (int)newLength);
*new_class_data_len = (jint)newLength;
*new_class_data = jvmti_space; /* VM will deallocate */
}
/* Always free up the space we get from java_crw_demo() */
if ( newImage != NULL ) {
(void)free((void*)newImage); /* Free malloc() space with free() */
}
}
(void)free((void*)classname);
}
} exitCriticalSection(jvmti);
}
/* Parse the options for this heapTracker agent */
static void
parse_agent_options(char *options)
{
#define MAX_TOKEN_LENGTH 16
char token[MAX_TOKEN_LENGTH];
char *next;
/* Defaults */
gdata->maxDump = 20;
/* Parse options and set flags in gdata */
if ( options==NULL ) {
return;
}
/* Get the first token from the options string. */
next = get_token(options, ",=", token, (int)sizeof(token));
/* While not at the end of the options string, process this option. */
while ( next != NULL ) {
if ( strcmp(token,"help")==0 ) {
stdout_message("The heapTracker JVMTI demo agent\n");
stdout_message("\n");
stdout_message(" java -agent:heapTracker[=options] ...\n");
stdout_message("\n");
stdout_message("The options are comma separated:\n");
stdout_message("\t help\t\t\t Print help information\n");
stdout_message("\t maxDump=n\t\t\t How many TraceInfo's to dump\n");
stdout_message("\n");
exit(0);
} else if ( strcmp(token,"maxDump")==0 ) {
char number[MAX_TOKEN_LENGTH];
next = get_token(next, ",=", number, (int)sizeof(number));
if ( next == NULL ) {
fatal_error("ERROR: Cannot parse maxDump=number: %s\n", options);
}
gdata->maxDump = atoi(number);
} else if ( token[0]!=0 ) {
/* We got a non-empty token and we don't know what it is. */
fatal_error("ERROR: Unknown option: %s\n", token);
}
/* Get the next token (returns NULL if there are no more) */
next = get_token(next, ",=", token, (int)sizeof(token));
}
}
/* Agent_OnLoad: This is called immediately after the shared library is
* loaded. This is the first code executed.
*/
JNIEXPORT jint JNICALL
DEF_Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
static GlobalAgentData data;
jvmtiEnv *jvmti;
jvmtiError error;
jint res;
TraceFlavor flavor;
jvmtiCapabilities capabilities;
jvmtiEventCallbacks callbacks;
static Trace empty;
/* 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.
*/
(void)memset((void*)&data, 0, sizeof(data));
gdata = &data;
/* First thing we need to do is get the jvmtiEnv* or JVMTI environment */
res = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1);
if (res != JNI_OK) {
/* This means that the VM was unable to obtain this version of the
* JVMTI interface, this is a fatal error.
*/
fatal_error("ERROR: Unable to access JVMTI Version 1 (0x%x),"
" is your JDK a 5.0 or newer version?"
" JNIEnv's GetEnv() returned %d\n",
JVMTI_VERSION_1, res);
}
/* Here we save the jvmtiEnv* for Agent_OnUnload(). */
gdata->jvmti = jvmti;
/* Parse any options supplied on java command line */
parse_agent_options(options);
/* Immediately after getting the jvmtiEnv* we need to ask for the
* capabilities this agent will need.
*/
(void)memset(&capabilities,0, sizeof(capabilities));
capabilities.can_generate_all_class_hook_events = 1;
capabilities.can_tag_objects = 1;
capabilities.can_generate_object_free_events = 1;
capabilities.can_get_source_file_name = 1;
capabilities.can_get_line_numbers = 1;
capabilities.can_generate_vm_object_alloc_events = 1;
error = (*jvmti)->AddCapabilities(jvmti, &capabilities);
check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities.");
/* Next we need to provide the pointers to the callback functions to
* to this jvmtiEnv*
*/
(void)memset(&callbacks,0, sizeof(callbacks));
/* JVMTI_EVENT_VM_START */
callbacks.VMStart = &cbVMStart;
/* JVMTI_EVENT_VM_INIT */
callbacks.VMInit = &cbVMInit;
/* JVMTI_EVENT_VM_DEATH */
callbacks.VMDeath = &cbVMDeath;
/* JVMTI_EVENT_OBJECT_FREE */
callbacks.ObjectFree = &cbObjectFree;
/* JVMTI_EVENT_VM_OBJECT_ALLOC */
callbacks.VMObjectAlloc = &cbVMObjectAlloc;
/* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */
callbacks.ClassFileLoadHook = &cbClassFileLoadHook;
error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");
/* At first the only initial events we are interested in are VM
* initialization, VM death, and Class File Loads.
* Once the VM is initialized we will request more events.
*/
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_START, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_INIT, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_DEATH, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_OBJECT_FREE, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_VM_OBJECT_ALLOC, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
/* 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));
check_jvmti_error(jvmti, error, "Cannot create raw monitor");
/* Create the TraceInfo for various flavors of empty traces */
for ( flavor = TRACE_FIRST ; flavor <= TRACE_LAST ; flavor++ ) {
gdata->emptyTrace[flavor] =
newTraceInfo(&empty, hashTrace(&empty), flavor);
}
/* Add jar file to boot classpath */
add_demo_jar_to_bootclasspath(jvmti, "heapTracker");
/* We return JNI_OK to signify success */
return JNI_OK;
}
/* Agent_OnUnload: This is called immediately before the shared library is
* unloaded. This is the last code executed.
*/
JNIEXPORT void JNICALL
DEF_Agent_OnUnload(JavaVM *vm)
{
/* Skip any cleanup, VM is about to die anyway */
}