0 foreword

The previous section describes how to implement the Agent based on JVMTI. There is also a way to implement the Agent based on Java Instrument API. You can write the Agent code at the Java code level, instead of C++/C based code. For details, see the Java Instrument Function And Principles.

The agent whose name starts with -javaAgent: specifies the agent whose name is instrument by default.

So the above two kinds of Agent implementation, but also in JVMTI source code how to run the work?

1 Initialize the Agent

At JVM startup, the JVM command line arguments – agentlib-AgentPath-JavaAgent are read and the Agent Library linked list is built. The Agent initialization code is as follows:

if (match_option(option, "-agentlib:", &tail) || (is_absolute_path = match_option(option, "-agentpath:", &tail))) {  
  if(tail ! = NULL) { const char* pos = strchr(tail,'=');  
    size_t len = (pos == NULL) ? strlen(tail) : pos - tail;  
    char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len);  
    name[len] = '\ 0';  
    char *options = NULL;  
    if(pos ! = NULL) { options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(pos + 1) + 1), pos + 1); }if ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) {  
      warning("profiling and debugging agents are not supported with Kernel VM");    
    } else  if// JVMTI_KERNEL constructs the Agent Library list add_init_agent(name, options, is_absolute_path); }}else if (match_option(option, "-javaagent:", &tail)) {
  // -javaagent   
  if(tail ! = NULL) { char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(tail) + 1), tail); Add_init_agent ();"instrument", options, false);  
  }  
  // -Xnoclassgc  
}
Copy the code

2 Load the Agent link library

When JVM create_VM is started, load the specified dynamic library for each agent library and call the Agent_OnLoad method. Loading Java Instrument Agent is like loading libinStrument’s dynamic library instrument.so:

// Create agents for -agentlib:  -agentpath:  and converted -Xrun  
void Threads::create_vm_init_agents() {  
  extern struct JavaVM_ main_vm;  
  AgentLibrary* agent;  
  JvmtiExport::enter_onload_phase();  
  for(agent = Arguments::agents(); agent ! = NULL; agent = agent->next()) { OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent);if(on_load_entry ! // Call the Agent_OnLoad function jint err = (*on_load_entry)(& main_VM, agent->options(), NULL);if(err ! = JNI_OK) { vm_exit_during_initialization("agent library failed to init", agent->name()); }}else {  
      vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());  
    }  
  }  
  JvmtiExport::enter_primordial_phase();  
}
Copy the code

3 Create Instrument JPLISAgent

Create a new JPLISAgent(Java Programming Language Instrumentation Services Agent) in the Agent_OnLoad method, initialize the configuration files in the class and package, The environment for jvmtiEnv is also retrieved from the Vm environment.

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) { JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE; jint result = JNI_OK; JPLISAgent * agent = NULL; // Create a new JPLISAgent object initerror = createNewJPLISAgent(VM, & Agent);if ( initerror == JPLIS_INIT_ERROR_NONE ) {  
        if(parseArgumentTail(tail, &jarfile, &options) ! = 0) { fprintf(stderr,"-javaagent: memory allocation failure.\n");  
            return JNI_ERR;  
        }  
        attributes = readAttributes(jarfile);  
        if (attributes == NULL) {  
            fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);  
            free(jarfile);  
            if(options ! = NULL) free(options);return JNI_ERR;  
        }  
        premainClass = getAttribute(attributes, "Premain-Class");  
        if (premainClass == NULL) {  
            fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile);  
            free(jarfile);  
            if(options ! = NULL) free(options); freeAttributes(attributes);returnJNI_ERR; } /* * Add to the jarfile appends the jarfile to the agent classpath. */ appendClassPath(agent, jarfile); ... }Copy the code

In this code, you can see that premain-class in the MANIFEST file of the jar is read, and the JAR file is appended to the agent’s Class path.

4 Registering and executing the JVMTI callback interface

Here are some of JVMTI’s callback interfaces through which to set the callback function pointer:

