Skip to content

jcmd class_reload

ceragon edited this page Jun 24, 2022 · 4 revisions

jvm 中的 class_reload

java 中的用法

public final class ClassReloadAgent {
    private static Instrumentation inst = null;
    public static void premain(String agentArgs, Instrumentation i) {
        inst = i;
    }
}

jvm 中对 premain 的调用

jboolean
invokeJavaAgentMainMethod( JNIEnv *    jnienv,
                           jobject     instrumentationImpl,
                           jmethodID   mainCallingMethod,
                           jstring     className,
                           jstring     optionsString) {
    jboolean errorOutstanding = JNI_FALSE;

    if ( mainCallingMethod != NULL ) {
        // 调用 java 代码中的 premain 方法
        (*jnienv)->CallVoidMethod(  jnienv,
                                    instrumentationImpl,
                                    mainCallingMethod,
                                    className,
                                    optionsString);
        errorOutstanding = checkForThrowable(jnienv);
    }
    return !errorOutstanding;
}

由于 instrumentationImpl 参数是关注的重点,因此查看一下它的赋值和调用过程

jboolean
startJavaAgent( JPLISAgent *    agent,
                JNIEnv *        jnienv,
                const char *    classname,
                const char *    optionsString,
                jmethodID       agentMainMethod) {
    // agent->mInstrumentationImpl ,因此再查看 agent 的调用
    if (success) {
        success = invokeJavaAgentMainMethod(   jnienv,
                                               agent->mInstrumentationImpl,
                                               agentMainMethod,
                                               classNameObject,
                                               optionsStringObject);
    }
    return success;
}
jboolean
processJavaStart(   JPLISAgent *    agent,
                    JNIEnv *        jnienv) {
    jboolean    result;
    if ( result ) {
        // 很明显,InstrumentationImpl 在这里赋值了
        result = createInstrumentationImpl(jnienv, agent);
    }
    if ( result ) {
        // 调用上面的方法
        result = startJavaAgent(agent, jnienv,
                                    agent->mAgentClassName, agent->mOptionsString,
                                    agent->mPremainCaller);    
    }
}

InstrumentationImpl 的 create

jboolean
createInstrumentationImpl( JNIEnv *        jnienv,
                           JPLISAgent *    agent) {
    jobject     localReference          = NULL;
    // peerReferenceAsScalar: 对应的就是 InstrumentationImpl 的 mNativeAgent
    // 因此还是得看 agent 的产生
    jlong   peerReferenceAsScalar = (jlong)(intptr_t) agent;
    localReference = (*jnienv)->NewObject(  jnienv,
                                                implClass,
                                                constructorID,
                                                peerReferenceAsScalar,
                                                agent->mRedefineAdded,
                                                agent->mNativeMethodPrefixAdded);
    
    resultImpl = (*jnienv)->NewGlobalRef(jnienv, localReference);
    agent->mInstrumentationImpl = resultImpl;
}

agent 的产生过程

void JNICALL
eventHandlerVMInit( jvmtiEnv *      jvmtienv,
                    JNIEnv *        jnienv,
                    jthread         thread) {
    JPLISEnvironment * environment  = NULL;
    environment = getJPLISEnvironment(jvmtienv);
    success = processJavaStart( environment->mAgent, jnienv);
}
JPLISEnvironment *
getJPLISEnvironment(jvmtiEnv * jvmtienv) {
    JPLISEnvironment * environment  = NULL;
    jvmtierror = (*jvmtienv)->GetEnvironmentLocalStorage(
                                            jvmtienv,
                                            (void**)&environment);
    return environment;
}
JvmtiEnv::GetEnvironmentLocalStorage(void** data_ptr) {
    *data_ptr = (void*)get_env_local_storage();
    return JVMTI_ERROR_NONE;
}

redefineClasses0

void JNICALL Java_sun_instrument_InstrumentationImpl_redefineClasses0
      (JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classDefinitions) {
    redefineClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classDefinitions);
}
void redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {
    jvmtiEnv*   jvmtienv                        = jvmti(agent);
    jvmtiClassDefinition* classDefs             = NULL;
    jsize       numDefs                         = 0;
    numDefs = (*jnienv)->GetArrayLength(jnienv, classDefinitions);
    jvmtiError  errorCode = JVMTI_ERROR_NONE;
    errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);
}
jvmtiError
JvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {
    VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);
    VMThread::execute(&op);
}

VM_RedefineClasses

class VM_RedefineClasses: public VM_Operation {
    bool doit_prologue();
    void doit();
    void doit_epilogue();
};

核心的比较能否替换的检查

