InMemoryDexClassLoader探究

ClassLoader对于写Java的同学来说再熟悉不过了,在AndroidO中新增了一种ClassLoader名叫InMemoryDexClassLoader的ClassLoader,从名字上看像是从内存中直接加载class的意思,为了探究其原理,这几天在空余时间翻了翻它的源码,写成文章记录一下。

源码分析

首先我们在来看一下这个InMemoryDexClassLoader的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
/**
* Create an in-memory DEX class loader with the given dex buffers.
*
* @param dexBuffers array of buffers containing DEX files between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param parent the parent class loader for delegation.
* @hide
*/

public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
super(dexBuffers, parent);
}

/**
* Creates a new in-memory DEX class loader.
*
* @param dexBuffer buffer containing DEX file contents between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param parent the parent class loader for delegation.
*/

public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}
}

非常的简短,它是BaseDexClassLoader的子类,从构造函数中可以看出,它确实是用来加载内存中的dex文件的。

1
2
3
4
5
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}

接下去看它的父类的构造函数,其中直接用ByteBuffer数组构造了一个DexPathList。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexFiles == null) {
throw new NullPointerException("dexFiles == null");
}
if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
throw new NullPointerException("dexFiles contains a null Buffer!");
}

this.definingContext = definingContext;
// TODO It might be useful to let in-memory dex-paths have native libraries.
this.nativeLibraryDirectories = Collections.emptyList();
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);

ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}

DexPathList的构造函数中,调用了makeInMemoryDexElements方法去创建Element数组,这里可以对比看一下DexClassLoader的构造方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(buf);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}

很简单,直接构造了DexFile,而在DexFile的构造函数中,调用了createCookieWithArray方法,走到了native层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static jobject DexFile_createCookieWithArray(JNIEnv* env,
jclass,
jbyteArray buffer,
jint start,
jint end)
{

std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
if (dex_mem_map == nullptr) {
DCHECK(Thread::Current()->IsExceptionPending());
return 0;
}

auto destination = reinterpret_cast<jbyte*>(dex_mem_map.get()->Begin());
env->GetByteArrayRegion(buffer, start, end - start, destination);
return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
}

在该方法中,将java的buffer转成了native层的一个MemoryMap,并且通过CreateSingleDexFileCookie去创建对应的cookie。

1
2
3
4
5
6
7
8
9
10
static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) {
std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data)));
if (dex_file.get() == nullptr) {
DCHECK(env->ExceptionCheck());
return nullptr;
}
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files.push_back(std::move(dex_file));
return ConvertDexFilesToJavaArray(env, nullptr, dex_files);
}

这个方法里调用了CreateDexFile去创建一个DexFile,并且调用ConvertDexFilesToJavaArray将其转换成java层需要的cookie。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {
std::string location = StringPrintf("Anonymous-DexFile@%p-%p",
dex_mem_map->Begin(),
dex_mem_map->End());
std::string error_message;
std::unique_ptr<const DexFile> dex_file(DexFile::Open(location,0,std::move(dex_mem_map),
/* verify */ true,
/* verify_location */ true,
&error_message));
if (dex_file == nullptr) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("%s", error_message.c_str());
return nullptr;
}

if (!dex_file->DisableWrite()) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("Failed to make dex file read-only");
return nullptr;
}

return dex_file.release();
}

在该方法中,调用了DexFile的Open方法,并且传入了MemoryMap。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
std::unique_ptr<const DexFile> DexFile::Open(const std::string& location,
uint32_t location_checksum,
std::unique_ptr<MemMap> map,
bool verify,
bool verify_checksum,
std::string* error_msg) {
ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);
CHECK(map.get() != nullptr);

if (map->Size() < sizeof(DexFile::Header)) {
*error_msg = StringPrintf(
"DexFile: failed to open dex file '%s' that is too short to have a header",
location.c_str());
return nullptr;
}

std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
location_checksum,
kNoOatDexFile,
verify,
verify_checksum,
error_msg);
if (dex_file != nullptr) {
dex_file->mem_map_.reset(map.release());
}
return dex_file;
}

Open方法里调用了OpenCommon方法,最终会调用DexFile的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DexFile::DexFile(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file)
: begin_(base),
size_(size),
location_(location),
location_checksum_(location_checksum),
header_(reinterpret_cast<const Header*>(base)),
string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
method_handles_(nullptr),
num_method_handles_(0),
call_site_ids_(nullptr),
num_call_site_ids_(0),
oat_dex_file_(oat_dex_file) {
CHECK(begin_ != nullptr) << GetLocation();
CHECK_GT(size_, 0U) << GetLocation();
// Check base (=header) alignment.
// Must be 4-byte aligned to avoid undefined behavior when accessing
// any of the sections via a pointer.
CHECK_ALIGNED(begin_, alignof(Header));

InitializeSectionsFromMapList();
}

