java中一个类是如何被虚拟机加载的呢?下面我们来介绍一下类加载器和双亲委派机制。
"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作是在JVM外部来实现的,以便让应用程序自己决定如何获取所需的类.实现这个动作的代码模块就称为类加载器.
ClassLoader只负责加载class文件,class文件在文件的开头都有特定的文件标识,至于它是否可以运行,则由Execution Engine决定.
类加载器分为虚拟机自带的加载器(前3种)和用户自定义的加载器(第4种)
1.启动类加载器(BootstrapClassLoader)
负责加载%JAVA_HOME%\lib下的rt.jar、charsets.jar和class等,或者是-Xbootclasspath参数指定的路径.根类加载器可以理解成一个概念上的东西,因为我们无法通过Java代码获取根类加载器,它属于JVM层面.
2.扩展类加载器(ExtClassLoader)
该类是sun.misc.Launcher的一个内部类.负责加载%JAVA_HOME%\lib\ext目录下的所有jar包和class文件,或者是java.ext.dirs参数指定的路径.
3.应用程序类加载器(AppClassLoader)
该类是sun.misc.Launcher的一个内部类.负责加载用户类路径上所指定的类库(主要负责加载应用程序的主函数类),如果应用程序中没有自定义加载器,那么类加载器就为默认加载器.
4.用户自定义的加载器
需要继承ClassLoader抽象类
说明:
扩展类加载器和应用类加载器都是通过类sun.misc.Launcher进行初始化,而Launcher类则由根类加载器进行加载.
虚拟机是根据类的全限定名来加载类的,那么有个问题,如果同时存在两个或多个全限定名完全一致的情况下.如何选择加载哪个类.这就是双亲委派机制要做的工作.
双亲委派模型是指当我们调用类加载器的loadClass()进行类加载时,该类加载器会首先请求它的父类加载器进行加载,依次递归.如果所有父类加载器都加载失败,则当前类加载器自己进行加载操作.
1-类加载器收到类加载的请求;
2-把这个请求委托给父加载器去完成,一直向上委托直到启动类加载器;
3-启动器加载器检查能不能加载(使用findClass()方法),能就加载(结束);否则,抛出异常,通知子加载器进行加载.
4-重复步骤三.
说明:
当一个HelloWorld.class文件被加载时.不考虑自定义类加载器,首先会在AppClassLoader中检查是否已加载过,如果有就无需再加载.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass().父类中同样会先检查自己是否已经加载过,如果没有再往上.直到到达BootstrapClassLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.
1:安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String
2:避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)
这种设计有个好处是,如果有人想替换系统级别的类:String.在双亲委派机制下这些系统类已经被Bootstrap classLoader加载过了,不会再去加载,从一定程度上防止了危险代码的植入.
注意:
1.ClassLoader类是一个抽象类,但没有包含任何抽象方法.
2.如果要实现自己的类加载器且不破坏双亲委派模型,只需继承ClassLoader类并重写findClass().
3.如果要实现自己的类加载器且破坏双亲委派模型,则需继承ClassLoader类并重写loadClass()、findClass().