Java反射原理

简述

对于Java反射而言 , 会非常耗性能 , 尤其是通过Class.forName来找到的Class对象. 主要的原理如下 :

  • Class.forName
    • 通过JNI调用到C层 , 再将类名转换成Descriptor
    • 通过Runtime获取ClassLinker对象
    • 通过LookupClassboot_class_path中寻找Class , 找到则返回
    • 通过BootClassLoader中寻找class , 找到则返回
    • 判断当前线程是否允许回调Java层函数 , 如果允许则开始校验描述符规则
    • 通过VMStack.getCallingClassLoader获取当前ClassLoader , 接着调用ClassLoader.loadClass返回Class
    • 更新ClassLoader的ClassTable
  • Class.getDeclaredMethods
    • 通过Class对象找到method_的值 , 即为方法区的地址
    • 通过8bit的大小来分割Method的地址

Class.forName

  1. 在Java层通过Class.forName来查找对应的Class
  • 如果传入的classLoader为空 , 则会通过VMStack.getCallingClassLoader获取ClassLoader
public static Class<?> forName(String name, boolean initialize,ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            // 如果从VMStack中获取ClassLoader失败 , 则从BootClassLoader中查找
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
            // 调用JNI层方法
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }
  1. /art/runtime/native/java_lang_class.cc文件中 , 通过Class.forName方法来获取对应的Class对象
  • 将包名换成C层的描述符 : Lcom/test/test
  • 在C层获取ClassLoader的Handle
  • 通过ClassLinker->FindClass找到Class指针
static jclass Class_classForName(JNIEnv* env, jclass, jstring javaName, jboolean initialize,  jobject javaLoader) {
  // 在ScopedFastNativeObjectAccess中 , 保存了JNIEnv对象以及所在的Thread对象
  ScopedFastNativeObjectAccess soa(env);
  ScopedUtfChars name(env, javaName);
  // 判断字符串是否为空
  if (name.c_str() == nullptr) {
    return nullptr;
  }
  // 将com.test.test转换成com/test/test验证二进制className
  if (!IsValidBinaryClassName(name.c_str())) {
    soa.Self()->ThrowNewExceptionF("Ljava/lang/ClassNotFoundException;",
                                   "Invalid name: %s", name.c_str());
    return nullptr;
  }
  // 将com.test.test转换成Lcom/test/test , 生成描述符
  std::string descriptor(DotToDescriptor(name.c_str()));
  // 从soa.Self中获取JNIEnv所在的线程对象
  StackHandleScope<2> hs(soa.Self());
  // 获取javaLoader的指针
  Handle<mirror::ClassLoader> class_loader(
      hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
  // 从Rumtime.Current中获取ClassLinker对象
  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
  // 通过ClassLinker.findClass找到Class对象 , 并且创建相关Handle
  Handle<mirror::Class> c(
      hs.NewHandle(class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader)));
  // 如果从class_table与ClassLoader中加载/获取失败的话
  if (c == nullptr) {
    ScopedLocalRef<jthrowable> cause(env, env->ExceptionOccurred());
    env->ExceptionClear();
    jthrowable cnfe = reinterpret_cast<jthrowable>(
        env->NewObject(WellKnownClasses::java_lang_ClassNotFoundException,
                       WellKnownClasses::java_lang_ClassNotFoundException_init,
                       javaName,
                       cause.get()));
    if (cnfe != nullptr) {
      // 抛出异常
      env->Throw(cnfe);
    }
    return nullptr;
  }
  if (initialize) {
    // 如果class不为空 , 并且已经初始化完 , 则会再确认初始化
    class_linker->EnsureInitialized(soa.Self(), c, true, true);
  }
  // 返回class对象的引用
  return soa.AddLocalReference<jclass>(c.Get());
}
  1. ClassLinker.FindClass中开始寻找Class指针
  • 通过LookupClass找Class指针
  • 如果Class加载失败 , 并且传入的classloader为空 , 则通过boot_class_path加载
  • 如果从boot_class_path找到了Class , 则会通过DefineClass加载class并且返回
  • 开始从BootClassloader中寻找class
  • 如果没找到 , 则判断当前线程是否允许回调Java层函数 , 失败则抛出异常
  • 如果允许回调Java层函数 , 则开始校验描述符规则
  • 通过描述符规则校验后 , 调用classLoader.loadClass返回class指针
  • 找到class后 , 会将ClassLoader的ClassTable更新
  • 最后返回class指针
