当JAVA编译器编译(Javac)为.class文件后,我们需要将class加载到JVM当中。
1、JVM加载类的过程。
当我们执行一个Java程序时。
(1)、java.exe会帮助我们找到JRE,接着找到JRE内部的jvm.dll。这才是真正的Java虚拟机,最后机载动态库,激活Java虚拟机。
(2)、虚拟机激活以后,会先做一些初始化的动作,不如读取系统参数等。一旦初始化动作完成后,就会产生第一个类装载器--Bootstrap Loader(启动类装载器)。
(3)、Bootstrap Loader所做的初始化工作中,除了一些基本的初始化动作外,最重要的就是加载Launcher.java 之中的ExtClassLoader(扩展类装载器),并将其的Parent设为null,代表其父加载器为BootstrapLoader。
(4)、然后Bootstrap Loader 再要求加载 Launcher.java之中的 AppClassLoader(用户自定义类装载器),并设定其Parent为之前产生的ExtClassLoader实体。这两个加载器都是以静态类的形式存在的。
Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载无关。
2、类加载器体系结构
JVM加载class文件必须通过一个叫做类装载器的程序。
(1)、Bootstrap ClassLoader :启动类加载器,负责加载存放在%JAVA_HOME%\lib目录中的,并且被java虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库,即使放在制定路径中也不会被加载)类库到虚拟机 的内存中,启动类加载器无法被java程序直接引用。
(2)、Extension ClassLoader:扩展类加载器。由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的类库,开发者可以直接使用扩展类加载器。
(3)、Application ClassLoader:应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者可以直接使用此加载器。如果程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。
注:上述三个JDK提供的类加载器虽然是父子类加载器关系,但是没有使用继承,而是使用了组合关系。
3、双亲委派模式(ParentsDelegation Model) (双亲并不是两个 。而是父类们。。)
用户自定义类装载器经常依赖其他类装载器--至少依赖虚拟机启动时创建的启动装载器来帮助他实现一些类装载请求。
在版本1.2之后,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式。
除启动类装载器之外的每一个类装载器,都有一个'双亲'类装载器,在某个特定的类装载器试图已常用方式装载类型以前,它会先默认的将其分配给它的双亲-请求它的双亲来装载这个类型。这个双亲再一次请求他的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器。
package test; import java.net.URL; import java.net.URLClassLoader; public class ClassLoaderTest { private static int count = -1; public static void testClassLoader(Object obj) { if (count < 0 && obj == null) { System.out.println("Input object is NULL"; return; } ClassLoader cl = null; if (obj != null && !(obj instanceof ClassLoader)) { cl = obj.getClass().getClassLoader(); } else if (obj != null) { cl = (ClassLoader) obj; } count++; String parent = ""; for (int i = 0; i < count; i++) { parent += "Parent "; } if (cl != null) { System.out.println( parent + "ClassLoader name = " + cl.getClass().getName()); testClassLoader(cl.getParent()); } else { System.out.println( parent + "ClassLoader name = BootstrapClassLoader"; count = -1; } } public static void main(String[] args) { URL[] urls = new URL[1]; URLClassLoader urlLoader = new URLClassLoader(urls); ClassLoaderTest.testClassLoader(urlLoader); } }
以上例程的输出为:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
4、由于是找到最上层的加载器来加载所以当类重复时会不会加载我们自定义的类的。
package java.lang; public class String { public static void main(String[] args){ } }
java.lang.NoSuchMethodError: main
Exception in thread "main"
结论:我们当然可以自定义一个和API完全一样的类,但是由于双亲委托模型,使得我们不可能加载上我们自定义的这样一个类。所以J2SE规范中希望我们自定义的包有自己唯一的特色(网络域名)。还有一点,这种加载器原理使得JVM更加安全的运行程序,