一、类加载过程
加载 | 链接 | |
验证 | ||
准备 | ||
解析 | 初始化 |
1.加载
加载是指,将类的class文件,读入到内存,并为其创建java.lang.Class文件
- 本地文件系统加载class文件——绝大部分的示例代码
- jar包中加载——例如jdbc
- 网络加载
- java文件的动态加载
类加载器无须“首次使用”加载,jvm虚拟机允许系统预先加载某些类
2.链接
system为之生成class对象之后,会进入链接阶段,负责吧类的二进制数据合并到jre当中
(1)验证:
Java较C++相对安全,存在类校验,c++则没有。验证是Java的一个重要防护,防止应用被恶意入侵。越严谨的验证就越安全,例如数组越界。——保证class文件包含的信息符合jvm要求,保护jvm。
- 文件格式验证:字节流文件符合class规范。
主次版本在jvm的范围 |
常量池类型 |
指向常量的索引值是否存在不存在的类型 |
- 元数据:字节码语义分析是否符合Java语法规范
- 字节码校验(重要):数据流和控制,确定语义合法,符合逻辑,然后才会run类的方法,不会有危险
- 符号引用的验证:保证引用一定会被访问,解决类无法访问
(2)准备:
- 为类的静态变量分配内存
- 设置默认初始值
(3)解析:
- 将二进制的符号引用替换成直接引用
- 符号引用
- 直接引用包括:
目标指针、偏移量 句柄
3.初始化
初始化为静态变量赋正确的默认值,这里与准备阶段区分。
例如:private static int a=5
其实是在内存空间开辟之后,准备阶段设置int默认值为0 在初始化阶段,才会赋值5
二、类加载的时机
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName("com.lyj.load"))
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类
三、类加载器:
将所有被载入内存的对象生成一个java.lang.Class实例,保证只被加载一次,且保证唯一性。
1.根加载器(bootstrap class loader):
加载Java的核心类,由原生代码实现,开发者无法变更引用。
2.扩展类加载器(extensions class loader)
加载jre扩展目录 lib/ex 或 java.ext.dirs
java实现,父类加载器是null
3.系统加载器(system class loader)
系统类加载器,也叫应用加载器
- class path
- java.class.path
- classPath指定的jar包路劲
ClassLoader的静态方法 getSystemClassLoader()
- 1.检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
- 2.如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
- 3.请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
- 4.请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
- 5.当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
- 6.从文件中载入Class,成功后跳至第8步。
- 7.抛出ClassNotFountException异常。
- 8.返回对应的java.lang.Class对象。
四、类加载机制
1.三种类加载机制
- 1.全盘负责:当一个类加载class时,该class的依赖和引用也由该类加载器完成
- 2.双亲委派:先让父类加载器尝试加载,如果没有加载,再用自己的加载器加载
- 3.缓存机制:将所有加载过的class缓存,先取缓存,如果拿不到,再讲二进制文件读入内存。所以,修改了class,需要重启才能生效。
2.双亲委派(重点)
双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
双亲委派总结:
子类加载器很懒,并不会优先加载子类,而是交给父类加载器加载。父亲做不了,才轮到儿子。
这样做的优点是,保证优先级,只被加载一次,既提高了效率,也更加安全。举例:java.lang.Integer由父类加载器加载,防止核心API库被随意篡改。