mirror::Class* ClassLinker::FindClass(Thread* self,
                                      const char* descriptor,
                                      Handle<mirror::ClassLoader> class_loader) {
 // 计算描述符的hash值 , 即Lcom/test/test的hash值
  const size_t hash = ComputeModifiedUtf8Hash(descriptor);
  // 在ClassLinker的ClassTable中找Class对象
  ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());
  if (klass != nullptr) {
    return EnsureResolved(self, descriptor, klass);
  }
  // 如果Class还没有加载的话 , 并且传入的ClassLoader没有创建的话
  if (descriptor[0] != '[' && class_loader == nullptr) {
    // 开在boot class path中寻找class对象
    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
    if (pair.second != nullptr) {
       // 找到的话 , 则通过DefineClass返回 class对象
      return DefineClass(self,descriptor, hash, ScopedNullHandle<mirror::ClassLoader>(),
                         *pair.first, *pair.second);
    } else {
      // 如果BootClassPath没有找到的话 , 就抛除NoClassDefFoundError
      ObjPtr<mirror::Throwable> pre_allocated =
          Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
      self->SetException(pre_allocated);
      return nullptr;
    }
  }
  // 返回结果对应的Class的指针
  ObjPtr<mirror::Class> result_ptr;
  bool descriptor_equals;
  // 如果描述符是个数组的话
  if (descriptor[0] == '[') {
    // 通过classLoader创建Array Class
    result_ptr = CreateArrayClass(self, descriptor, hash, class_loader);
    descriptor_equals = true;
  } else {
    ScopedObjectAccessUnchecked soa(self);
    // 开始在BaseDexClassLoader中查找Class
    bool known_hierarchy =
        FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);
    if (result_ptr != nullptr) {
     // 如果在BaseDexClassLoader中找到Class
      descriptor_equals = true;
    } else {
      // 如果当前线程不允许从JNI回调到JAVA层到话
      if (!self->CanCallIntoJava()) {
        // 抛出NolassDefFoundError
        ObjPtr<mirror::Throwable> pre_allocated =
            Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
        self->SetException(pre_allocated);
        return nullptr;
      }
      // 获取描述符的长度
      size_t descriptor_length = strlen(descriptor);
      // 开始校验描述符是否正确
      if (UNLIKELY(descriptor[0] != 'L') ||
          UNLIKELY(descriptor[descriptor_length - 1] != ';') ||
          UNLIKELY(memchr(descriptor + 1, '.', descriptor_length - 2) != nullptr)) {
         // 如果校验错误 , 则返回NolassDefFoundError错误
        ThrowNoClassDefFoundError("Invalid descriptor: %s.", descriptor);
        return nullptr;
      }
      // 截取Class名字
      std::string class_name_string(descriptor + 1, descriptor_length - 2);
      // 把com/test/test替换成com.test.test包名
      std::replace(class_name_string.begin(), class_name_string.end(), '/', '.');
      // 获取ClassLoader在本线程内的引用
      ScopedLocalRef<jobject> class_loader_object(
          soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
      ScopedLocalRef<jobject> result(soa.Env(), nullptr);
      {
        ScopedThreadStateChange tsc(self, kNative);
        //  获取class_name
        ScopedLocalRef<jobject> class_name_object(
            soa.Env(), soa.Env()->NewStringUTF(class_name_string.c_str()));
        // 调用JNIEnv->CallObjectMethod方法 , 也就是调用ClassLoader.loadClass(className)
        result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
                                                 WellKnownClasses::java_lang_ClassLoader_loadClass,
                                                 class_name_object.get()));
      }
      if (result.get() == nullptr && !self->IsExceptionPending()) {
         // 如果ClassLoader.loadClass失败的话 , 则打印日志并且返回
        ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s",
                                               class_name_string.c_str()).c_str());
        return nullptr;
      }
      // 如果result.get不为空 , 则获取到class的指针
      result_ptr = soa.Decode<mirror::Class>(result.get());
      // 再检查一遍Descriptor
      descriptor_equals = (result_ptr != nullptr) && result_ptr->DescriptorEquals(descriptor);
    }
  }
  // 如果还有Pending的异常没处理的话 , 则会再重新找一遍class
  if (self->IsExceptionPending()) {
    // 有可能其他线程加载完成了这个class , 所以需要再查找一遍
    result_ptr = LookupClass(self, descriptor, hash, class_loader.Get());
    if (result_ptr != nullptr && !result_ptr->IsErroneous()) {
      // 如果没有问题的话 , 则会清理异常
      self->ClearException();
      // 重新检查完没有异常的话 , 就会返回class指针
      return EnsureResolved(self, descriptor, result_ptr);
    }
    return nullptr;
  }
  // 开始插入class table中
  ObjPtr<mirror::Class> old;
  {
   // 加锁
    WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
   // 将classLoader中的classTable插入
    ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get());
    // 在class_table中查找descriptor
    old = class_table->Lookup(descriptor, hash);
    // 如果old为空 , 代表原来的class_Table中缺失没有
    if (old == nullptr) {
      // 将class的指针赋给old
      old = result_ptr;   
      if (descriptor_equals) {
        // 如果class与descriptor匹配上了 , 则将class的指针与对应的hash值插入class_Table
        class_table->InsertWithHash(result_ptr.Ptr(), hash);
        Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get());
      }  // else throw below, after releasing the lock.
    }
  }
  // 返回class的指针 , 即地址
  return result_ptr.Ptr();
}
  1. ClassLinker.LookupClass中会从 :
  • ClassLoader中获取ClassTable
  • 接着从ClassLoader中查找descriptor对应的ClassTable对象
  • 再从ClassTable中获取descriotor对应的Class的指针