typedef struct {
    /* 50 : VM Initialization Event */  
    jvmtiEventVMInit VMInit;   
    /* 51 : VM Death Event */  
    jvmtiEventVMDeath VMDeath; 
    /* 52 : Thread Start */  
    jvmtiEventThreadStart ThreadStart;
    /* 53 : Thread End */  
    jvmtiEventThreadEnd ThreadEnd;  
    /* 54 : Class File Load Hook */  
    jvmtiEventClassFileLoadHook ClassFileLoadHook;
    /* 55 : Class Load */  
    jvmtiEventClassLoad ClassLoad; 
    /* 56 : Class Prepare */  
    jvmtiEventClassPrepare ClassPrepare;
    /* 57 : VM Start Event */  
    jvmtiEventVMStart VMStart;
    /* 58 : Exception */  
    jvmtiEventException Exception;
    /* 59 : Exception Catch */  
    jvmtiEventExceptionCatch ExceptionCatch; 
    /* 60 : Single Step */  
    jvmtiEventSingleStep SingleStep;
    /* 61 : Frame Pop */  
    jvmtiEventFramePop FramePop;
    /* 62 : Breakpoint */  
    jvmtiEventBreakpoint Breakpoint; 
    /* 63 : Field Access */  
    jvmtiEventFieldAccess FieldAccess;
    /* 64 : Field Modification */  
    jvmtiEventFieldModification FieldModification; 
    /* 65 : Method Entry */  
    jvmtiEventMethodEntry MethodEntry;
    /* 66 : Method Exit */  
    jvmtiEventMethodExit MethodExit;
    /* 67 : Native Method Bind */  
    jvmtiEventNativeMethodBind NativeMethodBind;
    /* 68 : Compiled Method Load */  
    jvmtiEventCompiledMethodLoad CompiledMethodLoad;
    /* 69 : Compiled Method Unload */  
    jvmtiEventCompiledMethodUnload CompiledMethodUnload; 
    /* 70 : Dynamic Code Generated */  
    jvmtiEventDynamicCodeGenerated DynamicCodeGenerated; 
    /* 71 : Data Dump Request */  
    jvmtiEventDataDumpRequest DataDumpRequest;
    /* 72 */  
    jvmtiEventReserved reserved72;
    /* 73 : Monitor Wait */  
    jvmtiEventMonitorWait MonitorWait;
    /* 74 : Monitor Waited */  
    jvmtiEventMonitorWaited MonitorWaited;
    /* 75 : Monitor Contended Enter */  
    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
    /* 76 : Monitor Contended Entered */  
    jvmtiEventMonitorContendedEntered MonitorContendedEntered;
    /* 77 */  
    jvmtiEventReserved reserved77;
    /* 78 */  
    jvmtiEventReserved reserved78; 
    /* 79 */  
    jvmtiEventReserved reserved79; 
    /* 80 : Resource Exhausted */  
    jvmtiEventResourceExhausted ResourceExhausted;
    /* 81 : Garbage Collection Start */  
    jvmtiEventGarbageCollectionStart GarbageCollectionStart;
    /* 82 : Garbage Collection Finish */  
    jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
    /* 83 : Object Free */  
    jvmtiEventObjectFree ObjectFree;
    /* 84 : VM Object Allocation */  
    jvmtiEventVMObjectAlloc VMObjectAlloc;  
} jvmtiEventCallbacks;  
Copy the code

4.1 Executing the callback function of jvmtiEventVMInit

When creating create_VM, the JvmtiExport:: post_VM_initialized () command is executed after the JVMTI environment is initialized. Method with the following code:

void JvmtiExport::post_vm_initialized() {  
  EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Trg VM init event triggered" ));  
  // can now enable events  
  JvmtiEventController::vm_init();  
  JvmtiEnvIterator it;  
  for(JvmtiEnv* env = it.first(); env ! = NULL; env = it.next(env)) {if (env->is_enabled(JVMTI_EVENT_VM_INIT)) {  
      EVT_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Evt VM init event sent" ));  
      JavaThread *thread  = JavaThread::current();  
      JvmtiThreadEventMark jem(thread);  
      JvmtiJavaThreadEventTransition jet(thread);  
      jvmtiEventVMInit callback = env->callbacks()->VMInit;  
      if(callback ! Callback (env->jvmti_external(), jm.jni_env (), jm.jni_thread ()); }}}}Copy the code

4.2 perform jvmtiEventClassFileLoadHook callback function

Hook method is jvmtiEventClassFileLoadHook callback method, the code in classFileParser file:

instanceKlassHandle ClassFileParser::parseClassFile(symbolHandle name, Handle class_loader,Handle protection_domain, KlassHandle host_klass, GrowableArray<Handle>* CP_patches, symbolHandle& parsed_name,bool verify, TRAPS) {......if (JvmtiExport::should_post_class_file_load_hook()) {  
     unsigned char* ptr = cfs->buffer();  
     unsigned char* end_ptr = cfs->buffer() + cfs->length();  
     JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain,  
                                           &ptr, &end_ptr,  
                                           &cached_class_file_bytes,  
                                           &cached_class_file_length);  
     if(ptr ! = cfs->buffer()) { // JVMTI agent has modified class file data. // Set new class file stream using JVMTI agent modified // class file data. cfs = new ClassFileStream(ptr, end_ptr - ptr, cfs->source());  
        set_stream(cfs);  
     }  
  }  
  …  
}
Copy the code

