类加载:当程序中需要使用到某个类的时候(无论是Java API中自带的类,还是我们自己编写的Java类),
必须要先把这个类加载到内存中。
其实是把保存有这个类描述信息的文件(.class)装载到JVM中。
字节码文件中的所有信息会被解析读取出来,
变成JVM内存中的一个Class类的对象。
ClassLoader类加载器负责到指定的路径中查询.class文件,
并且加载到内存中。
Java中有三种类加载器:
1)BootStrapClassLoader 引导类加载器/启动类加载器(grandpa)
主要负责加载系统中的核心类库。
类加载路径:%JAVA_HOME%\jre\lib
启动类加载器用C++编写的,用户是没有办法直接调用的。
2)ExtClassLoader 扩展类加载器 (father)
主要负责加载系统中的扩展类。
类加载路径:%JAVA_HOME%\jre\lib\ext
扩展类加载器是用Java语言编写的。
3)AppClassLoader 应用类加载器/本地类加载器 (son)
主要负责加载本地类,也就是我们自己编写的Java类。
加载路径:%CLASSPATH%
应用类加载器是使用Java语言编写的。
程序中现在出现了一个加载任务(需要用某个类了),
到底是由哪个类加载器来完成这次的加载任务?
类加载机制:【双亲委托机制/双亲委派机制】
1.三个加载之间的关系
【启动类加载器】是【扩展类加载器】的父加载器。
【扩展类加载器】是【本地类加载器】的父加载器。
2.某个类需要被加载的过程
一个类什么时候需要被加载?
直接用java指令运行某个类,在程序中声明某个类的变量、
创建某个类的对象、访问某个类中的静态变量/方法…
加载任务产生之后,首先会通知【本地类加载器】。
所有的类加载器都有自己的缓存空间。
缓存空间中保存的就是自己已经加载过的一些类的信息。
当某个类加载器接收到加载任务通知时,
都会先到自己的缓存空间中查找之前是否加载过这个类。
如果缓存中存在,则直接返回使用,这一次加载任务结束。
如果缓存中不存在,那么它并不会自己直接尝试加载,
而是向父级加载器进行委派。
3.例如,现在有真实的一个类HelloWorld需要进行加载
首先,【本地类加载器】接收到加载的请求。
【本地类加载器】先查询自己的缓存空间,查看该类是否被加载过(是否存在被加载过的结果 Class对象)。
.class --> Class对象
如果有,则直接拿来使用。
如果没有,不会自己尝试加载,委派给父级加载器【扩展类加载器】。
【扩展类加载器】先查询自己的缓存空间,查看自己是否加载过该类。
如果有则直接返回给son【本地类加载器】去使用。
如果没有,则继续向上委派,委派给grandpa【启动类加载器】。
【启动类加载器】先查询自己的缓存空间,查看自己是否加载过该类。
如果有则直接返回并使用。
问题:如果启动类加载器也没有加载过这个类则怎么办?
到此,向上委派的过程结束。因为启动类加载器已经是最高级别的加载器了,它没有父亲了。
【注意】向上委派的过程只是让三个加载器去尝试着从自己的缓存空间中查找要加载的这个类,之前是否加载过。因为如果有的话,就可以直接拿来用了,就不需要再重复地加载了。
这个过程当中就算都没有找到加载过的记录,也没有关系。
向上委派结束之后,还有一个向下加载的过程。
4、向下加载的过程
按照从上到下的顺序,依次到自己的类加载路径中查询是否存在指定的类信息(.class文件)。如果找到,则执行加载并返回。如果没找到,则通知下级加载器让它自己尝试解决。
grandpa【启动类加载器】先尝试加载。
它会到自己的类加载路径(%JAVA_HOME%\jre\lib)中查找是否存在指定的HelloWorld.class文件。
如果有,则执行加载,并且返回。
如果没有,告诉【扩展类加载器】:
我这里没有那个类的信息,你自己想想办法。
【扩展类加载器】也会先到自己的类加载路径(%JAVA_HOME%\jre\lib\ext)中查询是否存在指定的HelloWorld.class文件。
如果有,则加载这个类,并且返回。
如果没有,告诉【本地类加载器】我这儿和你爷爷那儿都没有你要的类信息,你自己去找找吧。
【本地类加载器】就会到自己的类加载路径(%CLASSPATH%、执行指令的时候临时添加的-cp、-classpath所指定的路径)中查找是否存在这个HelloWorld.class。
如果存在,则执行加载。可以在程序中使用这个类了。
如果不存在,则抛出异常ClassNotFoundException。
找不到或无法加载该类,类加载失败。