Java && Android中类加载机制ClassLoader
一 Java中的ClassLoader
1 ClassLoader的类型
Java中的类加载器主要由两种类型:系统类加载器 & 自定义类加载器
系统类加载器包括三种:
- Bootstrap ClassLoader
- Extensions ClassLoader
- App ClassLoader
1.1 Bootstrap ClassLoader
用c/c++代码实现的加载器,用于加载Java虚拟机运行时所需要的系统类
也可通过启动java虚拟机时,指定-Xbootclasspath选项,来改变Bootstrap ClassLoader的加载目录
Java虚拟机的启动就是通过Bootstrap ClassLoader创建一个初始类完成的,由于Bootstrap ClassLoader是使用C/C++语言实现,所以该加载器不能被Java代码访问到,
注意:Bootstrap ClassLoader并不继承java.lang.ClassLoader
1.2 Extensions ClassLoader
用于加载Java的拓展类,拓展类的jar一般会放在$JAVA_HOME/jre/lib/ext目录下,用来提供除了系统类之外的额外功能。也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径
1.3 App ClassLoader
负责加载当前应用程序Classpath目录下的所有jar和Class文件,也可通过-Djava.class.path选项所指定的目录下的jar和Class文件
1.4 Custom ClassLoader
自定义类加载器通过继承java.lang.ClassLoader类的方式来实现自己的类加载器,Extensions ClassLoader & App ClassLoader也继承了java.lang.ClassLoader类。
2 ClassLoader的继承关系
运行一个java程序需要用到几种类型的类加载器呢?
public class ClassLoaderTest {
public static void main(String[] args){
// System.out.println(System.getProperty("sun.boot.class.path"));
// System.out.println(System.getProperty("java.ext.dirs"));
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null){
System.out.println(loader);
loader = loader.getParent();
}
}
}
结果如下所示
说明:第一行说明加载ClassLoaderTest的类加载器是AppClassLoader,第二行说明AppClassLoader的父类时ExtClassLoader。 AppClassLoader的父类加载器为ExtClassLoader,并不代表AppClassLoader继承自ExtClassLoader
关系图
- ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;
- SecureClassLoader继承了ClassLoader,但是SecureClassLoader并不是ClassLoader的实现类,他只是拓展了ClassLoader在权限方面的功能,加强了ClassLoader的安全性;
- URLClassLoader继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源;
- ExtClassLoader & AppClassLoader都继承自URLClassLoader,它们都是Launcher的内部类,Launcher是java虚拟机的入口应用,ExtClassLoader & AppClassLoader都是在Launcher中进行初始化的
3 双亲委托模式
3.1 双亲委托模式的特点
类加载器查找Class采用的是双亲委托模式,所谓双亲委托模式就是判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader查找到了,则直接返回,如果没找到,则继续依次向下查找,如果还没找到最后会交由自身查找。
3.2 双亲委托模式的好处
- 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是直接从缓存中直接读取;
- 更加安全,
4 自定义ClassLoader
系统提供的类加载器只能够加载指定目录下的jar包和Class文件,如果想要加载网络上的或者是D盘某一文件中的jar包和Class文件则需要自定义ClassLoader。
实现ClassLoader需要两个步骤:
1.定义一个自定义ClassLoader并继承抽象类ClassLoader;
2.复写findClass方法,并在findClass方法中调用defineClass方法
4.1 编写一个自定义ClassLoader加载位于D:\test下的Class文件
a:编写测试类并生成Class文件 Javac TestClass.java 生成TestClass.class
public class TestClass {
public void say() {
System.out.println("One more thing");
}
}
4.2 编写自定义ClassLoader
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class DiskClassLoader extends ClassLoader {
private String mPath;
public DiskClassLoader(String path) {
this.mPath = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
byte[] classData = loadClassData(name);
if(classData == null){
throw new ClassNotFoundException();
}else{
clazz = defineClass(name,classData,0,classData.length);
}
return clazz;
}
private byte[] loadClassData(String name) {
String fileName = getFileName(name);
File file = new File(mPath,fileName);
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer))!= -1){
out.write(buffer,0,length);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String getFileName(String name) {
int index = name.lastIndexOf(".");
//如果没有找到".",则直接在末尾添加.class
if(index == -1){
return name + ".class";
}else{
return name.substring(index + 1) + ".class";
}
}
}
最后验证DiskClassLoader是否可用:
public class ClassLoaderTest {
public static void main(String[] args){
DiskClassLoader diskClassLoader = new DiskClassLoader("F:\\Test");
try {
Class c = diskClassLoader.loadClass("com.aliang.a001_kotlin.TestClass");
if(c != null){
try {
Object obj = c.newInstance();
System.out.println(obj.getClass().getClassLoader());
try {
Method method = c.getDeclaredMethod("say", null);
try {
method.invoke(obj,null);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
值的注意的是:在工程中不能在项目工程中存在名为com.aliang.a001_kotlin.TestClass的Java文件,否则就不会使用DiskClassLoader来加载,
而是AppClassLoader来负责加载,这样我们定义DiskClassLoader就变得毫无意义。
错误的用法:不调用自定义的ClassLoader
删掉com.aliang.a001_kotlin.TestClass 之后调用自定义的ClassLoader。
二 Android中的ClassLoader
1 ClassLoader的类型:
Android中的ClassLoader也分为两种:系统ClassLoader和自定义ClassLoader
系统ClassLoader分类:
- BootClassLoader
- PathClassLoader
- DexClassLoader
1.1 BootClassLoader:
Android系统启动时会使用BootClassLoader来预加载常用类,与Java中不同的是他不是用C/C++代码实现,而是用Java实现,BootClassLoader的代码如下:
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
BootClassLoader是ClassLoader的内部类,并继承自ClassLoader,BootClassLoader是一个单例类,需要注意的是他的访问修饰符是默认的,只能在同一个包名中才能访问,因此我们再应用程序中是无法调用的。
1.2 DexClassLoader
DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk 和jar文件) ,不管是加载那种文件,最终都是要加在dex文件,代码如下:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
DexClassLoader的构造方法由四个参数:
- dexPath:dex相关文件路径集合,多个路径有文件分隔符分隔,默认文件分隔符为":";
- optimizedDirectory:解压的文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:
/data/data/<Package Name>/...
。 - librarySearchPath:包含C/C++库的路径的集合,多个路径用文件分隔符分隔分割,可以为null
- parent:父加载器
DexClassLoader 继承自BaseDexClassLoader,方法实现都在BaseDexClassLoader中实现
1.3 PathClassLoader
Android系统使用PathClassLoader来加载系统类和应用程序类,代码如下:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader继承自BaseDexClassLoader,实现也都在BaseDexClassLoader中.
PathClassLoader的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了此参数的值为:/data/dalvik-cache,很显然PathClassLoader无法自定义解压的dex文件存储路径,因此PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)
2 ClassLoader的继承关系
运行一个Android程序需要用到几种类型的类加载器呢?
代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader loader = MainActivity.class.getClassLoader();
while (loader != null){
Log.e("alvin-moon",loader.toString());
loader = loader.getParent();
}
}
结果如下:可以看到有两种类加载器:一种是PathClassLoader;另一种是BootClassLoader,DexPathList中包含了很多apk的路径,
"/data/app/com.aliang.a001_kotlin-1/base.apk"就是示例应用按炸U能够在手机上的位置
09-06 14:24:31.858 12039-12039/com.aliang.a001_kotlin E/alvin-moon: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.aliang.a001_kotlin-1/base.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_0_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_2_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_4_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_5_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_7_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.aliang.a001_kotlin-1/lib/arm64, /vendor/lib64, /system/lib64]]]
aaa@qq.com
和Java的ClassLoader一样,虽然系统所提供的类加载器主要由3中类型,但是系统提供的ClassLoader相关类却不只有3个.ClassLoader的继承关系如下图所示:
- ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能,BootClassLoader是他的内部类
- SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,他继承抽象类ClassLoader,SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
- URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
- InMemoryDexClassLoader是Android8.0新增的类加载器,继承I自BaseDexClassLoader,用于加载内存中的dex文件
- BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。
3.BootClassLoader的创建
BootClassLoader是何时被创建的呢?这要从Zygote进程开始说起
Zygote的main方法如下所示:
public static void main(String argv[]) {
...
try {
...
preload(bootTimingsTraceLog);
...
}
}
main方法时Zygote入口方法,其中调用了ZygoteInit的preload()方法,preload方法中又调用了ZygoteInit的preloadClasses方法:
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is;
try {
is = new FileInputStream(PRELOADED_CLASSES);//1
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
return;
}
...
try {
BufferedReader br
= new BufferedReader(new InputStreamReader(is), 256);//2
int count = 0;
String line;
while ((line = br.readLine()) != null) {//3
line = line.trim();
if (line.startsWith("#") || line.equals("")) {
continue;
}
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
try {
if (false) {
Log.v(TAG, "Preloading " + line + "...");
}
Class.forName(line, true, null);//4
count++;
} catch (ClassNotFoundException e) {
Log.w(TAG, "Class not found for preloading: " + line);
}
...
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
...
}
}
preloadClasses方法用于Zygote进程初始化时预加载常用类,注释1处将/system/etc/preloaded-classes文件封装成FileInputStream,preloaded-classes文件中存有预加载类目标,这个文件在系统源码中的路径为frameworks/base/preloaded-classes,这里列举一些preloaded-classes文件中的预加载类名称,如下所示:
android.app.ApplicationLoaders
android.app.ApplicationPackageManager
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$ApplicationContentResolver
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DownloadManager
android.app.Fragment
可以看到preloaded-classes文件中的预加载类的名称有很多都是我们非常熟知的。预加载属于拿空间换时间的策略,Zygote环境配置的越健全越通用,应用程序进程需要单独做的事情也就越少,预加载除了预加载类,还有预加载资源和预加载共享库,因为不是本文重点,这里就不在延伸讲下去了。
回到preloadClasses方法的注释2处,将FileInputStream封装为BufferedReader,并注释3处遍历BufferedReader,读出所有预加载类的名称,每读出一个预加载类的名称就调用注释4处的代码加载该类,Class的forName方法如下所示。
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
loader = BootClassLoader.getInstance();//1
}
Class<?> result;
try {
result = classForName(name, initialize, loader);//2
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
注释1处创建了BootClassLoader,并将BootClassLoader实例传入到了注释2处的classForName方法中,classForName方法是Native方法,它的实现由c/c++代码来完成,如下所示
@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;
4.PathClassLoader的创建
PathClassLoader的创建也得从Zygote进程开始说起,Zygote进程启动SyetemServer进程时会调用ZygoteInit的startSystemServer方法,如下所示。
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
...
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);//2
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/*1*/
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
if (pid == 0) {//2
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
handleSystemServerProcess(parsedArgs);//3
}
return true;
}
注释1处,Zygote进程通过forkSystemServer方法fork自身创建子进程(SystemServer进程)。注释2处如果forkSystemServer方法返回的pid等于0,说明当前代码是在新创建的SystemServer进程中执行的,接着就会执行注释3处的handleSystemServerProcess方法:
private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws Zygote.MethodAndArgsCaller {
...
if (parsedArgs.invokeWith != null) {
...
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
Thread.currentThread().setContextClassLoader(cl);
}
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
}
注释1处调用了createPathClassLoader方法,如下所示。
static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
String libraryPath = System.getProperty("java.library.path");
return PathClassLoaderFactory.createClassLoader(classPath,
libraryPath,
libraryPath,
ClassLoader.getSystemClassLoader(),
targetSdkVersion,
true /* isNamespaceShared */);
}
createPathClassLoader方法中又会调用PathClassLoaderFactory的createClassLoader方法,看来PathClassLoader是用工厂来进行创建的。
public static PathClassLoader createClassLoader(String dexPath,
String librarySearchPath,
String libraryPermittedPath,
ClassLoader parent,
int targetSdkVersion,
boolean isNamespaceShared) {
PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
...
return pathClassloader;
}
在PathClassLoaderFactory的createClassLoader方法中会创建PathClassLoader。
总结:
BootClassLoader是在Zygote进程的入口方法中创建的,PathClassLoader则是在Zygote进程创建SystemServer进程时创建的。