Post_to_env () is called at the end of jVMtiexport ::post_class_file_load_hook:

void post_to_env(JvmtiEnv* env, bool caching_needed) {  
   unsigned char *new_data = NULL;  
   jint new_len = 0;  
   JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,  
                                    _h_protection_domain,  
                                    _h_class_being_redefined);  
  
   JvmtiJavaThreadEventTransition jet(_thread);  
  
   JNIEnv* jni_env =  (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)? NULL : jem.jni_env();  
  
   jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook;  
  
   if(callback ! = NULL) { (*callback)(env->jvmti_external(), jni_env, jem.class_being_redefined(), jem.jloader(), jem.class_name(), jem.protection_domain(), _curr_len, _curr_data, &new_len, &new_data); }... }Copy the code

Function calls the jvmtiEventClassFileLoadHook callback functions, that is just defined jvmtiEventCallbacks in structure. Hook function eventHandlerClassFileLoadHook:

void JNICALL eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv, JNIEnv * jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protectionDomain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { JPLISEnvironment * environment = NULL; environment = getJPLISEnvironment(jvmtienv); / *if something is internally inconsistent (no agent), just silently return without touching the buffer */  
    if ( environment != NULL ) {  
        jthrowable outstandingException = preserveThrowable(jnienv);  
        transformClassFile(environment->mAgent,  
                            jnienv,  
                            loader,  
                            name,  
                            class_being_redefined,  
                            protectionDomain,  
                            class_data_len,  
                            class_data,  
                            new_class_data_len,  
                            new_class_data,  
                            environment->mIsRetransformer);  
        restoreThrowable(jnienv, outstandingException);  
    }  
}  
Copy the code

What is important is the transformClassFile function to see what it does:

transformedBufferObject = (*jnienv)->CallObjectMethod(  
                                                jnienv,  
                                                agent->mInstrumentationImpl,  
                                                agent->mTransform,  
                                                loaderObject,  
                                                classNameStringObject,  
                                                classBeingRedefined,  
                                                protectionDomain,  
                                                classFileBufferObject,  
                                                is_retransformer);  
Copy the code

InstrumentationImpl call the transform InstrumentationImpl The InstrumentationImpl class calls our custom MyTransformer class’s transform method using the TransformerManager’s transform method.

    private byte[] transform(ClassLoader var1, String var2, Class var3, ProtectionDomain var4, byte[] var5, boolean var6) {
        TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager;
        return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5);
    }
Copy the code

4.3 registered jvmtiEventClassFileLoadHook hook function

As above, so the hook function when jvmtiEventClassFileLoadHook is registered, back to create new JPLISAgent code:

JPLISInitializationError  createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {  
    JPLISInitializationError initerror       = JPLIS_INIT_ERROR_NONE;  
    jvmtiEnv *               jvmtienv        = NULL;  
    jint                     jnierror        = JNI_OK;  
    *agent_ptr = NULL;  
    jnierror = (*vm)->GetEnv(vm,(void **) &jvmtienv,JVMTI_VERSION);  
    if(jnierror ! = JNI_OK) { initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT; }else {  
        JPLISAgent * agent = allocateJPLISAgent(jvmtienv);  
        if (agent == NULL) {  
            initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;  
        } else {  
            initerror = initializeJPLISAgent(agent, vm, jvmtienv);  
            if (initerror == JPLIS_INIT_ERROR_NONE) {  
                *agent_ptr = agent;  
            } else {  
                deallocateJPLISAgent(jvmtienv, agent);  
            }  
        }  
        /* don't leak envs */ if ( initerror ! = JPLIS_INIT_ERROR_NONE ) { jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv); jplis_assert(jvmtierror == JVMTI_ERROR_NONE); } } return initerror; }Copy the code

InitializeJPLISAgent initializes JPLISAgent:

