类加载过程
类的加载过程可以看一下这篇文章JVM超详细解析
类加载器
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如
rt.jar、charsets.jar等。 - 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR
类包。 - 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那
些类。 - 自定义加载器:负责加载用户自定义路径下的类包。
可以通过 getClassLoader() 方法去获取类是由什么加载器加载的。例如
public class LoadClassTest {
public static void main(String[] args) {
//运行结果为AppClassLoader
System.out.println(LoadClassTest.class.getClassLoader().getClass().getSimpleName());
}
}
注意:引导加载器为null,因为引导加载器是c++实现。
应用程序类加载器和扩展类加载器的代码均在sun.misc.Launcher类中,结构如下:
双亲委派机制
从应用程序类加载器的loadClass方法开始,先查找自己是否已加载该类,若已加载则直接返回,若未加载则依次委托父加载器进行加载,若引导加载器加载失败则交给子加载器进行加载。
首先通过调用sun.misc.Launcher内部类AppClassLoader的 loadClass(String name, boolean resolve) 方法进行加载
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
if (ucp.knownToNotExist(name)) {
// The class of the given name is not found in the parent
// class loader as well as its local URLClassPath.
// Check if this class has already been defined dynamically;
// if so, return the loaded class; otherwise, skip the parent
// delegation and findClass.
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
return (super.loadClass(name, resolve));
}
在该方法的最后部调用了父方法loadClass(name, resolve),最后调用的是java.lang.ClassLoader的方法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//这里先进行查找看是否调加载过该类,若加载过就直接返回该类的class
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
//调用自己实现的加载器
//AppClassLoader和ExtClassLoader调用的是java.net.URLClassLoader中的findClass(final String var1)方法
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;
}
}
注意:加载器之间并没有继承关系是通过java.lang.ClassLoader的一个parent属性进行关联。
AppClassLoader与ExtClassLoader的父加载器是在sun.misc.Launcher构造时添加的。AppClassLoader的父类加载器是ExtClassLoader,ExtClassLoader的父类加载器为null(在加载的方法中判断父类加载器是否为空,若为空则直接调用引导类加载器进行加载所以ExtClassLoader加载器传的父加载器为null)。
最后都是通过调用父类构造方法进行设置父类加载器
双亲委派机制的好处:
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
API库被随意篡改。 - 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证被加载类的唯一性。
自定义加载器
需要加载的类
public class LoadClassTest {
public static void main(String[] args) {
System.out.println(LoadClassTest.class.getClassLoader().getClass().getSimpleName());
}
public void test(){
System.out.println("调用test()方法成功!");
}
}
自定义加载器
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
public class ClassLoadTest extends ClassLoader {
private String path;
public ClassLoadTest(String path) {
this.path = path;
}
@Override
public Class<?> findClass(String name){
try (FileInputStream fileInputStream = new FileInputStream(path);) {
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
return defineClass(name, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
ClassLoadTest load = new ClassLoadTest("D:/test/LoadClassTest.class");
Class<?> clazz = load.loadClass("LoadClassTest");
Method method = clazz.getDeclaredMethod("test", null);
method.invoke(clazz.newInstance(),null);
System.out.println("加载器为========>"+clazz.getClassLoader().getClass().getSimpleName());
}
}
结果
注意:因为在我的目录下也存在LoadClassTest类,因此根据双亲委派机制会调用父加载器进行加载,自定义加载器的父类加载器为AppClassLoader,所以该加载器为AppClassLoader,若想使用自己的加载器就需要移除该目录下的LoadClassTest类。
移除LoadClassTest类后调用结果。
小提示:为什么自定义的父类加载器是AppClassLoader?
自定义的加载器会调用java.lang.ClassLoader的默认方法。
打破双亲委派机制
双亲委派机制的主要逻辑在java.lang.ClassLoader中的 loadClass(String name, boolean resolve)的方法中体现,该类提供了下面方法可以重写,通过重写该方法就可以打破双亲委派机制,这里就不在展示代码。
注:上述分析代码来自jdk1.8版本。