1.类加载过程
加载 -> 链接->初始化
链接: 验证 准备 解析
一个类 只能被同一个类加载一次 (实现类的隔离)
1.1 加载
都干了什么
物理磁盘上存在的字节码***.class文件加载到内存中*** 生成了 一个Class对象
(类模板)
JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中,这样JVM在运行期便能通过类模板而获取Java类中的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用。
类模板对象 放在 方法区中
class 对象在哪里
1.2 链接
1.2.1 验证
目的保证 加载的字节码是合法 合规范的 安全性检查
1.2.1 准备
为 static 变量分配空间,并将其初始化为默认值,在初始化方法中 显式赋值。
1.这里不包含 static final 修饰的静态常量,因为常量在编译时就分配了,准备阶段 (当前阶段) 会显式赋值
注意
1、基本数据类型,非final修饰的静态变量会在准备prepare阶段赋初始值,然后在初始化initial中的方法中显式赋值
2、静态常量(基本数据类型、String类型字面量("XXX"这种情况))在编译阶段会初始化赋值,然后在准备阶段就会显式赋值
也就是 基本数量类型静态常量 和字符串常量
3、引用数据类型的静态常量,尤其是new String(“XXX”)这种形式,都是在初始化中的中进行显式赋值的
4、如果在static静态代码块中具有显式赋值操作(定义的后面没有赋值),那肯定就是在初始化中的方法中显式赋值
1.2.1 解析
解析时,将常量池中 类 接口 方法 字段的 的符号引用解析为直接引用
1.3 初始化
为类中静态变量,显式的赋值,即调用< clinit >()方法
< clinit > () 只有在类中的static变量显式的赋值或者在静态代码块中赋值了,才会生成
static
static{
}
< init >()一定会出现在Class的method中
static final 搭配
触发类的初始化
1.new 类 克隆 反序列化
2.Class.forName(“xxx”)初始化
3.main()方法的当前类
4. static 修饰的变量
5. 静态方法
6. 初始化子类时,父类未初始化,初始化父类。
7. 接口有default方法,直接或者间接 实现该接口 类的初始化,该接口要在之前被初始化。
未触发初始化
- 类加载器的loadclass方法
- 访问静态常量
- 创建类的数组对象
- 类.class 因为在类加载阶段已经生成了 java_mirror对象,即类对象.class
- Class.forname 为false时
2.类的加载器
2类 启动类加载器C/CPP 编写
继承抽象类 ClassLoader 类 自定义类加载器
启动类加载器 加载 jre/lib
扩展类 jre/lib/ext
应用类 classpath 下的路径
自定义 类加载器
可以隔离加载类
修改类的加载方式 打破双亲委派
可以继承URLClassLoad
双亲委派机制
什么是双亲委派机制
所谓的双亲委派,就是指调用类加载器的 loadClass 方法时,查找类的规则
安全,防止被篡改
避免重复加载,类只加载一次
双亲委派的优劣
优点:
- 一个类只能被加载一次,无需重复加载,确保唯一性。
- 保护程序安全,防止核心api 被串改。
劣势:
顶层的ClassLoader 无法访问底层的ClassLoader 所加载的类。
打破双亲委派机制
线程上下文 类加载器
spi tomcat 都打破了
spi
关于jdbc 相关jar 包的加载。
tomcat类加载机制
tomcat 类加载类打破了双亲委派机制
当我们需要用到一个类的时候,tomcat加载规则如下
- 使用BootStrap 引导类加载
- 使用System 系统类加载器加载
- 使用WebAppclassLoad 在web-inf/class,web-inf/lib 下加载
- 使用common 类加载器在catalina_home/lib中加载。
好了,至此,我们已经知道了tomcat为什么要这么设计,以及是如何设计的,那么,tomcat 违背了java 推荐的双亲委派模型了吗?答案是:违背了。 我们前面说过:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。
很显然,tomcat 不是这样实现,tomcat 为了实现隔离性
,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
2、我们思考一下:Tomcat是个web容器, 那么它要解决什么问题?
- 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。(
使用webappclassload 加载各自的,实现隔离
)
2… 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是扯淡的。 (不同webapp 共享类加载怎么解决,使用common 类加载器
) - web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
- web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用? 所以,web容器需要支持 jsp 修改后不用重启。(
jsp 累加器
)
spi 是什么
SPI全称为Service Provider Interface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。***这样可以在运行时,动态为接口替换实现类。***正因此特性,我们可以很容易的通过SP1机制为我们的程序提供拓展功能。SPI机制在第三方框架中也有所应用,比如Dubbo就是通过SP1机制加载所有的组件。不过,Dubbo并未使用Java原生的SPI机制,而是对其进行了增强,使其能够更好的满足需求。在Dubbo中,SPI是一个非常重要的模块。
//接口
public interface Czbk {
void service();
}
//两个实现类
public class Czxy implements Czbk {
@Override
public void service() {
System.out.println("上大学,来传智学院,一所不一样的大学,收获不一样的你");
}
}
public class Itheima implements Czbk {
@Override
public void service() {
System.out.println("学IT,到黑马");
}
}
测试类结果
public static void main(String[] args) {
ServiceLoader<Czbk> serviceLoader = ServiceLoader.load(Czbk.class);
Iterator<Czbk> czbkIterator = serviceLoader.iterator();
while (czbkIterator.hasNext()) {
Czbk czbk = czbkIterator.next();
czbk.service();
}
}
原生java spi问题
配置文件中配置了多少个接口实现类,就都要读取执行。
想要单独执行某个实现了方法?