1.ClassLoader概念
ClassLoader是用来动态的加载class文件到虚拟机,并转换成java.lang.class类的一个实例,并通过实例的newInstance()方法创建出该类的一个实例。除此之外,ClassLoader还负责加载java应用所需的资源,如图片文件和配置文件。
ClassLoader类其实是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。ClassLoader类使用委托模型来搜索类和资源。每个 ClassLoader实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
2.简介ClassLoader的流程
首先JVM启动的时候,并非一次性加载所有的class文件,而是通过(Bootstrap classLoader)启动类加载器来加载JVM自身工作需要的类,如java.lang.,java.util.等等;这些类位于&JAVA_HOME/jre/lib/rt.jar。启动类加载器不继承自ClassLoader,是在底层C++编写,已经嵌入到了JVM内核当中,当JVM启动后,启动类加载器也随之启动,负责加载核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器
ExtClassLoader:扩展的class loader,加载位于JAVA_HOME/jre/lib/ext目录下的扩展jar。 AppClassLoader:系统class loader,父类是ExtClassLoader,加载CLASSPATH下的目录和jar;它负责加载应用程序主函数类。
如果要实现自己的类加载器,不管是实现抽象列ClassLoader,还是继承URLClassLoader类,它的父加载器都是AppClassLoader,因为不管调用哪个父类加载器,创建的对象都必须最终调用getSystemClassLoader()作为父加载器,getSystemClassLoader()方法获取到的正是AppClassLoader。
注意:Bootstrap classLoader并不属于JVM的等级层次,它不遵守ClassLoader的加载规则,Bootstrap classLoader并没有子类
3.ClassLoader的加载
JVM加载class文件到内存有两种方式:
隐式加载,不通过在代码里调用ClassLoader来加载需要的类,而是通过JVM来自动加载需要的类到内存,例如:当类中继承或者引用某个类时,JVM在解析当前这个类不在内存中时,就会自动将这些类加载到内存中。显示加载,在代码中通过ClassLoader类来加载一个类,例如调用this.getClass.getClassLoader().loadClass()或者Class.forName()。
加载过程:
找到.class文件并把这个文件加载到内存中;字节码验证,Class类数据结构分析,内存分配和符号表的链接;类中静态属性和初始化赋值以及静态代码块的执行。
4.ClassLoader双亲委托模式进行类加载
每一个自定义的ClassLoader都必须继承ClassLoader这个抽象类,父类中有一个getParent()方法,用来返回当前ClassLoader的parent(不是指父类,而是实例化该ClassLoader时制定的一个ClassLoader),如果为null,那默认制定启动类加载器(bootstrap classloader)。这个parent有什么用呢?
我们写自定义的MyClassLoader加载java.lang.String,那么String这个类并不是被MyClassLoader加载,是被bootstrap classLoader进行加载的。这就是双亲委托模式,任何一个自定义的ClassLoader加载一个类之前,它都先委托它的父亲ClassLoader进行加载,如果为null,这个parent默认是bootstrap classLoader,上面的例子结果,最终委托给bootstrap classloader,返回String的Class。
看下面一段源码:
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(var1)) {
Class var4 = this.findLoadedClass(var1);
if(var4 == null) {
long var5 = System.nanoTime();
try {
if(this.parent != null) {
var4 = this.parent.loadClass(var1, false);
} else {
var4 = this.findBootstrapClassOrNull(var1);
}
} catch (ClassNotFoundException var10) {
;
}
if(var4 == null) {
long var7 = System.nanoTime();
var4 = this.findClass(var1);
PerfCounter.getParentDelegationTime().addTime(var7 - var5);
PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
PerfCounter.getFindClasses().increment();
}
}
if(var2) {
this.resolveClass(var4);
}
return var4;
}
}
首先传参数:这个方法第一个参数 类名称 name,第二个参数是 是否连接该类 resolve,方法内部的代码显示出一个类加载的大概过程,我们要实现一个自定义的类的时候,只需实现findClass方法即可。
为什么要使用这种双亲模式呢? 第一避免重复加载类,如果父亲已经加载这个类,就没必要子ClassLoader再加载一次;第二出于安全因素,可以试想,JVM启动的时候会加载核心库api,这个时候我们可以随时自定义String来动态替换库中的String了!!!双亲委托模式会检测String已经加载了,而我们自定义的ClassLoader类是不会加载的。
5.Class类
JVM中每个被ClassLoader加载的Class文件,最终都以Class类的实例被我们引用,我们可以把Class类当成是普通类的一个模板,JVM根据这个模板生成对应的实例。Class里面有个静态方法forname
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
loader = BootClassLoader.getInstance();
}
Class<?> result;
try {
result = classForName(name, initialize, loader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
这个方法和ClassLoader的loadClass目的一样,都是用来加载class的,但是两者作用有所区别。这里还得重复上面的一个知识点:在JVM加载类额时候,需要三个步骤,装载、连接、初始化。装载就是找到相应的Class文件,读入JVM;初始化就不很简单了(都知道一个类的初始化顺序过程);主要讲一下连接。连接分三步,第一步是验证class是否符合规格,第二步是准备,就是为类变量分配内存同时设置默认值,第三步就是解释,而这一步是可选的。这里看一下ClassLoader的两个加载Class的方法
public Class<?> loadClass(String var1) throws ClassNotFoundException {
return this.loadClass(var1, false);
}
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException
是否解释就是根据上边只有两个参数的loadClass方法的第二个参数来设置的,第一个loadClass其实默认调用的是两个参数的loadClass,第二个参数默认false,所以在这里可以看出loadClass加载类实际上就是加载的时候并不对该类做解释,因此也不会初始化该类。而Class类的forName方法相反,使用这个方法加载的时候就会将Class进行解释和初始化。再看一下这个forName方法的参数
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
第一个类名称,第二个是否初始化,第三个类加载器,明白了吧!!!
举个例子: JDBC DRIVER的加载,我们在加载JDBC驱动是通过DriverManager,必须在这个类中注册,如果驱动类没有被初始化是不能被注册的,因此必须使用Class的forName方法,不能用ClassLoader的 loadClass方法!!!over。
总结:我们可以用ClassLoader自定义类加载器,定制我们所需要的加载方式,例如从网络加载(和热更新很想,不过热更新是加载Dex文件的),从其他格式的文件加载等等,都是通过ClassLoader,当然ClassLoader还有很多知识。。。待更新吧!
有新的想法请@我哦,我的邮箱是 liugstick@163.com !!!