【JVM系列】Java类加载器
一、默认类加载器Java虚拟机中系统默认的类加载器有三个:BootStrap,ExtClassLoader,AppClassLoaderJava类都是被类加载器加载的,ExtClassLoader,AppClassLoader也是Java类,显然它们也需要被加载,所以必然有一个类加载器不是Java类,并且由它来加载ExtClassLoader,AppClassLoader这两个Java类加载器,这
一、默认类加载器
Java虚拟机中系统默认的类加载器有三个:BootStrap,ExtClassLoader,AppClassLoader
Java类都是被类加载器加载的,ExtClassLoader,AppClassLoader也是Java类,显然它们也需要被加载,所以必然有一个类加载器不是Java类,并且由它来加载ExtClassLoader,AppClassLoader这两个Java类加载器,这个类加载器就是BootStrap,BootStrap是使用C/C++代码写的,已经封装到JVM内核中了,当Java虚拟机启动的时候这个类就会被加载,然后BootStrap会加载ExtClassLoader,并将它的parent赋值为null,表示它的父类加载器是BootStrap,接着加载AppClassLoader,并将它的父类加载器赋值为ExtClassLoader,表示它的父类加载器是ExtClassLoader。
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader cl = ClassLoaderTest.class.getClassLoader();
while(cl != null){
System.out.print(cl.getClass().getName()+"->");
cl = cl.getParent();
}
System.out.println(cl);
}
}
sun.misc.Launcher$AppClassLoader->sun.misc.Launcher$ExtClassLoader->null
从上面的执行结果可以很清楚的看出它们之间的关系。null表示加载器为BootStrap,因为它不是Java类,所以获取不到这个ClassLoader,BootStrap的子加载器为ExtClassLoader,ExtClassLoader的子加载器为AppClassLoader,另外,ExtClassLoader和AppClassLoader都是sun.misc.Launcher的内部类。
为什么它有三个默认的加载器,第一个BootStrap肯定是必须的,因为Java类必须被类加载器加载,那么肯定有一个不是Java类的加载器祖先来加载Java类,ExtClassLoader和AppClassLoader都是Java类,它们被定义主要是因为Java类分为很多类别,不同的类别由不同的加载器来加载,也就是为ExtClassLoader和AppClassLoader它们有自己的管辖加载范围。
二、类加载器的委托机制
当Java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?
(1). 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader cl);方法,可以获取/指定本线程中的类加载器)
(2). 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
(3). 还可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出ClassNotFoundException
下面我们来具体看看每个加载器的加载范围和委托过程。
如上图所示: MyClassLoader->AppClassLoader->Ext->ClassLoader->BootStrap.自定定义的MyClassLoader首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的MyClassLoader1类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。
三,自定义类加载器和加载过程
自定义类加载器必须继承自ClassLoader类。
在ClassLoader的构造方法中可以指定parent,没有指定的话,就使用默认的parent
protected ClassLoader()
protected ClassLoader(ClassLoader parent)
默认的parent是使用ClassLoader.getSystemClassLoader()来获的,它实际就是AppClassLoader。
也就是说不管我们使用ClassLoader.loadClass还是我们自定义的MyClassLoader.loadClass来加载类,它默认的父加载类都是AppClassLoader。
下面我们来定义一个MyClassLoader,使用MyClassLoader.loadClass来加载指定的类,我们来看看loadClass源码,看看它是执行过程。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
//加上锁,同步处理,因为可能是多线程在加载类
synchronized (getClassLoadingLock(name)) {
//检查,是否该类已经加载过了,如果加载过了,就不加载了
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果自定义的类加载器的parent不为null,就调用parent的loadClass进行加载类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果自定义的类加载器的parent为null,就调用findBootstrapClass方法查找类,就是Bootstrap类加载器
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.
long t1 = System.nanoTime();
//如果parent加载类失败,就调用自己的findClass方法进行类加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
可以看到,如果parent加载类失败,就调用自己的findClass方法进行类加载,下面我们来看看findClass方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看到它就抛出了一个异常,如果我们希望使用这个类来进行加载,这个方法需要我们进行重写,它的作用就是查找指定位置的class文件,用来进行加载。
找个这个class文件之后,就需要对这个文件进行加载了,使用的是defineClass方法,返回类型为Class。它是一个native方法,不需要重写。
从上面可以看到,这三个方法的执行流程是:loadClass->findClass->defineClass,我们只需要重写findClass方法来查找指定位置的class文件,其他两个方法不需要重写。
下面我们来举个例子:
public class TestDemo {
public static void print() {
System.out.println("Hello World");
}
}
它是被加载的类,它的class文件位于目录:/Users/Documents/workspace/ClassLoaderTest/bin下面。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader {
//需要加载类.class文件的目录
private String classDir;
//无参的构造方法,用于class.newInstance()构造对象使用
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
//class文件的路径
String classPathFile = classDir + "/" + name + ".class";
try {
FileInputStream fis = new FileInputStream(classPathFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] classByte = bos.toByteArray();
//将字节流变成一个class
return defineClass(classByte,0,classByte.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
这个是我们自定义的ClassLoader,用来加载TestDemo类。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader("/Users/Documents/workspace/ClassLoaderTest/bin");
try {
Class testDemo = classLoader.loadClass("TestDemo");
Method method = testDemo.getMethod("print", null);
method.invoke(null, null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这个类中,我们使用自定义的ClassLoader来加载TestDemo类,并且执行print方法。
三、使用ClassLoader的原因
先说说java中import的作用,import中所引用的类文件有两个特点:
(1)必须存在于本地,当程序运行时如果需要某个类,内部类装载器会自动装载该类。
(2)编译时必须存在,否则会因为找不到引用文件而不能正常编译。
但是有时,我们所需要的类不能满足上面两种条件,例如我们需要动态加载某个类,这个类是从远程服务器上下载下来的,它不参与编译,而是运行时动态调用的,这是我们就需要使用ClassLoader来动态的进行加载。
import是静态导入之后,就可以直接使用某个类,ClassLoader是动态加载某个类之后,然后就可以对该类进行实例化并且使用该类。
一般情况,应用程序不需要创建一个全新的ClassLoader对象,而是使用当前环境已经存在的ClassLoader,因为Java的Runtime环境初始化时,其内部会创建一个ClassLoader对象用来加载Runtime所需的各种Java类。
更多推荐
所有评论(0)