赞
踩
与那些在编译时需要进行连接工作的语言不同,在Java语言里面,类型的加载,连接和初始化过程都是在程序运行期间完成的。
这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性。
Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。
ClassLoader
顾名思义就是类加载器,负责将Class加载到JVM中。其三个作用作用:
1. 将Class加载到JVM中
2. 审查每个类应该由谁加载,它是一种父优先的等级加载机制。
3. 将Class字节码重新解析成JVM统一要求的对象格式。
Bootstrap ClassLoader
System.getProperty("sun.boot.class.path")
目录下。Bootstrap ClassLoader
并不属于JVM的类等级层次,因为Bootstrap ClassLoader
没有遵守ClassLoader
的加载规则。另外Bootstrap ClassLoader
并没有子类。ExtClassLoader(sun.misc.Launcher$ExtClassLoader)
System.getProperty("java.ext.dirs")
目录下。ExtClassLoader
的父类也不是Bootstrap ClassLoader
。ExtClassLoader
并没有父类,我们在应用中能够提取到的顶级父类就是ExtClassLoader
。AppClassLoader(sun.misc.Launcher$AppClassLoader)
System.getProperty("java.class.path")
目录下。即classpath路径下的类包。sun.misc.Launcher
ExtClassLoader
和AppClassLoader
都是sun.misc.Launcher
的内部类,且都继承自URLClassLoader
在创建Launcher
对象时,
ExtClassLoader
,ExtClassLoader
对象作为父加载器创建AppClassLoader
对象。Launcher.getClassLoader
获取到的ClassLoader
就是AppClassLoader
对象,所以如果在Java应用中没有定义其他ClassLoader
,那么除了System.getProperty("java.ext.dirs")
目录下的类是由ExtClassLoader
加载外,其他类都是由AppClassLoader
来加载。一般以类加载器需要加载一个class或者资源文件的时候,他会先委托给他的parent类加载器,让parent类加载器先来加载,如果没有,才再在自己的路径上加载。这就是人们常说的双亲委托,即把类加载的请求委托给parent。
在写这篇文章前,有个问题困扰了我很长时间,那就是:两种加载Class方法中,显式加载自不必说,问题是隐式加载某个Class时,JVM是如何选择进行加载Class的ClassLoader的?
参见如下样例代码:
public class CustomCls2 {
public void say() {
System.out.println(this.getClass().getClassLoader());
}
}
public class CustomCls1 {
public void say() {
System.out.println("加载CustomCls1的ClassLoader: ");
System.out.println(this.getClass().getClassLoader());
System.out.println("加载CustomCls2的ClassLoader: ");
new CustomCls2().say();
}
}
public class CustomClassLoader extends URLClassLoader {
private static final String PATH = "E:\\gitRepository\\_javaDir\\javaCode\\_BasicLearing\\bin\\";
public CustomClassLoader() {
super(new URL[] {});
}
/**
* @param urls
*/
public CustomClassLoader(URL[] urls) {
super(urls);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] loadClassData = loadClassData(name);
return defineClass(name, loadClassData, 0, loadClassData.length);
}
private byte[] loadClassData(String name) {
name = name.replace(".", "/");
try {
FileInputStream iStream = new FileInputStream(new File(PATH + name + ".class"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while ((b = iStream.read()) != -1) {
baos.write(b);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}
// 单元测试
@Test
public void customClassLoader() throws Exception {
CustomClassLoader loader = new CustomClassLoader();
Class<?> loadClass = loader.loadClass("__java.classloader_.CustomCls1");
CustomCls1 newInstance = (CustomCls1)loadClass.newInstance();
newInstance.say();
/* 输出结果
加载CustomCls1的ClassLoader:
sun.misc.Launcher$AppClassLoader@1fe9999
加载CustomCls2的ClassLoader:
sun.misc.Launcher$AppClassLoader@1fe9999
*/
}
由上面的示例代码可以得出:
1. 隐式加载的Class,其对应的ClassLoader正常情况下是隐式引入该Class的那个Class对应的ClassLoader。即加载调用者的那个ClassLoader。在上例中就是 加载 CustomCls2 的ClassLoader就是加载 CustomCls1的那个ClassLoader。
2. 其实以上这段逻辑在Java显式加载Class中已有暗示。具体代码就是Class.forName("xxx")
,在其源码中可以看到,虽然getClassLoader0()
是个native方法。但是我们大概可以肯定上一条逻辑。
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
// 获取调用本方法的那个类
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。