mirror::Class* ClassLinker::LookupClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        ObjPtr<mirror::ClassLoader> class_loader) {
  // 锁住ClassLoader所在的线程
  ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
  // 从ClassLoader中获取ClassTable
  ClassTable* const class_table = ClassTableForClassLoader(class_loader);
  if (class_table != nullptr) {
     // 从ClassTable中获取descriptor对应的Class指针
    ObjPtr<mirror::Class> result = class_table->Lookup(descriptor, hash);
    if (result != nullptr) {
       // 如果找到则返回class对应的指针
      return result.Ptr();
    }
  }
  return nullptr;
}

Class.getDeclaredMethods

Class.getDeclaredMethods调用后 , 同样会调用到JNI层

  • 通过jobject找到Class对象的地址
  • 根据klass->GetDeclaredMethods会先获取到method_的基址
  • 根据8bit进行分割 , 通过这些指针构建成Method对象
static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis,
                                                      jboolean publicOnly) {
  ScopedFastNativeObjectAccess soa(env);
  StackHandleScope<2> hs(soa.Self());
  // 解析javaThis的class地址
  Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
  size_t num_methods = 0;
  // 根据Method地址所占用的长度开始遍历Method对象
  for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
    auto modifiers = m.GetAccessFlags();
    //  计算Method数量
    if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
        (modifiers & kAccConstructor) == 0) {
      ++num_methods;
    }
  }
  // 初始化Method的ArrayClass数组
  auto ret = hs.NewHandle(mirror::ObjectArray<mirror::Method>::Alloc(
      soa.Self(), mirror::Method::ArrayClass(), num_methods));
  if (ret == nullptr) {
    soa.Self()->AssertPendingOOMException();
    return nullptr;
  }
  num_methods = 0;
  // 又开始遍历一遍
  for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
    auto modifiers = m.GetAccessFlags();
      // 满足下面条件
    if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) &&
        (modifiers & kAccConstructor) == 0) {
      // 通过函数指针(即m)创建ARTMethod对象
      auto* method =
          mirror::Method::CreateFromArtMethod<kRuntimePointerSize, false>(soa.Self(), &m);
      if (method == nullptr) {
        soa.Self()->AssertPendingException();
        return nullptr;
      }
      // 添加method
      ret->SetWithoutChecks<false>(num_methods++, method);
    }
  }
  // 返回Mthod数组
  return soa.AddLocalReference<jobjectArray>(ret.Get());
}

根据Class对象中methods_属性的值 , 代表Method的基址

inline LengthPrefixedArray<ArtMethod>* Class::GetMethodsPtr() {
  return reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(
      static_cast<uintptr_t>(GetField64(OFFSET_OF_OBJECT_MEMBER(Class, methods_))));
}

FAQ

  1. 多线程同时加载类是否会有问题
    回答 : 不会有问题 , 在类加载的时候 , 都有Mutex进行加锁

参考资料

JNI 提示

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249