文章目录
类加载的本质
ClassLoader是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。
字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。
它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。
JVM 双亲委派机制
Java1.2之后引入双亲委派模式 。
核心原理: 如果其中一个类加载器收到了类加载的请求,它并不会自己去加载而是会将该请求委托给父类的加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,如此递归,请求最终到达顶层的启动类加载器。
如果父类能加载,则直接返回,如果父类加载不了则交由子类加载,这就是双亲委派模式。
好处
- 向上委托给父类加载,父类加载不了再自己加载
- 避免重复加载,防止Java核心api被篡改
BootstrapClassLoader(启动类加载器)
负责加载 JVM 运行时核心类,加载System.getProperty("sun.boot.class.path")所指定的路径或jar
ExtensionClassLoader
负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVAHOME/lib/rt.jar文件中.
加载System.getProperty("java.ext.dirs")所指定的路径或jar。
在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
AppClassLoader
AppClassLoader才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。
我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。
加载System.getProperty("java.class.path")所指定的路径或jar。
在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld
Tomcat的 类加载顺序
在Tomcat中,默认的行为是先尝试在Bootstrap和Extension中进行类型加载,如果加载不到则在WebappClassLoader中进行加载,如果还是找不到则在Common中进行查找 .
Common是公共的包, tomcat可以外挂很多个webapps (tomcat和app分开部署) ,优先以webapps下的为主。
tomcat7 —> 默认 WebappClassLoader 类加载器
tomcat 8 ---->默认 ParallelWebappClassLoader 类加载器
Tomcat违反了双亲委派机制?
也不尽然,核心的Java的加载还是遵从双亲委派 。
Tomcat中 各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托 .
原因有二
- 为了避免类冲突,每个 webapp 项目中各自使用的类库要有隔离机制
- 不同 webapp 项目支持共享某些类库
Tomcat加载机制小结
当tomcat启动时,会创建几种类加载器:
- Bootstrap 引导类加载器 : 加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)
- System 系统类加载器 : 加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于
CATALINA_HOME/bin下 - Common 通用类加载器:加载tomcat使用以及应用通用的一些类,位于
CATALINA_HOME/lib下,比如servlet-api.jar - webapp 应用类加载器: 每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于
WEB-INF/lib下的jar文件中的class 和WEB-INF/classes下的class文件。
总之 当应用需要到某个类时,则会按照下面的顺序进行类加载:
1 使用bootstrap引导类加载器加载
2 使用system系统类加载器加载
3 使用应用类加载器在WEB-INF/classes中加载
4 使用应用类加载器在WEB-INF/lib中加载
5 使用common类加载器在CATALINA_HOME/lib中加载
常见错误
NoClassDefFoundError
NoClassDefFoundError是在开发JavaEE程序中常见的一种问题。
该问题会随着你所使用的JavaEE中间件环境的复杂度以及应用本身的体量变得更加复杂,尤其是现在的JavaEE服务器具有大量的类加载器。
在JavaDoc中对NoClassDefFoundError的产生是由于JVM或者类加载器实例尝试加载类型的定义,但是该定义却没有找到,影响了执行路径。
换句话说,在编译时这个类是能够被找到的,但是在执行时却没有找到。
这一刻IDE是没有出错提醒的,但是在运行时却出现了错误。
NoSuchMethodError
在另一个场景中,我们可能遇到了另一个错误 NoSuchMethodError。NoSuchMethodError代表这个类型确实存在,但是一个不正确的版本被加载了。
ClassCastException
ClassCastException,在一个类加载器的情况下,一般出现这种错误都会是在转型操作时,比如:A a = (A) method();,很容易判断出来method()方法返回的类型不是类型A,但是在 JavaEE 多个类加载器的环境下就会出现一些难以定位的情况。