类加载器负责将网络或磁盘上的class文件加载到内存中。 生成对应的java.lang.class对象。 当一个类加载到JVM中时,不会重新加载同一类。
怎么上同一个班呢? 在JAVA中,类由完全限定的类名(包名和类名)唯一标识,而在JVM中,类由完全限定的类名和类加载器唯一标识。 也就是说,如果在不同的类加载器中加载JAVA中的同一类,则生成的class对象将视为不同。
JVM启动后,将形成由三个类加载器组成的初始类加载器层次
1、启动类加载器BootstrapClassLoader:
是嵌入在用c语言编写的JVM内核中的装载器,主要装载JAVA_HOME/lib下的类库。 启动加载器不能直接用于APP应用程序。
2、扩展类加载器扩展类加载器:
加载器用JAVA编写,父类的加载器是Bootstrap,主要根据加载JAVA_HOME/lib/ext目录类库的sun.misc.launcher $ ext class loader 开发人员可以使用这些扩展类加载器。
已知由java系统属性java.ext.dirs指定的目录由ExtClassLoader加载程序加载。 如果程序未指定此系统属性(Djava.ext.dirs=sss/lib ),则加载器默认加载$JAVA_HOME/lib/ext
公共类测试
{
publicstaticvoidmain (字符串[ ] args ) {
system.out.println (system.getproperty (' Java.ext.dirs ' ) );
}
}
执行结果:
c :程序文件(x86 ) Javajdk1.6.0_43jrelibext; C:WindowsSunJavalibext
3、系统加载器app类加载器:
系统类加载器(也称为APP应用程序类加载器)将加载位于APP应用程序的classpath目录中的所有jar和class文件。 其父加载器是ext类加载器。
公共类测试
{
publicstaticvoidmain (字符串[ ] args ) {
system.out.println (class loader.getsystemclassloader ();
}
}
执行结果:
sun.misc.launcher $ app class loader @ addb f1
程序中的方法通过返回委托的系统类加载器,执行结果显示系统类加载器是在sun.misc.launcher $ app class loader中实现的。
上述三种加载器的层次关系如下。
注:类加载器的体系不是“继承”体系,而是代理体系。 大多数类加载器首先在自己的parent中查找类或资源。 如果找不到,在自己的本地找。 班级加载器的委托行动动机是为了防止同一班级多次加载。
可以通过程序验证:
publicstaticvoidmain (字符串[ ] args ) {
system.out.println (class loader.getsystemclassloader ().getParent ) );
}
执行结果:
sun.misc.launcher $ ext class loader @ 42e 816
从这里可以看到,应用程序类加载器的父加载器确实是ext类加载器。
我们上了一楼。 如果预期正确,则ext类加载器的父加载器应该是bootstrap类加载器
publicstaticvoidmain (字符串[ ] args ) {
system.out.println (class loader.getsystemclassloader ().getParent ) );
}
执行结果:
空值
这并不是说ExtClassLoader没有父加载器,而是因为Bootstrap ClassLoader是用c编写的。
UML类图:
现在,我们来解读源代码,看看这些类的继承关系
app类加载器和ext类加载器都是Lancher的内部类。
静态类扩展
Loader extends URLClassLoaderstatic class AppClassLoader extends URLClassLoader
public class URLClassLoader extends SecureClassLoader
public class SecureClassLoader extends ClassLoader
public abstract class ClassLoader
我们有这样一个结论,除了启动类加载器Bootstrap ClassLoader,其他的类加载器都是ClassLoader的子类。
我们来反编译看下rt.jar,在sun.misc.Launcher$AppClassLoader路径中,看下AppClassLoader的源码:
static class AppClassLoader extends URLClassLoader{
public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader)
throws IOException{
String str = System.getProperty("java.class.path");
File[] arrayOfFile = (str == null) ? new File[0] : Launcher.access$200(str);
return ((AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader)
{
public Object run(){
URL[] arrayOfURL = (this.val$s == null) ? new URL[0] : Launcher.access$300(this.val$path);
return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
}
}));
}
在第6行代码中可以看到,系统类加载器只能加载java.class.path路径下的class文件。我们通过程序看下java.class.path指定的路径
public class Test
{
public static void main(String[] args){
System.out.println(System.getProperty("java.class.path"));
}
}
执行结果:
如果是JAVA工程:
F:workSpacetestbin
如果是JAVAWEB工程:
F:workSpacestudyWebRootWEB-INFclasses
双亲委派模型
如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。
因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。
双亲委派的实现比较简单,我们来看下源码:
protected synchronized Class> loadClass(String paramString, boolean paramBoolean)
throws ClassNotFoundException
{
//检查是否被加载过
Class localClass = findLoadedClass(paramString);
//如果没有加载,则调用父类加载器
if (localClass == null) {
try {
//父类加载器不为空
if (this.parent != null)
localClass = this.parent.loadClass(paramString, false);
else {
//父类加载器为空,则使用启动类加载器
localClass = findBootstrapClass0(paramString);
}
}
catch (ClassNotFoundException localClassNotFoundException)
{
//如果父类加载失败,则使用自己的findClass方法进行加载
localClass = findClass(paramString);
}
}
if (paramBoolean) {
resolveClass(localClass);
}
return localClass;
}
先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass方法,若父类加载器不存在,则使用启动类加载器。如果父类加载器加载失败,则抛出异常之后看,再调用自己的findClass方法进行加载。
作者:冬瓜蔡
原文:http://www.cnblogs.com/dongguacai/p/5879931.html
始发于微信公众号: Java知音