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 firstfindLoadedClass()
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
BootClassLoader
to 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.cc
file.
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 ofRuntime::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_loader
it is nullptr
, nullptr
used , 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_table
ClassLoader
ClassTable
class_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.java
implemented, 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;
}
pathList
The type is a dex operation such as a handle DexPathList
to save dexfile
a file. pathList.findClass()
The implementation looks for the class in the current dex and initializes it pathList
at 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;
}
}
dexElements
The 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 dexfile
is achieved through the method , native
and the return type is used to identify the uniqueness. Implementation code:mCookie
mCookie
int
dex
openDexFileNative()
//`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 aotManager
opening 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 element
saved dex DexFile
handle, 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 native
implemented 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 Runtime
getting the current ClassLinker
object, 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"