jvmtiError VM_RedefineClasses::compare_and_normalize_class_versions(
             InstanceKlass* the_class,
             InstanceKlass* scratch_class) {
    // 1. 父类的地址要一致
    // 2. 父类不能为空
    // 3. 父类的名字得一样
    if (the_class->super() != scratch_class->super() &&
          (the_class->super() == NULL || scratch_class->super() == NULL ||
           the_class->super()->name() !=
           scratch_class->super()->name())) {
        return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED;
    }
    
    // 原类的所有接口
    Array<Klass*>* k_interfaces = the_class->local_interfaces();
    // 新类的所有接口
    Array<Klass*>* k_new_interfaces = scratch_class->local_interfaces();
    // 首先接口数量得一样
    int n_intfs = k_interfaces->length();
    if (n_intfs != k_new_interfaces->length()) {
        return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED;
    }
    // 不仅接口的名字要对上,顺序还不能变
    for (i = 0; i < n_intfs; i++) {
        if (k_interfaces->at(i)->name() !=
            k_new_interfaces->at(i)->name()) {
            return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED;
        }
    }
    
    if (the_class->is_in_error_state()) {
        // TBD #5057930: special error code is needed in 1.6
        return JVMTI_ERROR_INVALID_CLASS;
    }
    
    // 类的属性要一致
    jvmtiError err = check_nest_attributes(the_class, scratch_class);
    if (err != JVMTI_ERROR_NONE) {
        return err;
    }
    
    // 类型的访问标志要一致,不能变
    jushort old_flags = (jushort) the_class->access_flags().get_flags();
    jushort new_flags = (jushort) scratch_class->access_flags().get_flags();
    if (old_flags != new_flags) {
        return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED;
    }
    
    // 检查成员变量是否一致
    JavaFieldStream old_fs(the_class);
    JavaFieldStream new_fs(scratch_class);
    for (; !old_fs.done() && !new_fs.done(); old_fs.next(), new_fs.next()) {
        // access
        old_flags = old_fs.access_flags().as_short();
        new_flags = new_fs.access_flags().as_short();
        // JVM_RECOGNIZED_FIELD_MODIFIERS: 是所有支持的访问标志
        // 由于异或后相同为0,不同为1. 所以只要异或的结果不为0,那么与上后者就会变为 1
        if ((old_flags ^ new_flags) & JVM_RECOGNIZED_FIELD_MODIFIERS) {
            return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED;
        }
        
        // 成员变量的顺序不允许改变
        if (old_fs.offset() != new_fs.offset()) {
            return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED;
        }
        
        // name and signature
        Symbol* name_sym1 = the_class->constants()->symbol_at(old_fs.name_index());
        Symbol* sig_sym1 = the_class->constants()->symbol_at(old_fs.signature_index());
        Symbol* name_sym2 = scratch_class->constants()->symbol_at(new_fs.name_index());
        Symbol* sig_sym2 = scratch_class->constants()->symbol_at(new_fs.signature_index());
        // 比较在常量表中的名字是否一样,注意此步骤不严格要求在常量表中的索引一致
        if (name_sym1 != name_sym2 || sig_sym1 != sig_sym2) {
            return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED;
        }
    }
    
    // 成员变量的数量不一致
    if (!old_fs.done() || !new_fs.done()) {
        return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED;
    }
    
    // 比较方法的内容是否一致
    Array<Method*>* k_old_methods(the_class->methods());
    Array<Method*>* k_new_methods(scratch_class->methods());
    int n_old_methods = k_old_methods->length();
    int n_new_methods = k_new_methods->length();
    Thread* thread = Thread::current();
    int ni = 0;
    int oi = 0;
    while (true) {
        Method* k_old_method;
        Method* k_new_method;
        enum { matched, added, deleted, undetermined } method_was = undetermined;
        if (oi >= n_old_methods) {
            if (ni >= n_new_methods) {
                break;
            }
            // 新增了方法
            k_new_method = k_new_methods->at(ni);
            method_was = added;
        } else if (ni >= n_new_methods) {
            k_old_method = k_old_methods->at(oi);
            // 删除了方法
            method_was = deleted;
        } else {
            k_old_method = k_old_methods->at(oi);
            k_new_method = k_new_methods->at(ni);
            // 方法名字得一样
            if (k_old_method->name() != k_new_method->name()) {
                // 正常情况下,返回值 == 0
                if (k_old_method->name()->fast_compare(k_new_method->name()) > 0) {
                    method_was = added;
                } else {
                    method_was = deleted;
                }
            } else if (k_old_method->signature() == k_new_method->signature()) {
                // 方法的名字一样,签名也匹配
                method_was = matched;
            } else {
                // 出现了不一样的方法,很有可能是新类删除了原类当前的方法
                // 如果新类的下一个方法没有匹配上,则说明新类删除了方法,否则就说明新类增加了一个方法
                int nj;
                for (nj = ni + 1; nj < n_new_methods; nj++) {
                    // 新类的下一个方法
                    Method* m = k_new_methods->at(nj);
                    if (k_old_method->name() != m->name()) {
                        // 新类删除了方法
                        method_was = deleted;
                        break;
                    }
                    if (k_old_method->signature() == m->signature()) {
                        // 匹配上了,说明新类在之前插入了一个新方法
                        // 下面是在两个方法对调,调整顺序
                        k_new_methods->at_put(ni, m);
                        k_new_methods->at_put(nj, k_new_method);
                        k_new_method = m;
                        method_was = matched;
                        break;
                    }
                    
                }
                if (nj >= n_new_methods) {
                    // 新类没有下一个方法了,说明新类就是把方法删了
                    method_was = deleted;
                }
            }
        }
        
        switch (method_was) {
            case matched:
                // 比较方法的访问标志是否一样
                old_flags = (jushort) k_old_method->access_flags().get_flags();
                new_flags = (jushort) k_new_method->access_flags().get_flags();
                if ((old_flags ^ new_flags) & ~(JVM_ACC_NATIVE)) {
                    return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED;
                }
                {
                    u2 new_num = k_new_method->method_idnum();
                    u2 old_num = k_old_method->method_idnum();
                    // 下面是在调整新类的方法顺序,来保证和原类一致
                    if (new_num != old_num) {
                        Method *idnum_owner = scratch_class->method_with_idnum(old_num);
                        if (idnum_owner != NULL) {
                            idnum_owner->set_method_idnum(new_num);
                            idnum_owner->set_orig_method_idnum(k_new_method->orig_method_idnum());
                        }
                        // Take current and original idnum from the old_method
                        k_new_method->set_method_idnum(old_num);
                        k_new_method->set_orig_method_idnum(k_old_method->orig_method_idnum());
                        // 可能产生 OOM 异常
                        if (thread->has_pending_exception()) {
                            return JVMTI_ERROR_OUT_OF_MEMORY;
                        }
                    }
                    
                }
                ++oi;
                ++ni;
                break;
            case added:
                new_flags = (jushort) k_new_method->access_flags().get_flags();
                // 必须是私有的,且 final 和 static 满足其一
                if ((new_flags & JVM_ACC_PRIVATE) == 0
                       // hack: private should be treated as final, but alas
                      || (new_flags & (JVM_ACC_FINAL|JVM_ACC_STATIC)) == 0
                     ) {
                    return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED;
                }
                {
                    // 由于新增了方法,所以需要给方法安排一个常量表的位置
                    u2 num = the_class->next_method_idnum();
                    if (num == ConstMethod::UNSET_IDNUM) {
                        // 常量表满了
                        return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED;
                    }
                    // 新增方法的索引
                    u2 new_num = k_new_method->method_idnum();
                    // num 是分配给当前方法的位置,查一下这个位置在新类中是否被占了
                    Method* idnum_owner = scratch_class->method_with_idnum(num);
                    if (idnum_owner != NULL) {
                        // 被占了,对调一下位置
                        idnum_owner->set_method_idnum(new_num);
                        idnum_owner->set_orig_method_idnum(k_new_method->orig_method_idnum());
                    }
                    // 没被占, 设置位置
                    k_new_method->set_method_idnum(num);
                    k_new_method->set_orig_method_idnum(num);
                    if (thread->has_pending_exception()) {
                      return JVMTI_ERROR_OUT_OF_MEMORY;
                    }
                }
                break;
            case deleted:
                old_flags = (jushort) k_old_method->access_flags().get_flags();
                // private final 或者 private static 方法可以删除
                if ((old_flags & JVM_ACC_PRIVATE) == 0
                        || (old_flags & (JVM_ACC_FINAL|JVM_ACC_STATIC)) == 0
                        ) {
                    return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED;
                }
                break;
        }
        
    }
}

特殊的热加载测试

测试成员变量的默认值

java 源码如下

public class TestReload {
    private final int d = 6;
    private static int c = 4;
    private final static int b = 3;
    private int a = 2;
    public void testPrint() {
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }

    public static void main(String[] args) throws InterruptedException {
        TestReload reload = new TestReload();
        while (true) {
            reload.testPrint();
            TimeUnit.SECONDS.sleep(1L);
        }
    }
}

在上面的例子中,只有变量 d 和 b 可以热加载,通过反汇编字节码得到如下结果

 0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
 3: aload_0
 4: getfield      #3                  // Field a:I
 7: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
13: iconst_3
14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
17: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
20: getstatic     #7                  // Field c:I
23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
26: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
29: bipush        6
31: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
34: return

对应的字节码如下,所以结论就是 final 的基本类型变量,直接被优化成了字节码,而不是 getField。

  • a: getfield
  • b: iconst_3
  • c: getstatic
  • d: bipush 6

Clone this wiki locally