java类加载流程:
其实是通过ClassLoader(类加载器)去把对应的class文件类型动态加载到JVM中。
其中类加载器有三个,在sun.misc.Launcher这个Java虚拟机的入口应用里,Launcher初始化ExtClassLoader和AppClassLoader。并通过System.getProperty(“sun.boot.class.path”)得到了BootStrapClassLoader。
加载顺序:
BootStrapClassLoader(启动类加载器):通过sun.boot.class.path(可修改加载路径)加载JRE目录下的jar包和class文件。 ExtClassLoader(扩展类加载器):通过java.ext.dirs(可修改加载路径)加载lib\ext。 AppClassLoader(系统类加载器):加载当前java工程目录bin下的class文件。
AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用。
Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。
双亲委托机制:
一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。
委托是从下向上,然后具体查找过程却是自上至下。
重要方法:
loadClass():
执行findLoadedClass(String)去检测这个class是不是已经加载过了。
执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。
双亲委托源代码:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) { // 首先,检测是否已经加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //父加载器不为空则调用父加载器的loadClass c = parent.loadClass(name, false); } else { //父加载器为空则调用Bootstrap Classloader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { long t1 = System.nanoTime(); //父加载器没有找到,则调用findclass c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { //调用resolveClass() resolveClass(c); } return c; } }
不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?
如果要这样做的话,需要我们自定义一个classloader。
步骤:
编写一个类继承自ClassLoader抽象类。
复写它的findClass()方法。
在findClass()方法中调用defineClass()。
findClass()
方法,而不要直接改写
loadClass()
方法。
defineClass():
这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。
Context ClassLoader:线程上下文类加载器
查看Thread.java源码可以发现:
ContextClassLoader只是一个成员变量,通过setContextClassLoader()
方法设置,通过getContextClassLoader()
设置。
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置。