一个Java程序是由很多类组成的,在程序中需要使用到某个类的时候,必须先把它加载到JVM内存中。
什么时候算是需要用到某个类:
使用java.exe工具启动某个Java类需要创建该类的对象访问类中的静态变量、静态方法使用Class.forName()获取某个类的类镜像类本身是一种比较抽象的概念,在Java程序声明周期各个阶段,有不同的存在形式。
加载之前:就是磁盘中的一个.class字节码文件。.class文件就是.java源文件经过编译生成的结果。
加载之后:变成JVM内存中一个Class类型的对象。
Class是什么?
类:描述现实生活中一组具有相同特点的事物 所有的Java类本身是不是一种具有相同特点的事物? 所有的类都有修饰符、类名、包名、变量、方法、构造器… 也可以用一个Java类来描述“所有的Java类”这一类事物。
类加载:读取一个.class字节码文件中保存的十六进制的类的描述信息,根据这些信息在JVM内存中对应地创建一个Class对象。一个Class对象就代表Java中的一个类。
只有在内存中存在一个类对应的Class对象,在程序中才能对这个类进行各种操作。
类加载器:类加载过程的执行者。
Java中的类加载器一共有三种:
类加载器名称加载路径说明BootstrapClassLoader启动类加载器/引导类加载器%JAVA_HOME%\jre\lib负责加载系统中的核心类库,使用C++编写,本身就是JVM的一部分,用户不能直接调用。ExtClassLoader扩展类加载器%JAVA_HOME%\jre\lib\ext负责加载系统中的扩展类,用Java编写的。AppClassLoader本地类加载器/应用类加载器%CLASSPATH%负责加载用户自己编写的Java类,用Java编写的。上述的三种类加载器虽然加载的内容不同,但是并非完全没有关系。
【启动类加载器】是【扩展类加载器】的父加载器。
【扩展类加载器】是【本地类加载器】的父加载器。
每种加载器都有自己的缓存区,保存已经加载过的类信息。如果某个类已经被加载过,那么一定能够在缓存区中找到对应的记录。直接取出使用即可,不需要重复加载。
假设,现在有一个自己编写的Java类HelloWorld需要被加载。
首先【本地类加载器】会先接收到加载请求。
但是它不会立即执行加载,它会先查看自己的缓存区,看之前是否加载过该类。
如果找到了加载的记录,就可以直接拿出来使用,整个加载过程结束。
如果没有找到加载记录,说明本加载器从来没有加载过这个类。这时候,会向上委派。
向上委派指的是把加载请求委托给了父加载器【扩展类加载器】。
【扩展类加载器】接收到委托,也不会立即执行加载。而是先查看自己的缓存区,如果之前已经加载过该类则直接返回,类加载成功。如果自己的缓存中没有,会把加载请求继续向上委派。
【启动类加载器】接收到委托,也不会立即执行加载,也是先查看自己的缓存区。如果有,则直接返回。
如果启动类加载器的缓存区中都没有找到加载记录,那就说明整个系统是第一次加载这个类。
向上委派的整个过程其实就是在逐级查询缓存区,判断是否有任何一个类加载器加载过现在要加载的类。如果找到就直接返回使用,避免重复加载。如果启动类加载器缓存区中不存在类信息,则向上委派失败。开始执行向下加载过程。
顺序和向上委派的过程是相反的。
从上到下尝试加载,谁能加载,就由谁加载。
尝试加载:查找自己的类加载路径下是否有HelloWorld.class文件。
首先,【启动类加载器】会先查看自己的类加载路径中是否存在保存有该类描述信息的字节码文件(HelloWorld.class),如果有则执行加载并将该类信息保存到缓存区中,类加载结束(成功)。
如果在自己的类加载路径下没有找到指定的字节码,则向下通知子加载器【扩展类加载器】。
【扩展类加载器】收到通知,会查看自己的类加载路径中是否存在该字节码,如果有则直接执行加载并将结果放入缓存区,类加载结束(成功)。
如果没有,则继续向下通知子加载器【本地类加载器】。
这时候【本地类加载器】才会自己尝试执行加载。它会到%CLASSPATH%下查找是否有对应的字节码文件,如果有则执行加载并将结果放入缓存,类加载结束(成功)。
如果执行完了向上委派和向下加载完整过程,最后在本地类加载器这里都没有找到对应的字节码文件,就代表整个类加载失败。抛出异常:ClassNotFoundException
思考:实际上,Java程序中大部分自定义的Java类最终还是要通过AppClassLoader执行加载,那何必再费这么大劲折腾一趟?
双亲委托机制主要目的是防止一个类被重复加载,主要指的是系统级别的类。主要是为了Java的安全性考虑。
假设一个场景:java.lang.System属于系统级别的类,本身会被BootStrapClassLoader加载。如果现在我们自己编写一个包叫做java.lang,在这个包下定义一个类叫做System(这样的做法不违反Java的语法)。如果让AppClassLoader重复加载自己写的java.lang.System,可能会存在系统中一个类有两种不同的版本,产生一些混乱。或者后者会把前者覆盖掉,篡改了核心Java类库中的部分功能。动摇到了Java语言的根基。
如果有一些别有用心的人,借助这一机制编写一些病毒代码植入到我们的应用程序中代替系统级别类库,就可能会对程序甚至是操作系统产生毁灭性的打击。