类加载子系统
加载的类信息存放在方法区中,除了类信息方法区还会存放运行时常量池信息,包括字符串字面量和数字常量。
jvm规范不代表jvm一定这么实现,例如HotSpot VM1.7之前使用永久代来实现方法区,1.8之后使用元空间,就像接口和类的关系,元空间是实现方法区的实现类。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。
类的加载过程:
![在这里插入图片描述](https://img-blog.csdnimg.cn/b73778e2d4f14121a6e89e3b938432fb.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA546L5pyo6aOO,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)加载:
1、通过类的全限定名获取定义此类的二进制字节流
2、将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
3、在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的入口
链接
1、验证
确保class文件符合虚拟机需求,比如coffeebaby开头
2、准备
类变量分配内存和初始值,如果被fianl修饰则变为常量,常量在编译阶段赋值
3、解析
初始化
1、执行类构造器的()方法。此方法不需要定义,是javac自动收集类中所有赋值操作和静待代码块中语句合并而来
双亲委派机制
类加载器收到加载请求不会先去加载,而是委托父类执行父类加载器执行,直至引导类加载器。如果父类加载器可以完成类加载就成功返回,如果父类加载器无法加载则子类加载器加载
如何打破双亲委派机制
线程上下文类加载器
JNDI(Java Naming and Directory Interface)服务就是上面描述的这种场景,JNDI是Java的标准服务,它自身的代码由Bootstrap类加载器加载,由于JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现的JNDI接口提供者(SPI)的代码,独立厂商提供的代码jar包放置在ClassPath下,如果使用双亲委派模型加载类的方式是搞不定的,怎么办呢?
为了解决这个困境,Java设计团队引入了线程上下文类加载器(Thread Context ClassLoader),虽然它确实不太优雅,但解决问题啊。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程还未设置,它将从父线程中继承一个,如果在应用程序的全局范围内都没有设置过,那么这个类加载器就是AppClassLoader。有了线程上下文类加载器,JNDI服务就可以加载所需要的SPI代码,即父类加载器可以请求子类加载器完成类加载动作,这其实是违反了双亲委派原则的。实际上JNDI、JDBC、JCE、JAXB、JBI等所有涉及SPI加载动作的基本都采取的这种方式。
总结:线程上下文加载器之所以打破双亲委派模型是因为双亲委派模型依赖的单一方向的,并不能解决父类加载器去依赖子类加载器这种逆方向需求。
Tomcat类加载器
实际上,不只是Driver驱动的实现是这样,只要有需要,在双亲委派机制无法满足需求前提下,在tomcat、spring等等的容器框架也是通过一些手段绕过双亲委派机制。
双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。如下图所示
从图中的委派关系中可以看出:
- CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用。
- CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。
- WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。
总结:tomcat之所以破坏双亲委派模型,我想主要在于双亲委派模型只看到了共享性,没有看到隔离性需求,即共享是有条件的共享。
OSGI类加载器
非双亲委派模型的另一种需求来自程序动态性追求。比如代码热替换(HotSwap)、模块热部署(Hot Deployment)。可以哪USB热插拔技术来做比方。热部署对生产系统来说具有很大吸引力,不用停机就能完成部署效率啊。
OSGI是Java模块化标准,OSGI实现模块热部署的关键是它自定义的类加载器,每一个模块都有一个自己定义的类加载器,当需要更换一个Bundle时,则把Bundle连同类加载器一同替换以实现热替换。
在OSGI环境下,类加载器不再是树型结构的双亲委派模型,而是网状结构,当收到类加载请求时,OSGI是按照下面顺序进行类搜索的:
- 以“java.*”开头的类,委派给父类加载器加载。
- 将委派列表名单内的类,委派给父类加载器加载。
- 将Import列表中的类,委派给Export这个类的Boundle的类加载器加载。
- 查找当前Boundle的ClassPath,使用自己的类加载器加载。
- 查找类是否在Fragment Boundle中,如果在,则委派给Fragment Boundle类加载器加载。
- 查找Dynamic Import列表的Boundle,委派给对应的Boundle类加载器加载。
如果以上都未查询到,则查找失败。
上面的搜索顺序除了1,2两点和双亲委派类似,其余都是平级类加载过程。
总结:OSGI Boundle类加载器提供了类加载的另一种机制,加载器结构不一定非得是树型结构,也可以是网状结构。