JAVA启动后,是通过JVM各级ClassLoader来加载各个类到内存。为了更加了解加载过程,我经过分析和写了一个简单的ClassLoader来粗浅的分析它的原理。java
JVM的ClassLoader分三层,分别为Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他们不是类继承的父子关系,是逻辑上的上下级关系。mysql
Bootstrap ClassLoader是启动类加载器,它是用C++编写的,从%jre%/lib目录中加载类,或者运行时用-Xbootclasspath指定目录来加载。sql
Extension ClassLoader是扩展类加载器,从%jre%/lib/ext目录加载类,或者运行时用-Djava.ext.dirs制定目录来加载。编程
System ClassLoader,系统类加载器,它会从系统环境变量配置的classpath来查找路径,环境变量里的.表示当前目录,是经过运行时-classpath或-Djava.class.path指定的目录来加载类。数组
通常自定义的Class Loader能够从java.lang.ClassLoader继承,不一样classloader加载相同的类,他们在内存也不是相等的,即它们不能互相转换,会直接抛异常。java.lang.ClassLoader的核心加载方法是loadClass方法,如:缓存
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
经过上面加载过程,咱们能知道JVM默认是双亲委托加载机制,即首先判断缓存是否有已加载的类,若是缓存没有,但存在父加载器,则让父加载器加载,若是不存在父加载器,则让Bootstrap ClassLoader去加载,若是父类加载失败,则调用本地的findClass方法去加载。tomcat
双亲委托机制的做用是防止系统jar包被本地替换,由于查找方法过程都是从最底层开始查找。 所以,通常咱们自定义的classloader都须要采用这种机制,咱们只须要继承java.lang.ClassLoader实现findclass便可,若是须要更多控制,自定义的classloader就须要重写loadClass方法了,好比tomcat的加载过程,这个比较复杂,能够经过其余文档资料查看相关介绍。app
各个ClassLoader加载相同的类后,他们是不互等的,这个当涉及多个ClassLoader,而且有经过当前线程上线文获取ClassLoader后转换特别须要注意,能够经过线程的setContextClassLoader设置一个ClassLoader线程上下文,而后再经过Thread.currentThread().getContextClassLoader()获取当前线程保存的Classloader。可是自定义的类文件,放到Bootstrap ClassLoader加载目录,是不会被Bootstrap ClassLoader加载的,由于做为启动类加载器,它不会加载本身不熟悉的jar包的,而且类文件必须打包成jar包放到加载器加载的根目录,才可能被扩展类加载器所加载。jvm
Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的关系以下:this
Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。
可是这并非继承关系,只是语义上的定义,基本上,每个ClassLoader实现,都有一个Parent ClassLoader。
能够经过ClassLoader的getParent方法获得当前ClassLoader的parent。Bootstrap ClassLoader比较特殊,由于它不是java class因此Extension ClassLoader的getParent方法返回的是NULL。
因为一些特殊的需求,咱们可能须要定制ClassLoader的加载行为,这时候就须要自定义ClassLoader了.
自定义ClassLoader须要继承ClassLoader抽象类,重写findClass方法,这个方法定义了ClassLoader查找class的方式。
主要能够扩展的方法有:
findClass 定义查找Class的方式
defineClass 将类文件字节码加载为jvm中的class
findResource 定义查找资源的方式
现已有的ClassLoader实现有以下几种:
java.net.URLClassLoader
java.security.SecureClassLoader
java.rmi.server.RMIClassLoader
sun.applet.AppletClassLoader
Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子类。
这个是URLClassLoader的构造方法:
public URLClassLoader(URL[] urls, ClassLoader parent) public URLClassLoader(URL[] urls)
urls参数是须要加载的ClassPath url数组,能够指定parent ClassLoader,不指定的话默认以当前调用类的ClassLoader为parent。
因为classloader 加载类用的是全盘负责委托机制。所谓全盘负责,便是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的全部 Class也由这个classloader负责载入,除非是显式的使用另一个classloader载入。
因此,当咱们自定义的classloader加载成功了com.company.MyClass之后,MyClass里全部依赖的class都由这个classLoader来加载完成。
Class.forName() 与 load.loadClass()的区别:
Class.forName("xx.xx")等同于Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第二个参数(bool)表示装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量。
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也能够用(ClassLoader.getSystemClassLoader())
Class cls = loader.loadClass("xx.xx"); //这句话没有执行初始化,其实与Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")执行的是更底层的操做。
只有执行cls.NewInstance()才可以初始化类,获得该类的一个实例
Class的装载分了三个阶段,loading,linking和initializing,分别定义在The Java Language Specification的12.2,12.3和12.4。
Class.forName(className) 其实是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是否是必须被初始化。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。
区别就出来了。Class.forName(className)装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class尚未被link。
forName支持数组类型,loadClass不支持数组
通常状况下,这两个方法效果同样,都能装载Class。但若是程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),若是换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
为何呢?打开com.mysql.jdbc.Driver的源代码看看,
// // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
原来,Driver在static块中会注册本身到java.sql.DriverManager。而static块就是在Class的初始化中被执行。因此这个地方就只能用Class.forName(className)。