本文已参与「新人创作礼」活动,一起开启掘金创作之路。
类加载器
作用
- 在加载阶段(Load),读取字节码,将字节码加载进JVM,转换成
java.lang.Class
对象 - 确定类在JVM中的唯一性。(子父级的关系限制了类的唯一性,全限定名一致的类,如果类加载器不同,也并不是同一个类)
分类/分层
java1.2版本中,只有Bootstrap的类加载器,这种情况下,很容易造成java自身的一些核心类由于同全限定名而被覆盖,不安全,因此对于类的安全性,信任考虑,进行了分层,java核心类>安装扩展类>自定义类
加载器分类 | 层级(数字越小,层级越高) | 加载内容 | 备注 |
---|---|---|---|
Bootstrap ClassLoader | 1 | 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。 | C++实现,不可访问 |
Extension ClassLoader | 2 | 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。 | |
App ClassLoader | 3 | 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。 | |
Custom ClassLoader | 4 | 通过java.lang.ClassLoader的子类自定义加载class | Tomcat使用的这种方式 |
主要特性
全盘负责
- 当一个类加载器负责加载当前类的时候,当前类所
依赖
、引用
的其他类,也将有该类加载器负责加载引入,除非显式的指定类加载器 - 类加载器加载引入的含义是指:指定类的加载入口,实际上究竟由谁加载,则由
父类委托
机制完成
父类委托(双亲委派)
- 子类如果没有加载过类,则交由父类加载
- 父类无法加载,则交由子类查找加载
缓存机制
- 缓存机制保证加载过的Class在内存中缓存
- 优先从缓存中读取,找不到再Load
- 缓存很难被卸载,因此需要很多时候更改了字节码文件,需要重启JVM
- 相同的Class的
loadClass()
;只会调用一次,类变量因此只初始化一次
loadClass源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
复制代码
双亲委派问题
- 三方服务的问题,例如数据库驱动包
java.sql.Driver
打破双亲委派
SPI(service provider interface)
Java核心类中定义了很多接口和调用逻辑,但是没有具体的实现。SPI的方式,是自定义实现该接口的实现类,并且在META-INF/services下注册实现类,供核心类库使用,例如JDBC的DriverManager。
OSGI
通过卸载更新类加载器,同事卸载和替换类,实现代码的热部署。
自定义类加载器
自定义字节码的查找流程,重写loadClass