END
首先,我们来简单的回顾下类加载机制中的内容。 类加载机制虚拟机把类的数据从.class文件加载到内存,并对class文件中的数据进行校验、转换、解析、初始化等操作后,最终形成可以被虚拟机识别并使用的Class对象的过程就叫做“虚拟机的类加载”,主要包括为3大阶段。 阶段一:加载 加载,类加载器通过类的全限定名来获取类的二进制字节流,获取的方式可以通过jar包、war包、网络、JSP文件中获取,绝大部分情况下是通过jar包、war包中获取。 获取到字节流后,会将字节流中的信息转化为方法区中的运行时数据结构。在内存中,生成代表该类的Class对象,作为访问该类的数据入口。 阶段二:连接 连接比较复杂,分为3个小阶段: 验证:确保被加载类的正确性,即确保被加载的类符合javac编译的规范,可编译通过的代码。 准备:为类的静态变量分配内存,并初始化为默认值(零值)。 解析:将类中的符号引用转化为直接引用。 阶段三:初始化 为类的静态变量赋值,与连接阶段中的的准备不同。此阶段,代码可debug查看。 如int类型的静态变量static int x = 3,连接阶段赋零值即为0,而初始化阶段赋值即为3。 以上就是类加载机制的三大阶段,而我们今天要将的类加载器存在于阶段一中--加载。可以说,没有类加载器也就没有了后续的流程,类加载器在Java虚拟机中起到了至关重要的作用。 类加载器类加载器(class loader)将Java类从本地磁盘加载到Java虚拟机中,并同时创建了该类的Class对象,实现了“通过一个类的全限定类名来获取此类的二进制字节流”功能。 类加载器是Java语言的一项创新,也是Java语言流程的重要原因之一,在类层次划分、OSGI、热部署、代码加密等领域有着重要的作用,成为Java不可或缺的一部分。 首先,我们来写一个测试类,来看下类加载器,ClassLoaderTest测试类: public class ClassLoaderTest { public static void main(String[] args) { ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (true) { System.out.println(loader); if(loader==null){ break; } loader = loader.getParent(); } } } 运行结果: sun.misc.Launcher$AppClassLoader@41dee0d7sun.misc.Launcher$ExtClassLoader@f7b650anull 首先获取到的是AppClassLoader类加载器,紧接着又获取的是ExtClassLoader类加载器,最后获取的对象为null 为什么为null呢,后续来解答! 接下来,我们来看看在Java体系中到底有哪些类加载器。 类加载的分类在Java中,类加载器可以分为两大类,一类是由Java系统提供的,另外一类是自定义的,由开发人员编写提供的。 系统类加载器: 引导类加载器(bootstrap class loader):用来加载Java的核心库,由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作,不继承自java.lang.ClassLoader(这就是上面例子中为什么最后取到的对象为null的原因)。负责加载 扩展类加载器(extensions class loader):用来加载Java的扩展库,由sun.misc.Launcher$ExtClassLoader来实现。负责加载 系统类加载器(system class loader):用来加载Java应用的类路径(CLASSPATH)的Java类,由sun.misc.Launcher$AppClassLoader来实现。一般来说,Java应用中的类都是由它来完成加载的,可以通过ClassLoader.getSystemClassLoader()来获取。 自定义类加载器: 自定义类加载器(User Custom ClassLoader):开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。在程序运行期间, 通过自定义的java.lang.ClassLoader子类动态加载class文件。 java.lang.ClassLoader类介绍
以上为ClassLoader对于类加载功能的主要方法介绍。 在我们的应用程序中,都是由这4种类加载器互相配合进行加载,这4种类加载器在虚拟机中维护了一种父子关系,这种关系叫做“双亲委派模型”。下面,我们就来看看什么是双亲委派模型。 双亲委派模型下面的图片中,展示的就是“双亲委派模型”,模型中呈现出Java体系架构中的四大类加载器的关系,除了顶层的引导类加载器之外,其余类加载都需要有父加载器存在,但是此子父类关系并不是通过java代码中继承的方式实现。具体如何实现,后面讲解。 知道了类加载器的结构模型,那么该模型在代码整个Java体系中如何工作呢? 工作流程:一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个类加载请求委派给其父类加载器去完成,每一个层的类加载器都是如此,依次向父类加载器传递,最终所有的类加载请求都会传送到顶层的启动类加载器(bootstrap)中,只有当父加载器反馈无法完成这个类加载请求时,子类加载器才会尝试自己去进行类加载操作,如果子类加载器也依旧无法完成,则代码层面就会抛出异常。 此时,你会不会感到疑惑?为啥儿子自己的活不去干,而首先交给他爹去完成呢?这么做的目的何在? 在Java体系中,双亲委派模型保证了类的唯一性,将Java类与它的类加载器绑定到了一起,当父类加载器加载完成后,子类加载器不会再次加载。此外,双亲委派模型还保证了Java框架的安全性。例如:java.lang.Object类,无论是上述哪个类加载器要加载这个类,最终都会委派给模型中的启动类加载器去加载,因此java.lang.Object类在程序中保证了唯一性。 相反,如果没有使用该模型,而是由各个类加载器自行去加载的话,那么系统中就会出现不同的java.lang.Object类,类的唯一性被打破,Java体系中的基本行为就得不到保证。例如:,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。涉及到“类相等”的方法有:Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法以及instanceof对象所属关系判定。 试想一下,如果我们自定义一个java.lang.Object类会怎么样?(其实我们自定义的java.lang.Object类无法在程序中被导入,只能模拟定义java.lang.Object类--java.lang.ObjectTest) 当JVM请求类加载进行自定义的类加载时,双亲委派模型会将请求传递到启动类加载器中,但是启动类加载器默认只加载 为什么,why? 这是因为以java.开头的是核心API包,需要访问权限,强制加载会抛出异常,任何以java.开头的包都会报错: Exception in thread 'main' java.lang.SecurityException: Prohibited package name: java.lang 此异常是代码层面抛出的,并不是native方法虚拟机底层抛出,源码可见(ClassLoader类): if ((name != null) && name.startsWith('java.')) { throw new SecurityException ('Prohibited package name: ' + name.substring(0, name.lastIndexOf('.')));} 此时,你会不会又突发奇想,我自己定义一个类,放在 编写代码,并打成jar包,jar包的名称就叫做 jiaboyan.jar: jar cvf jiaboyan.jar com\jiaboyan\test\ObjectTest.class 接下来,把jiaboyan.jar包放入到 执行main()方法,结果如下: sun.misc.Launcher$AppClassLoader@8fd9b4d 从输出可以看出,放置到 why?不是说了委派给最顶层的类加载进行加载吗?其实,这是由于虚拟机出于安全角度考虑,不会加载 下一篇,将会对类加载器源码进行分析!!! END |
|