JVM(二) 类加载器介绍及双亲委派模型解释

什么是类加载器:
image.png
在类“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”被称为“类加载器”(Class Loader),这个动作是可以自定义实现的;

JVM有哪些类加载器:
1、启动类加载器(Bootstrap ClassLoader),使用C++语言实现,是虚拟机自身的一部分;
2、其他所有的类加载器,由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader;

站在Java开发者的角度来看,自JDK 1.2开始,Java一直保持着三层类加载器架构;
image.png
image.png

1、启动类加载器(Bootstrap ClassLoader):
(根的类加载器)C++语言实现的<JAVA_HOME>\jre\lib\rt.jar,resources.jar、charsets.jar被-Xbootclasspath参数所指定的路径中存放的类库;

2、扩展类加载器(Extension ClassLoader):
sun.misc.Launcher$ExtClassLoader,<JAVA_HOME>\jre\lib\ext,被java.ext.dirs系统变量所指定的路径中所有的类库;

3、应用程序类加载器(Application ClassLoader):
系统的类加载器sun.misc.Launcher$AppClassLoader加载用户类路径(ClassPath)上所有的类库;

注意:上面解释的类加载器之间没有继承关系,ext和app 类加载器都是Launcher的内部类
image.png

双亲委派模型:
image.png
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载;比如jdk的string类就会被Bootstrap加载器加载,我们自己定义的类就会被AppClassLoad加载。

JDK为什么要设计双亲委派模型,有什么好处?
1、确保安全,避免Java核心类库被修改;
2、避免重复加载;
3、保证类的唯一性;

如果你写一个jaa.lang.String的类去运行,发现会抛出如下异常;
image.png

关于双亲委派的在jdk源码中的体现:
我们随便定义一个类,写一个main方法即可,然后在AppClassLoader的loadClass方法处打上断点即可,运行Test03的main方法
image.png

image.png
这个ucp就是我们所有类加载器需要加载的jar文件,有jre lib下的 以及mvn仓库的jar文件
image.png

knownToNotExist不存在就进入判断是否已经加载过了,显然我们第一次加载是没有加载过
image.png

然后进入super.loadClass(var1, var2);
image.png
进入appClassLoader的父类URLClassLoader发现没有loadClass方法 再进入URLClassLoader的父类SecureClassLoader 发现还是没有loadClass方法 最后进入SecureClassLoader的父类
ClassLoader 进入该类的loadClass方法。重点关注该类的的loadClass方法和findClass方法
image.png

往下走发现此时的parent是ext类加载器,然后调用ext的loadClass方法(ExtClassLoade这个类没有loadClass方法,最终还是调用ClassLoader的loadClass方法),还是进入该类的这个loadClass方法
image.png

然后发现parent是null,然后就去找bootstrap类加载器
image.png
最终发现调用的是native方法,因为bootstrap类加载器是C++写的
image.png

由于java.lang.InternalError是在rt.jar包,所以它最终会被bootstarp类加载器所加载。如果是自己定义的类的话,那么变量c一定为null,那么那就进入下面的if块执行findClass去找自己的类加载器,看下图的类的name
image.png
从ucp里面获取类Test03的资源相关,不为null就进入defineClass【将byte字节流解析为JVM能够识别的Class对象(直接调用这个方法生成的Class对象还没有resolve,这个resolve将会在这个对象真正实例化时resolve)】。
image.png
最终返回AppClassLoader
image.png

双亲委派总结:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
例如:一个类都是先由appClassLoader接收到类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,例如appClassLoader 会委托给他的parent是extClassLoader,当extClassLoader接收到类加载请求会委托给它的parent是bootstrapClassLoader去加载,如果bootstrapClassLoader不能加载就传递给extClassLoader去尝试加载,extClassLoader不能加载就传递给appClassLoader去加载,如果还不能加载就抛出ClassNotFoundException
jvm规范并没有明确要求类加载器的加载机制一定要是使用双亲委派模型,只是建议这种方式而已,
比如tomcat就打破了双亲委派模型。在tomcat中类加载器机制就和传统的双亲委派模型有一定的区别,当缺省的类加载器接收到一个类的加载请求,首先会由它自行加载,当他加载失败时才会将类的加载任务委派给它的超类加载器加载,这也是servlet规范推荐的一种做法。

为什么tomcat要打破双亲委派模型:
因为tomcat的是一个servlet容器,webapp下可以放多个app,比如app1、app2…如果app1和app2都有同名(全限定名)的class,那么jvm就会以为是加载过了这个类,所以tomcat采用了webAppClassLoader去自行加载各自的app。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享