Source code analysis of Android ClassLoader loading process

background

During the Android development process, the development partners are certainly no strangers to dynamically loading code. There should be contact with each open source framework, and the main principle is inseparable from related classes such as ClassLoader. Here we will start with the source code of related classes such as ClassLoader in Android to better understand and learn the principle of dynamically loading classes.

Detailed analysis of the loading principle of ClassLoader

The inheritance relationship of ClassLoader is as follows:

Here we mainly analyze how the BaseDexClassLoader.findClass()and ClassLoader.loadClass()two functions perform the process of finding classes in the system.

Let's take a look at the system loading class ClassLoader.loadClass()function implementation code, in ClassLoader.java:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先 检测是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        //去调用父类的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    //未找到的情况下,使用findClass在当前dex查找
                    c = findClass(name);
                }
            }
            return c;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
  • 1. loadClass()Call first findLoadedClass()to determine whether the current class has been loaded;
  • 2. If no recursion is found, go to the parent class to find out whether it is loaded into the cache;
  • 3, are not cached, go BootClassLoaderto find;
  • 4. If the above is not found, search down from the top-level parent class in turn, and call to findClass()find the current dex.

findLoadedClass function analysis

The following figure shows findLoadedClass()the call flow; according to the call flow chart and the source code, the detailed analysis principle is carried out.

The corresponding source code implementation part is described below:

    protected final Class<?> findLoadedClass(String name) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        return VMClassLoader.findLoadedClass(loader, name);
    }

The function is finally called uniformly VMClassLoader.findLoadedClass()to find the class.

native static Class findLoadedClass(ClassLoader cl, String name);

implemented in the java_lang_VMClassLoader.ccfile.

static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoader,jstring javaName) {
  ....
  ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(javaLoader);
  ClassLinker* cl = Runtime::Current()->GetClassLinker();

  ObjPtr<mirror::Class> c = VMClassLoader::LookupClass(cl,
                                                       soa.Self(),
                                                       descriptor.c_str(),
                                                       descriptor_hash,
                                                       loader);
  if (c != nullptr && c->IsResolved()) {
    return soa.AddLocalReference<jclass>(c);
  }
  ...
  if (loader != nullptr) {
    // Try the common case.
    StackHandleScope<1> hs(soa.Self());
    c = VMClassLoader::FindClassInPathClassLoader(cl,
                                                  soa,
                                                  soa.Self(),
                                                  descriptor.c_str(),
                                                  descriptor_hash,
                                                  hs.NewHandle(loader));
    if (c != nullptr) {
      return soa.AddLocalReference<jclass>(c);
    }
  }

  return nullptr;
}

  static mirror::Class* LookupClass(ClassLinker* cl,
                                    Thread* self,
                                    const char* descriptor,
                                    size_t hash,
                                    ObjPtr<mirror::ClassLoader> class_loader)
      REQUIRES(!Locks::classlinker_classes_lock_)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return cl->LookupClass(self, descriptor, hash, class_loader);
  }
  static ObjPtr<mirror::Class> FindClassInPathClassLoader(ClassLinker* cl,
                                                          ScopedObjectAccessAlreadyRunnable& soa,
                                                          Thread* self,
                                                          const char* descriptor,
                                                          size_t hash,
                                                          Handle<mirror::ClassLoader> class_loader)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    ObjPtr<mirror::Class> result;
    if (cl->FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result)) {
      return result;
    }
    return nullptr;
  }

The above code findLoadedClass()is divided into two steps;

  • 1. class_linker_->Lookupclass()Load the class by searching;
  • 2. If not found class_linker_->FindClassInPathClassLoader(), search through.

class_linker_startVM()Initialization performed when the virtual machine starts the function. <br> Initialization done at the time of Runtime::class_linker_the function.Runtime::Init()

  if (UNLIKELY(IsAotCompiler())) {
    class_linker_ = new AotClassLinker(intern_table_);
  } else {
    class_linker_ = new ClassLinker(intern_table_);
  }

Continue to analyze ClassLinker::LookupClass()the specific implementation of the function;

mirror::Class* ClassLinker::LookupClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        ObjPtr<mirror::ClassLoader> class_loader) {
  ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
  ClassTable* const class_table = ClassTableForClassLoader(class_loader);
  if (class_table != nullptr) {
    ObjPtr<mirror::Class> result = class_table->Lookup(descriptor, hash);
    if (result != nullptr) {
      return result.Ptr();
    }
  }
  return nullptr;
}

LookupClass()The function is obtained by whether class_loaderit is nullptr, nullptrused , or the current one otherwise . To store the currently loaded class, it can actually be understood as a class cache. How to perform dex parsing and aot and other loading system classes and parsing maps to memory is not analyzed here. You can understand the art virtual machine startup for detailed analysis.boot_class_table_class_tableClassLoaderClassTableclass_table

