classloader顾名思义就是类加载器,负责将class加载到jvm中。
事实上,classloader除了能将class加载到jvm中,还有一个重要的作用就是会审查每个类应该由谁来加载,它是一种父优先的等级加载机。
classloader除了上述两个作用以外还有一个作用就是将class字节码重新解析成jvm统一要求的对象格式(说明class文件并不是加载进来就能用)
classloader类结构分析
classloader主要有几个方法
1 defineclass
defineclass主要负责将byte字节流解析成JVM能够识别的Class对象。比如通过网络获取字节流,然后转化为Class对象。
defineclass生成的Class对象还么有resolve,只会对象真正实例化时才会resolve(resolve是解析连接的过程,经过这一步Class对象才能真正被访问到)
2 findclass
defineclass和findclass方法通常是一起使用的,我们通过直接覆盖classloader父类的findclass方法来实现家在规则。
如果想在类加载到JVM时就被连接,那么亦可以在findclass中再次调用resolveclass方法来进行连接。
3 loadclass
loadclass按照双亲委派模型进行加载,加载不到才会调用findclass。
如果我们要在运行时加载一个类,直接调用this.getClass().getClassLoader().loadClass()方法就可以完成类加载了。
classloader是个抽象类,如果要实现自己的classloader,一般可以继承uriclassloader,因为这个类已经帮我们实现了大部分工作。
classloader的等级加载机制
整个jvm平台提供三层classloader
1 bootstrapclassloader
这个classloader只服务于JVM自身,主要加载JVM自身工作所需要的类,是JVM自己控制的,用户访问不到,这个类加载器也不遵守双亲委派模型,没有子类。
负责加载lib目录下面的类
2 extclassloader
extclassloader虽然是jvm的一部分,但是不是jvm实现的,它负责加载lib/ext下面的类
3 appclassloader专门为用户服务,父类是extclassloader。服务于classpath下的类加载。
如果我们要实现自己的类加载,最终都要把appclassloader作为父加载器。
事实上,bootstrapclassloader不是ext的父加载器,他也没有子类。
jvm加载class文件的两种方式:
1 隐式加载
是指jvm自动加载类的方式,不用显示调用classloader加载
2 显示加载
在代码中使用classloader加载。
如何加载class文件
classloader加载一个class文件到JVM时经过的过程
class文件 ——> findclass ——> class规范验证,准备,解析 ——> 类属性初始化赋值 ——> class对象
1 第一个阶段是把class文件把这个文件包含的字节码加载到内存
2 第二个阶段又可以分为三个步骤,分别是字节码验证、class类数据结构分析以及相应的内存分配和最后的符号表的链接。
3 第三个阶段是类中静态属性和初始化赋值,以及静态块的执行等。
加载字节码到内存(执行defineclass)
其实在抽象类classloader中并没有定义如何去加载
1 如何去找到指定类并且把它的字节码加载内存需要的子类中去实现,也就是要实现findclass方法。
2 我们来看一下子类urlclassloader是如何实现findclass的,在urlclassloader中通过一个urlclasspath帮助取的要加载的class文件字节流,而这个urlclasspath定义了到哪里去找这个class文件。
3 如果找到了这个class文件,再读取它的byte字节流,通过defineclass方法来创建类对象。
实际上,ext把默认的搜索路径指定为环境变量配置的lib/ext了。而appclassloader则指向classpath的环境变量。
验证与解析(执行verify,prepare和resolve)
1 字节码验证,类装入器对于类的字节码要做许多检测,以保证格式正确,行为正确。
2 类准备,在这个阶段准备代表每个类中定义的字段,方法和实现接口所必须的数据结构。
3 解析,在这个阶段装入器装入类所引用的其他所有类,可以用许多方式引用类,比如超类,接口,字段等。
初始化class对象(初始化类中的值)
在类中包含的静态初始化器都被执行,静态字段也被初始化为默认值。
常见加载类错误分析
classnotfoundexception
这个异常主要是因为找不到对应类的class字节码,说明classpath下没有指定文件存在。
noclassdefounderror
这个异常在用java命令行执行java类时可能会用到,因为指定类名时如果没写完整,就会出现这个问题,
unsatisfiedlinkerror
少见
classcastexception
类型转换失败,可能出现在不能转换的类型发生转换时。
exceptioninitializeerror
少见
常用的classloader分析
web应用中的一个servlet是如何加载类的,我们看一下它的classloader,发现层级关系是extclassloader -> appclassloader -> standardclassloader -> webappclassloader
tomcat在何时创建这些classloader:
1 standardclassloader在bootstrap类(tomcat的一个类)的initclassloaders方法中创建的。
2 standardclassloader创建成功时,会被设置为整个tomcat的根classloader
3 tomcat使用standardclassloader加载catalina并创建对象(catalina是tomcat的默认容器,catilina是tomcat以前的名字),整个tomcat容器的类加载器也是standardclassloader。
4 standardclassloader实际上并不处理加载逻辑,因为它是是appclassloader的代理,实际上的加载器还是appclassloader。
5 tomcat容器本身(catilina)是谁加载的不重要,我们要了解其中的servlet是谁加载的。
6 web.xml配置了每个servlet对应的类,所以应该是由类加载器显示加载的。(解析xml显示加载)
7 standardcontext(解析web.xml后映射的上下文实例)检查classloader是否存在,然后instancemanager这个类会去真正加载这些servlet实例。
8 instancemanager类使用的classloader是webappclassloader,它的处理和appclassloader有所不同。
1 首先检查webappclassloader是否已加载
2 如果没有,则检查在jvm中是否已加载,调用classloader的findloadedclass
3 如果前两个缓存没有,则用appclassloader加载
4 检查类是否在特殊的包名下,如果在的话可以用standardclassloader来加载
5 如果还没找到,则由webappclassloader来加载,回去web-inf/classes目录下查找,然后用defineclass方法生成Class对象。
如何实现自己的classloader
自定义classloader的使用场景
1 到自定义路径下查找指定class文件
2 获得网络传输的类的字节码,可能已加密
3 如果一个已加载的class文件被修改,可以重新加载这个类,实现热部署
加载自定义路径下的class文件
指定classpath即可
加载自定义格式的class文件
加密的class字节流,先解密再加载即可
实现类的热部署
jvm在加载类之前会判断请求的类是否已被加载,也就是通过findcloadedclass来判断。
1 看这个类的完整类名是否一致。
2 看加载这个类的类加载器是否是同一个实例。即使是同一个classloader类的两个实例,加载同一个类也会不一样。
3 所以实现热部署可以用classloader不同的实例对象来加载
如果重复地加载一个类,会抛出linkageerror错误。
Java应不应该加载动态类
Java有一个痛处,就是修改一个类,必须要重启一遍,才会重新加载,非常费时,如果使用动态的类加载就可以节省很多时间。
但是这是不好的,Java的优势正是基于共享对象的机制,达到信息的高度共享,对象一旦被创建就可以被重复利用。
如果动态加载一个对象到jvm,很难在jvm中平滑过渡。在理论上可以替换原对象然后修改所有指向该对象的引用,但是它违反了jvm的设计原则,。
对象的引用关系只有对象的创建者和持有和使用,jvm不可以干预对象的引用关系。
特例
对象不能被动态替换的原因是有很多引用指向它,很多变量保存着它的状态,如果对象不会被引用所指,没有人持有对象的状态,则可以动态地替换对象。
JSP就是这么做的,一旦JSP页面被修改,我们就可以热加载对应生成的servlet实例,这个实例是单例的,并且是无状态的,没有引用指向它,利用这个原理,很多热部署的方案也出现了。
微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“即可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源)