Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。
官方给出ClassLoader功能翻译为:
类加载器是负责加载类的对象。ClassLoader类是一个抽象类。给定类的二进制名称,类加载器应尝试查找或生成构成该类定义的数据。一种典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
ClassLoader分类
ClassLoader从继承关系图:
Android中的ClassLoader主要分为BootClassLoader、PathClassLoader和DexClassLoader这三种类型(BootClassLoader位于ClassLoader同文件下)。
BootClassLoader
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;
}
public BootClassLoader() {
super(null);
}
...
}
BootClassLoader 继承自ClassLoader抽象类,实现方式为单例模式,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,所以我们在应用程序中是无法直接调用到的。比如系统提供的String、Handler
这样的类是由BootClassLoader来加载的,这样就可以获取这个类的classLoader。
String.class.getClassLoader();
Handler.class.getClassLoader()
PathClassLoader类代码:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
PathClassLoader继承自BaseDexClassLoader,要在加载类需要传入指定要加载的路径,PathClassLoader构造方法中各个参数的含义:
在Activity或者Fragment等组件中直接可以获得classLoader对象,得到的是PathClassLoader,它可以通过context获取到。
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
@Override
public ClassLoader getClassLoader() {
return mBase.getClassLoader();
}
}
DexClassLoader类代码:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
DexClassLoader 也是继承自BaseDexClassLoader ,相比较PathClassLoader而言,DexClassLoader的构造方法中多了一个参数optimizedDirectory。
optimizedDirectory:Android系统将dex文件进行优化后所生成的ODEX文件的存放路径,该路径必须是一个内部存储路径。PathClassLoader中使用默认路径“/data/dalvik-cache”,而DexClassLoader则需要我们指定ODEX优化文件的存放路径。
上述三种ClassLoader中,PathClassLoader的parent为BootClassLoader,DexClassLoader的parent同样为BootClassLoader。
加载类过程:
首先加载类是通过ClassLoader中的loadClass方法加载,ClassLoader部分代码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
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) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}
loadClass方法中,首先调用findLoadedClass(String)方法检查这个类是否被Jvm加载过,findLoadedClass为native方法。如果找到缓存,则直接返回类,如果没有缓存,则去找parent父类加载器去加载类(注意这个类不是父类,而是parent的真实类型。如PathClassLoader的parent是BootClassLoader)。如果父类不为null,则去层层往上找parent类加载器来加载这个类。如果父加载器为null,类加载器装载虚拟机内置的加载器调用findClass(String)方法装载类。这个机制叫双亲委派机制。
-
什么是双亲委派机制?
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以加载完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
双亲委派机制的作用
1.避免重复加载,当父类加载器已经加载了该类的时候,就没有必要子类加载器再加载一次了。
首先,类加载器是按照级别层层往下加载的,当下层的加载器去加载某一个类时,有可能上层的加载已经加载过的,比如FrameWork层的加载被BootClassLoader加载过,下层不用再去加载了。
2.安全性考虑,防止核心API库被随意篡改。
系统类加载器已经加载过了FrameWork层了类,如果我们自己再写一个系统级别的类,创建包java.lang,创建一个自己的String类,类加载器去加载这个类覆盖了原本的java.lang下的String,那么这个时候使用String整个应用就出问题了。
package java.lang;
class String {
@NonNull
@Override
public String toString() {
return "";
}
@Override
public boolean equals(@Nullable Object obj) {
return false;
}
}
而因为双亲委派机制,加载这个String类之前,调用parent的BootClassloader,判断已经加载过String类,这个类其实不会再去找了,解决了被篡改的问题。
类加载的动态性体现:
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
类的加载过程:
- 装载:通过“类全路径名”查找并加载类的二进制数据
- 链接:把类的二进制数据合并到JRE中
验证:确保被加载类的正确性,确保加载内容不危害虚拟机;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
- 初始化
静态代码块执行初始化操作
类加载分类
1、启动类加载器 bootstrap classloader :加载jre/lib/rt.jar
2、扩展类加载器 extension classloader :加载jre/lib/ext/*.jar
3、应用程序类加载器 application classloader:加载classpath上指定的类库
双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
双亲委派模型工作工程:
1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
3.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
4.如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
5.如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
6.如果均加载失败,就会抛出ClassNotFoundException异常。
参考
深度分析Java的ClassLoader机制
Android ClassLoader源码解析
ClassLoader源码解析