findClass() function analysis

The following figure is the call flow of findClass; according to the call flow chart and the following code for a detailed analysis and understanding;

Below we introduce the corresponding source code implementation part.

findClass()The function is being BaseDexClassLoader.javaimplemented, and the main thing this function does is to look up the class in the current dex. Returns if the class is in the current dex.

code show as below:

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ...
            throw cnfe;
        }
        return c;
    }

pathListThe type is a dex operation such as a handle DexPathListto save dexfilea file. pathList.findClass()The implementation looks for the class in the current dex and initializes it pathListat construction time.new DexClassLoader()

  public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        ...
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
        ...
    }

DexPathList.java

public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {

        ...
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);

        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

dexElementsThe array holds the dexfile file handle. The specific implementation makeDexElements()calls the function in the loadDexFile()function to load the dex. This function implements:

DexFile.java
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file, loader, elements);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
        }
    }

DexFile.loadDex()Perform parsing to load the dex file. The key code is as follows:

private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    ...
    mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
    mInternalCookie = mCookie;
    mFileName = sourceName;
    ...
}

private static Object openDexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    // Use absolute paths to enable the use of relative paths when testing on host.
    return openDexFileNative(new File(sourceName).getAbsolutePath(),
                              (outputName == null)
                              ? null
                              : new File(outputName).getAbsolutePath(),
                              flags,loader,elements);
}

private static native Object openDexFileNative(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements);

The final open dexfileis achieved through the method , nativeand the return type is used to identify the uniqueness. Implementation code:mCookiemCookieintdexopenDexFileNative()

//`dalvik_system_DexFile.cc`
static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements)
{
  ...
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  
  ...

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), class_loader, dex_elements, /*out*/ &oat_file, /*out*/ &error_msgs);
  ....
}

The above code is implemented by aotManageropening and returning mCookie, and further opening is not expanded here. That is, the above has been filled , and the search method of the function is elements[]expanded below .pathList.findClass()

    //BaseDexClassLoader.java
    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

findClass()It will traverse elements[], each elementsaved dex DexFilehandle, and then call the loadClassBinaryName()function to perform the current dex lookup class.

//DexPathList.java
  public Class<?> findClass(String name, ClassLoader definingContext,
          List<Throwable> suppressed) {
      return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;
  }
  public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
      return defineClass(name, loader, mCookie, this, suppressed);
  }

  private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) {
      Class result = null;
      try {
          result = defineClassNative(name, loader, cookie, dexFile);
      } catch (NoClassDefFoundError e) {
          if (suppressed != null) {
              suppressed.add(e);
          }
      } catch (ClassNotFoundException e) {
          if (suppressed != null) {
              suppressed.add(e);
          }
      }
      return result;
  }

The function of actually going to dex or finding a class in memory is nativeimplemented defineClassNative()in , let's analyze the real implementation process:

private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)

//dalvik_system_DexFile.cc
static jclass DexFile_defineClassNative(JNIEnv* env,
                                        jclass,
                                        jstring javaName,
                                        jobject javaLoader,
                                        jobject cookie,
                                        jobject dexFile) {
  std::vector<const DexFile*> dex_files;
  const OatFile* oat_file;
  if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
    ...
    return nullptr;
  }

  ScopedUtfChars class_name(env, javaName);
  ...

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
  for (auto& dex_file : dex_files) {
      ...
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
                                                               descriptor.c_str(), 
                                                               hash,
                                                               class_loader,
                                                               *dex_file,
                                                               *dex_class_def);
      // Add the used dex file. This only required for the DexFile.loadClass API since normal
      // class loaders already keep their dex files live.
      class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),
                                                 class_loader.Get());
      ....
        return soa.AddLocalReference<jclass>(result);
      }
    }
    ...
  return nullptr;
}

By Runtimegetting the current ClassLinkerobject, and then by class_linker->DefineClass()looking up the class in the current dex. Then cache the found class by class_linker->InsertDexFileInToClassLoader()inserting it into class_table, and return the found class. No further analysis is carried out here.

The source code analysis of the Android ClassLoader loading process has been analyzed almost at this point. If you want to understand the specific principles in depth, you can look at the implementation of the source code yourself. It is introduced here. It is the first time to write an article on technology sharing. Please correct me if I have any mistakes, thank you!

<br>

(360 technology original content, please keep the QR code at the end of the article for reprinting, thank you~)

About 360 Technology

360 Technology is a technology sharing public account created by the 360 ​​technology team, and pushes dry technical content every day

For more technical information, please pay attention to the WeChat public account of "360 Technology"

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324126748&siteId=291194637