Java类加载器
Posted 黑面书生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java类加载器相关的知识,希望对你有一定的参考价值。
类的生命周期
加载-验证-准备-解析-初始化-使用-卸载
1 加载阶段
把.class二进制数据读到内存中,并放到方法区,然后在堆中创建一个Java.lang.Class对象,这个对象就是用来封装类在方法区的数据结构的。
所以,类加载机制的最终产物是:在堆中创建了java.lang.Class对象,这个对象提供了访问方法区内部数据结构的接口。
2 验证阶段
这个主要就是验证包的签名等
- 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的JVM处理。只有验证通过了,字节流才会进入方法区存储。
- 元数据验证:比如类是否有父类,类是否继承了不能被继承的类等,保证不存在不符合Java语言规范的元数据信息。
- 字节码验证:对类的方法体进行校验分析
- 符号引用验证:对常量池中的各种符号引用进行校验
3 准备阶段
为静态变量分配内存,并设置初始值。
内存分配动作发生在方法区的,在准备阶段,给类成员进行初始化。
类型 | 初始化值 |
---|---|
String | null |
Object | null |
int | 0 |
4 解析阶段
将符号引用转成直接引用
说白了,就是,将变量换成内存中真实地址,都将高级的东西解析为机器识别的底层东西。
- 符号引用: 用一组符号描述所引用的目标,引用的目标不一定已经加载到内存中。
- 直接引用:直接指向目标的指针、相对偏移量、间接定位到目标的句柄,直接引用的目标已经在内存中。
5 初始化(最重要的)
5-1 初始化2种方式
初始化有生命类变量和静态代码块2种方式,它们的优先级相同,谁在前面谁先来。
static int age = 20;
static{
System.out.println("hello,world");
System.out.println(age);
}
6 初始化的触发几种情况
6-1 创建类对象的时候
- new
- 反射
- 对象的反序列化
6-2 调用类的某个静态方法
package com.siyu;
public class Claloader {
static int age = 20;
static{
System.out.println("hello,world");
System.out.println(age);
}
public static void getName(){
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
package com.siyu;
public class Test {
public static void main(String[] args) {
// 调用类的某个静态方法,触发类的初始化
Claloader.getName();
// hello,world
// 20
// main
}
}
6-3 调用某个类或接口中的类变量
package com.siyu;
public class Claloader {
static int age = 20;
static{
System.out.println("hello,world");
System.out.println(age);
}
public static void getName(){
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
package com.siyu;
public class Test {
public static void main(String[] args) {
// 调用类的某个静态方法,触发类的初始化
int age = Claloader.age;
// hello,world
// 20
}
}
6-4 调用子类静态变量,引父类初始化
package com.siyu;
public class Claloader {
static int age = 20;
static{
System.out.println("hello,world");
System.out.println(age);
}
public static void getName(){
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
class Sub extends Claloader{
static String name = "nezha";
static {
System.out.println("这是子类的静态代码块");
}
}
package com.siyu;
public class Test {
public static void main(String[] args) {
int age = Sub.age;
// hello,world
// 20
}
}
6-5 直接用java.exe运行某个类
7 注意的是
- 子类引用父类静态变量,不会引发子类初始化
- 通过数组定义引用类,不会引起类初始化
- 使用final修饰的为常量,不会引起初始化
8 java类加载器
JVM加载字节码文件靠的是类加载器,这个操作是在JVM外部实现的。
这样应用程序就可以自己决定如何获取所需的类。
如果两个类来自同一个Class文件,但是由不同的类加载器加载,那么者两个类一定是不相等。
从JVM角度讲,只有两种加载器。一种是启动类加载器,是虚拟机自身的一部分,由C++语言实现;
还有就是其他类加载器,由Java语言实现,全都继承自抽象类java.lang.ClassLoader 独立于虚拟机外部。
从开发角度看,主要分为这三种:
启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>lib目录中,或者被 -Xbootclasspath参数所指定的路径中。
扩展类加载器(Extension ClassLoader):主要加载<JAVA_HOME>libext目录中的
应用程序类加载器(Application ClassLoader):加载用户类路径(ClassPath)上所指定的类库。如果我们没有自定义过自己的类加载器,那么这就是程序默认的类加载器。
在使用类加载器加载类的过程种,最好遵循双亲委派模型。双亲委派的原理是:类加载器收到加载类的请求时,先把这个请求委派给父类加载去完成,每一层次的加载器按这个这个逻辑执行。那么所有的加载请求最终都应该传送到顶层的启动类加载中。父加载器无法加载,子加载器才会自己加载。这样做的好处是可以避免类的重复加载,保证程序运行的稳定性。
以上是关于Java类加载器的主要内容,如果未能解决你的问题,请参考以下文章