类加载,类加载器将编译好的class文件以字节码的形式加载到运行内存的元数据区(java7及以前是方法区),并且在堆内创建Class类型的对象,来描述class文件的内容。
程序可以通过class对象来调用类的属性,方法等功能。
类加载来源:文件、远程服务、Jar包等字节流。
每个Class都有一个ClassLoader,来标识class是由哪个ClassLoader加载的
类加载过程
延迟加载,不是一次性加载全部需要的类,而是按需加载,程序在运行过程中会逐步遇到需要的类,需要时使用ClassLoader加载,加载后就保存起来,不需要重新加载
类加载过程如下:
- 加载,类加载器获得class文件的二进制字节流,转化为方法区的数据结构,在堆中生成代表这个类的Class对象,提供对方法区数据的访问方式
- 链接
- 验证,验证被加载的类的正确性,可配置关闭。
- 准备,为静态变量分配内存,但不赋值
- 解析,类中对常量池的符号引用转为直接引用
- 初始化,对类的静态变量,静态代码块执行初始化操作
加载器分类
BootstrapClassLoader,根类加载器,负责加载JDK核心类,也就是Java_home/lib下的类,也就是rt.jar
ExtensionClassLoader,扩展类加载器,负责加载Java ext扩展下的类,也可以是参数java.ext.dirs参数指定的目录下的类
AppClassLoader,面向应用的类加载器,加载应用classpath下的类,以及java.class.path参数指定的目录下的类
CustomClassLoader,用户自定义ClassLoader,从用户自定义的位置加载类
URLClassLoader,是ClassLoader的一个实现,ExtensionClassLoader和AppClassLoader都是使用URLClassLoader实现
加载机制
双亲委派机制
parent具有更高的加载优先级,类加载机制加载类时,先不尝试加载,先请求父类加载器加载,父加载器再请求他的父类加载器,在父类加载器加载不了时,子类加载器才加载,扫描对应的classpath。
为什么双亲委派?
提高性能,避免重复加载类,parent类加载器加载的类在子类加载器之间共享
更加安全,防止对核心类库的覆盖
线程ContextClassLoader
需要显式的使用,否则在线程中调用Class.forName(),不会自动使用ContextClassLoader。
Thread.currentThread().getContextClassLoader()
ContextClassLoader实现了跨线程共享类,默认情况下,ContextClassLoader自动继承于父线程,多个子线程之间共享同一个ClassLoader,这样一个线程加载的类在另一个线程也可以使用,因为使用的一个ClassLoader,这样就实现了跨线程共享类。
同样,可以指定线程使用不同的ContextClassLoader来实现线程之间或者线程池之间的类隔离。
SPI类加载
SPI,Service Provider Interface面向接口编程。
SPI类加载,一个类使用类加载器A来加载,那么这个类的依赖也由A来加载,对于SPI接口,接口再核心库rt.jar,实现往往在用户的ClassPath中,BootstrapClassLoader就加载不到,如JDBC,JNDI实现。这时候就需要使用线程的ContextClassLoader来加载,打破双薪委派机制。
热加载热部署
以tomcat为例,tomcat架构参考tomcat
tomcat热加载,销毁当前的context,重新创建类加载器,加载类,重新创建context
热部署,Host监控目录下的所有应用,如果应用被删除就销毁对应的Context,如果有新的应用加入就部署新加入应用
(完 ^_^)