这里可以非常清晰看到,就是通过Dex的文件格式对其进行内存地址的赋值(对Dex文件格式不熟悉的同学可以去看我的这篇文章),这里有一点值得注意,oat_dex_file这个值,在Open中传的就是kNoOatDexFile,也就是空。回到上面的CreateSingleDexFileCookie方法,最终调用ConvertDexFilesToJavaArray时,oat_file里传的也是null。

对比DexClassLoader

那么我们对比一下原来的DexClassLoader,DexClassLoader在Native层调用的函数是openDexFileNative。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements)
{

ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return 0;
}

Runtime* const runtime = Runtime::Current();
ClassLinker* linker = runtime->GetClassLinker();
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;

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

if (!dex_files.empty()) {
jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
if (array == nullptr) {
ScopedObjectAccess soa(env);
for (auto& dex_file : dex_files) {
if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
dex_file.release();
}
}
}
return array;
} else {
ScopedObjectAccess soa(env);
CHECK(!error_msgs.empty());
// The most important message is at the end. So set up nesting by going forward, which will
// wrap the existing exception as a cause for the following one.
auto it = error_msgs.begin();
auto itEnd = error_msgs.end();
for ( ; it != itEnd; ++it) {
ThrowWrappedIOException("%s", it->c_str());
}

return nullptr;
}
}

其中通过OatFileManager的OpenDexFilesFromOat去创建DexFie,而在OpenDexFilesFromOat方法中,会通过oat_file_assistant类的dex2oat的方式创建oat文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args,
std::string* error_msg) {
Runtime* runtime = Runtime::Current();
std::string image_location = ImageLocation();
if (image_location.empty()) {
*error_msg = "No image location found for Dex2Oat.";
return false;
}

std::vector<std::string> argv;
argv.push_back(runtime->GetCompilerExecutable());
argv.push_back("--runtime-arg");
argv.push_back("-classpath");
argv.push_back("--runtime-arg");
std::string class_path = runtime->GetClassPathString();
if (class_path == "") {
class_path = OatFile::kSpecialSharedLibrary;
}
argv.push_back(class_path);
if (runtime->IsJavaDebuggable()) {
argv.push_back("--debuggable");
}
runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);

if (!runtime->IsVerificationEnabled()) {
argv.push_back("--compiler-filter=verify-none");
}

if (runtime->MustRelocateIfPossible()) {
argv.push_back("--runtime-arg");
argv.push_back("-Xrelocate");
} else {
argv.push_back("--runtime-arg");
argv.push_back("-Xnorelocate");
}

if (!kIsTargetBuild) {
argv.push_back("--host");
}

argv.push_back("--boot-image=" + image_location);

std::vector<std::string> compiler_options = runtime->GetCompilerOptions();
argv.insert(argv.end(), compiler_options.begin(), compiler_options.end());

argv.insert(argv.end(), args.begin(), args.end());

std::string command_line(android::base::Join(argv, ' '));
return Exec(argv, error_msg);
}

类加载

当我们需要用到某个类的时候,native层是通过class_linker的defineClass去解决的,而在defineClass中,通过Dex文件结构去获取其class data区的数据,需要一个索引,我们可以在oat_file这个类中找到获取索引的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const DexFile::ClassDef* OatFile::OatDexFile::FindClassDef(const DexFile& dex_file,
const char* descriptor,
size_t hash) {
const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
DCHECK_EQ(ComputeModifiedUtf8Hash(descriptor), hash);
if (LIKELY((oat_dex_file != nullptr) && (oat_dex_file->GetTypeLookupTable() != nullptr))) {
const uint32_t class_def_idx = oat_dex_file->GetTypeLookupTable()->Lookup(descriptor, hash);
return (class_def_idx != DexFile::kDexNoIndex) ? &dex_file.GetClassDef(class_def_idx) : nullptr;
}
// Fast path for rare no class defs case.
const uint32_t num_class_defs = dex_file.NumClassDefs();
if (num_class_defs == 0) {
return nullptr;
}
const DexFile::TypeId* type_id = dex_file.FindTypeId(descriptor);
if (type_id != nullptr) {
dex::TypeIndex type_idx = dex_file.GetIndexForTypeId(*type_id);
return dex_file.FindClassDef(type_idx);
}
return nullptr;
}

这里可以清楚的看到,如果一个DexFile中有oat_file,则通过oat_file去查,反之直接利用dex的文件结构去查。

后记

Google为什么要推出这么一个ClassLoader呢?我的猜想是由于插件化,热修复等技术的兴起,大家都在动态的往ClassLoader里做文章,而这难免会设计到DexFile的操作和dex2oat,这些东西Google都是不希望大家去直接使用的,于是就推出了这个直接在内存中操作dex,避免dex2oat的ClassLoader。