JPLISInitializationError initializeJPLISAgent(JPLISAgent * agent,JavaVM * VM,jvmtiEnv * jvmtiEnv) {...... checkCapabilities(agent); jvmtierror == (*jvmtienv)->GetPhase(jvmtienv, &phase); jplis_assert(jvmtierror == JVMTI_ERROR_NONE);if (phase == JVMTI_PHASE_LIVE) {  
        return JPLIS_INIT_ERROR_NONE;  
    }  
    /* now turn on the VMInit event */  
    if ( jvmtierror == JVMTI_ERROR_NONE ) {  
        jvmtiEventCallbacks callbacks;  
        memset(&callbacks, 0, sizeof(callbacks));  
        callbacks.VMInit = &eventHandlerVMInit;  
        jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks,  sizeof(callbacks));  
  
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);  
    }  
  ……  
}  
Copy the code

JPLISAgent first registers a VMInit initialization function eventHandlerVMInit, tracking the eventHandlerVMInit function:

void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv,  
                    JNIEnv * jnienv,  
                    jthread thread) {  
    JPLISEnvironment * environment  = NULL;  
    jboolean success = JNI_FALSE;  
  
    environment = getJPLISEnvironment(jvmtienv);  
  
    /* process the premain calls on the all the JPL agents */  
    if( environment ! = NULL ) { jthrowable outstandingException = preserveThrowable(jnienv); success = processJavaStart( environment->mAgent, jnienv); restoreThrowable(jnienv, outstandingException); } / *if we fail to start cleanly, bring down the JVM */  
    if(! success ) { abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART); }}Copy the code

In processJavaStart:

jboolean processJavaStart(JPLISAgent * agent, JNIEnv * jnienv) {  
    jboolean    result;  
    result = initializeFallbackError(jnienv);  
    jplis_assert(result);  
    if ( result ) {  
        result = createInstrumentationImpl(jnienv, agent);  
        jplis_assert(result);  
    }  
    if ( result ) {  
        result = setLivePhaseEventHandlers(agent);  
        jplis_assert(result);  
    }  
    if ( result ) {  
        result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller);  
    }  
    if ( result ) {  
        deallocateCommandLineData(agent);  
    }  
    return result;  
} 
Copy the code

Is registered in the setLivePhaseEventHandler function as follows:

callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook; 
Copy the code

5 JPLISAgent structure

struct _JPLISAgent { JavaVM * mJVM; /* handle to the JVM */ JPLISEnvironment mNormalEnvironment; / *forevery thing but retransform stuff */ JPLISEnvironment mRetransformEnvironment; / *for retransform stuff only */  
    jobject                 mInstrumentationImpl;   /* handle to the Instrumentation instance */  
    jmethodID               mPremainCaller;         /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */  
    jmethodID               mAgentmainCaller;       /* method on the InstrumentationImpl for agents loaded via attach mechanism */  
    jmethodID               mTransform;             /* method on the InstrumentationImpl that does the class file transform */  
    jboolean                mRedefineAvailable;     /* cached answer to "does this agent support redefine" */  
    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */  
    jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */  
    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */  
    char const *            mAgentClassName;        /* agent class name */  
    char const *            mOptionsString;         /* -javaagent options string */  
};  
struct _JPLISEnvironment {  
    jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */  
    JPLISAgent *            mAgent;                 /* corresponding agent */  
    jboolean                mIsRetransformer;       /* indicates if special environment */  
};  
Copy the code
  1. MNormalEnvironment: Agent environment;
  2. MRetransformEnvironment: retransform environment;
  3. MInstrumentationImpl: Instrument object provided by sun;
  4. mPremainCaller:sun.instrument.InstrumentationImpl.loadClassAndCallPremainMethod, which will be called when agent starts loading;
  5. mAgentmainCaller:sun.instrument.InstrumentationImpl.loadClassAndCallAgentmainMethod, which will be called when agent Attach dynamically loads agent;
  6. mTransform:sun.instrument.InstrumentationImpl.transformMethods;
  7. mAgentClassName: specified in the Manifest.mf of javaAgentAgent-Class;
  8. MOptionsString: initial agent parameter;
  9. mRedefineAvailableParameter in manifest.mfCan-Redefine-Classes:true;
  10. mNativeMethodPrefixAvailableParameter in manifest.mfCan-Set-Native-Method-Prefix:true;
  11. mIsRetransformerParameter in manifest.mfCan-Retransform-Classes:true;

In startJavaAgent method calls the start JPLISAgent way, let’s look at invokeJavaAgentMainMethod:

