What these software do, in general, is to at predefined or some times, stop the JVM momentarily and "probe" it so that the software can capture its performance metrics like API execution times, JVM thread activity, JVM stack traces etc. and the main intention was to allow software developers and system engineers to gain more visibility into the system they were building or supporting.
p.s. the ultimate aim is go home on time :-)
In a span of a few years, an incredible pool of engineers and designers have used the JVMPI (deprecated and replaced by JVMTI) and JVMTI to build better products to help corporations, developers solve their system performance problems.
Having said that, the purpose of this article is to show you how you can build your very first "probe" to run on Windows, Linux and the Solaris Operating Systems. Here's how i plan to do this:
1/ I am going to tell you a bit about the technology i am going to use to do this
2/ Next, i am going to share with you what are the some of the stuff you can do with this technology
3/ Finally, i am going to share with you some basic steps on how you can get it to run on the Windows, Linux and Solaris platforms.
Let's start ...
What's the fundamental technology in building a JVM Agent ?
- The technology that is currently used to built an agent is based on the Java Virtual Machine Tool Interface
and the hyperlink provided would give you an more in-depth understanding. The paragraph below is a high level summary from a technical standpoint.
"JVMTI provides for all the functional capabilities of the previous native interfaces JVMDI and JVMPI. However, it does not have many of the limitations of those older interfaces. Some of the JVMPI capabilities require the use of JVMTI and the technique called Byte Code Insertion (BCI), sometimes referred to as Byte Code Injection or Byte Code Instrumentation. JVMTI allows for all JVM functionality to continue to operate, like JIT or JVM compilation and different GC implementations. Certain JVMTI features are controlled by asking for JVMTI Capabilities, and some of these can cause changes in JVM performance, but most features are available while the JVM is running "full speed". All JVMTI object handles are JNI handles, and JVMTI Event callbacks always include a JNIEnv argument to facilitate JNI usage. Multiple JVMTI agents can operate in a single JVM and all interfaces return an error code to determine success or failure of the request. It is the intention of JVMTI to displace both JVMPI and JVMDI, and to ultimately be the single native tool interface into the JVM."What are some of the stuff possible with this "fundamental technology"?
- The following is taken from the official JDK 1.5 Javadocs
It provides both a way to inspect the state and to control the execution of applications running in the Java virtual machine (JVM). JVM TI supports the full breadth of tools that need access to JVM state, including but not limited to: profiling, debugging, monitoring, thread analysis, and coverage analysis tools.So the next question is, how can i develop one from scratch ? Sure you can.
The process i did is roughly as follows:
- A C/C++ Compiler
- Below is a sample code where it basically does a thread dump which is enough to get you started and to have something meaningful to look at and not a "Hello World"
- Compile the program code accordingly to the platform
- Attach the completed DLL, SO object to the JVM using the -agentlib or -agentpath
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "jni.h"
#include "jvmti.h"
/* Global agent data structure */
typedef struct {
/* JVMTI Environment */
jvmtiEnv *jvmti;
jboolean vm_is_started;
/* Data access Lock */
jrawMonitorID lock;
} GlobalAgentData;
static GlobalAgentData *gdata;
/* Check for NULL pointer error */
#define CHECK_FOR_NULL(ptr) \
checkForNull(ptr, __FILE__, __LINE__)
static void
checkForNull(void *ptr, char *file, int line)
{
if ( ptr == NULL ) {
printf("ERROR: NULL pointer error in %s:%d\n", file, line);
abort();
}
}
/* Deallocate JVMTI memory */
static void
deallocate(jvmtiEnv *jvmti, void *p)
{
jvmtiError err;
err = (*jvmti)->Deallocate(jvmti, (unsigned char *)p);
if ( err != JVMTI_ERROR_NONE ) {
printf("ERROR: JVMTI Deallocate error err=%d\n", err);
abort();
}
}
/* Get name for JVMTI error code */
static char *
getErrorName(jvmtiEnv *jvmti, jvmtiError errnum)
{
jvmtiError err;
char *name;
err = (*jvmti)->GetErrorName(jvmti, errnum, &name);
if ( err != JVMTI_ERROR_NONE ) {
printf("ERROR: JVMTI GetErrorName error err=%d\n", err);
abort();
}
return name;
}
/* Check for JVMTI error */
#define CHECK_JVMTI_ERROR(jvmti, err) \
checkJvmtiError(jvmti, err, __FILE__, __LINE__)
static void
checkJvmtiError(jvmtiEnv *jvmti, jvmtiError err, char *file, int line)
{
if ( err != JVMTI_ERROR_NONE ) {
char *name;
name = getErrorName(jvmti, err);
printf("ERROR: JVMTI error err=%d(%s) in %s:%d\n",
err, name, file, line);
deallocate(jvmti, name);
abort();
}
}
/*
Check the state of the Java Thread
*/
static unsigned char* JNICALL getThreadState(jvmtiEnv* jvmti, jthread thread, jint state) {
jvmtiError err;
err = (*jvmti)->GetThreadState(jvmti, thread, &state);
CHECK_JVMTI_ERROR( jvmti, err );
//
// Return state of thread
//
switch( state & JVMTI_JAVA_LANG_THREAD_STATE_MASK) {
case JVMTI_JAVA_LANG_THREAD_STATE_NEW: return "New";
case JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED: return "Terminated";
case JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE: return "Runnable";
case JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED: return "Blocked";
case JVMTI_JAVA_LANG_THREAD_STATE_WAITING: return "Waiting";
case JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING: return "Timed Waiting";
}
return "UNKNOWN";
}
/*
JVMTI_EVENT_THREAD_START and JVMTI_EVENT_THREAD_END
Attempts to dump info of each live thread
at thread start/end.
*/
static void JNICALL dumpThreadInfo(jvmtiEnv* jvmti) {
jvmtiError err;
jint totalThreadCount =0;
jint threadState = 0;
int i = 0, j = 0;
jthread* threadPtr;
jvmtiThreadInfo threadInfo;
err = (*jvmti)->GetAllThreads(jvmti, &totalThreadCount, &threadPtr);
CHECK_JVMTI_ERROR(jvmti, err);
//
// Examine the "live" threads
//
for(i = 0; i < totalThreadCount; i++) {
jvmtiFrameInfo frames[10]; // Allocate space for 10 stack frames
jint recordCount = 0; // Actual stack records
/* make sure the stack variables are garbage free */
(void)memset(&threadInfo, 0, sizeof(threadInfo));
//
// Get / Display the Thread's "state" information
//
err = (*jvmti)->GetThreadInfo(jvmti, threadPtr[i], &threadInfo);
printf("\n\n\"%s\", prio=%d, daemon=%s, state=%s\n", \
threadInfo.name, threadInfo.priority, (threadInfo.is_daemon == 1? "yes":"no"), getThreadState(jvmti, threadPtr[i], threadState));
//
// Get / Display the Thread's "stack trace" information
//
err = (*jvmti)->GetStackTrace(jvmti, threadPtr[i], 0, 10, frames, &recordCount);
CHECK_JVMTI_ERROR( jvmti, err );
printf(" Stack Trace Depth: %d\n", recordCount);
printf(" ---------------------\n");
for(j = 0; j < recordCount; j++) {
char* className = "";
char* methodName = "";
char* methodSig = "";
char* sourceFileName = "";
int k = 0;
jlocation methodStartAddr;
jlocation methodEndAddr;
jclass classPtr;
jboolean isNative;
jvmtiLineNumberEntry* lineNumTbl;
jint lineNumTblSize;
err = (*jvmti)->GetMethodName(jvmti, frames[j].method, &methodName, &methodSig, NULL);
CHECK_JVMTI_ERROR( jvmti, err );
err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[j].method, &classPtr);
CHECK_JVMTI_ERROR( jvmti, err );
err = (*jvmti)->GetClassSignature(jvmti, classPtr, &className, NULL);
CHECK_JVMTI_ERROR( jvmti, err );
err = (*jvmti)->GetSourceFileName(jvmti, classPtr, &sourceFileName);
CHECK_JVMTI_ERROR( jvmti, err );
//
// Check for the "native"ness of the method while iterating through the stack
//
err = (*jvmti)->IsMethodNative(jvmti, frames[j].method, &isNative);
CHECK_JVMTI_ERROR( jvmti, err );
if ( !isNative ) {
err = (*jvmti)->GetMethodLocation(jvmti, frames[j].method, &methodStartAddr, &methodEndAddr); // if method is in native-mode, memory access error
CHECK_JVMTI_ERROR( jvmti, err );
err = (*jvmti)->GetLineNumberTable(jvmti, frames[j].method, &lineNumTblSize, &lineNumTbl);
CHECK_JVMTI_ERROR( jvmti,err );
printf("\n at %s in class %s (%s:%d-%d)", methodName, className, sourceFileName, lineNumTbl[0].line_number, lineNumTbl[lineNumTblSize-1].line_number);
}
if ( isNative ) {
printf("\n at %s in class %s (%s:Native)", methodName, className, sourceFileName);
}
// Release memory
(*jvmti)->Deallocate(jvmti, methodName);
(*jvmti)->Deallocate(jvmti, methodSig);
(*jvmti)->Deallocate(jvmti, className);
(*jvmti)->Deallocate(jvmti, sourceFileName);
}
// Release memory
(*jvmti)->Deallocate(jvmti, threadInfo.name);
}
printf("\n\n ------------------------ THREAD DUMP COMPLETE -------------------------------------- \n\n");
}
/* Agent_OnLoad() is called first, we prepare for a VM_INIT event here. */
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
static GlobalAgentData data;
jint rc;
jvmtiError err;
jvmtiCapabilities capabilities;
jvmtiEventCallbacks callbacks;
jvmtiEnv *jvmti;
/* 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;
/* Get JVMTI environment */
jvmti = NULL;
rc = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION);
if (rc != JNI_OK) {
printf("ERROR: Unable to create jvmtiEnv, GetEnv failed, error=%d\n", rc);
return -1;
}
// save the jvmti for Agent_OnUnload
gdata->jvmti = jvmti;
//
// Set the "capabilities"
//
(void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
capabilities.can_get_source_file_name = 1;
capabilities.can_get_line_numbers = 1;
err = (*jvmti)->AddCapabilities(jvmti, &capabilities);
CHECK_JVMTI_ERROR( jvmti,err );
/* Set callbacks and enable event notifications */
memset(&callbacks, 0, sizeof(callbacks));
callbacks.DataDumpRequest = &dumpThreadInfo;
err = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
CHECK_JVMTI_ERROR(jvmti, err);
err = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_DATA_DUMP_REQUEST, NULL);
CHECK_JVMTI_ERROR(jvmti, err);
(*jvmti)->CreateRawMonitor(jvmti, "agent lock", &(gdata->lock));
printf("\n\nDemo JVMTI agent loaded and initialized\n\n");
return 0;
}
/* Agent_OnUnload() is called last */
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm)
{
}
/*
Created the main function just to placate the
MS VC++ 6.0 compiler
*/
int main(int argc, char** argv) {
return 0;
}
How can i get it to run on Windows, Linux and Solaris ?
- Java assumed that libraries loaded into itself are multi-thread safe i.e. MT-safe and therefore all your native code are possibly executed by multiple threads at any one time so there is need to put code accessing static or global data in a critical section.
Thereafter, i consulted my Visual C++ 6.0 Compiler and build the DLL which i would subsequently use to load my first rudimentary agent into the JVM and below is a screenshot running my agent with BEA WebLogic 9.2 Application Server. The figure on the left and right respectively is a screen capture of activating my first
JVM thread dump.
For Microsoft C++ Compiler, i find the following helps:
- "/Gy" - For function level linking. Refer to Microsoft's MSDN for further details and its related to build speed w.r.t. COMDATS;
- "-Op" - Disables conformance to the ANSI C and IEEE 754 standards for floating-point arithmetic (This is important for code that manipulates the precision of floating point types)
I hope i can get around to Linux and Solaris and show you ... that's all for now
0 comments:
Post a Comment