jboolean invokeJavaAgentMainMethod(JNIEnv * jnienv, jobject instrumentationImpl, jmethodID mainCallingMethod, jstring className, jstring optionsString) { jboolean errorOutstanding = JNI_FALSE; jplis_assert(mainCallingMethod ! = NULL);if(mainCallingMethod ! = NULL ) { (*jnienv)->CallVoidMethod(jnienv, instrumentationImpl, mainCallingMethod, className, optionsString); errorOutstanding = checkForThrowable(jnienv);if ( errorOutstanding ) {  
            logThrowable(jnienv);  
        }  
        checkForAndClearThrowable(jnienv);  
    }  
    return! errorOutstanding; }Copy the code

In the function, is actually invokes the Java class sun. Instrument. LoadClassAndCallPremain InstrumentationImpl class methods.

    private void loadClassAndCallPremain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "premain", var2);
    }

    private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "agentmain", var2);
    }
Copy the code

Continue to see the sun Java. Instrument. LoadClassAndStartAgent method InstrumentationImpl class:

private void loadClassAndStartAgent(String classname, String methodname, String optionsString) throws Throwable { ... try { m = javaAgentClass.getDeclaredMethod( methodname, new Class<? >[] { String.class, java.lang.instrument.Instrumentation.class } ); twoArgAgent =true;  
        } catch (NoSuchMethodException x) {  
            // remember the NoSuchMethodException  
            firstExc = x;  
        }  
  
        if(m == null) { // now try the declared 1-arg method try { m = javaAgentClass.getDeclaredMethod(methodname, new Class<? >[] { String.class }); } catch (NoSuchMethodException x) { // ignore this exception because we'll try // two arg inheritance next } } if (m == null) { // now try the inherited 2-arg method try { m = javaAgentClass.getMethod( methodname, new Class
      [] { String.class, java.lang.instrument.Instrumentation.class } ); twoArgAgent = true; } catch (NoSuchMethodException x) { // ignore this exception because we'll try  
                // one arg inheritance next  
            }  
        }  
  
        if(m == null) { // finally try the inherited 1-arg method try { m = javaAgentClass.getMethod(methodname, new Class<? >[] { String.class }); } catch (NoSuchMethodException x) { // none of the methods exists so we throw the // first NoSuchMethodException as per 5.0 throw firstExc; } } // the premain method should not be required to be public, // make it accessible so we can call it // Note: The spec says the following: // The agent class must implement a public static premain method...setAccessible(m, true);  
  
        // invoke the 1 or 2-arg method  
        if (twoArgAgent) {  
            m.invoke(null, new Object[] { optionsString, this });  
        } else {  
            m.invoke(null, new Object[] { optionsString });  
        }  
  
        // don't let others access a non-public premain method setAccessible(m, false); }Copy the code

InstrumentationImpl class to initialize our custom Transformer premain method: InstrumentationImpl InstrumentationImpl class to InstrumentationImpl

public class MyInjectTransformer  implements ClassFileTransformer{  
    public static void premain(String options, Instrumentation ins) {  
        ins.addTransformer(new SQLInjectTransformer());  
    }  
      
    @Override  
    public byte[] transform(ClassLoader loader, String className, Class<? > classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {  
        return null; }}Copy the code

6. Working principle and operation sequence diagram

6.1 Start and create the JVM and register the vmInit callback function

6.2 perform vmInit callback function, register jvmtiEventClassFileLoadHook callback function, load and initialize the Instrument Agent

6.3 loading parsing Class files, execute jvmtiEventClassFileLoadHook callback function

6.4 The following uses -JavaAgent as an example to describe the working principle

  1. When the JVM starts, the Instrument Agent is loaded by passing in the Agent JAR using the JVM parameter javaAgent.
  2. During Instrument Agent initialization, the JVMTI initialization function eventHandlerVMinit is registered.
  3. When the JVM starts, the initialization function eventHandlerVMinit is called to start the Instrument Agent, With the sun. Instrument. InstrumentationImpl loadClassAndCallPremain methods in the Class to initialize Premain – Class of the specified Class Premain method;
  4. The eventHandlerVMinit initialfunction registers the ClassFileLoadHook function for class resolution;
  5. Before parsing Class, the JVM calls JVMTI ClassFileLoadHook function, hook function call sun. Instrument. InstrumentationImpl transform method in the Class, The transformer method of the TransformerManager finally calls the transform method of our custom Transformer class.
  6. Because the bytecode is changed before Class parsing, it is directly replaced by the data stream of the modified bytecode, and finally enters the Class parsing without affecting the whole Class parsing.
  7. Reload the Class and repeat steps 5-6.