类的生命周期
类的生命周期:加载→验证→准备→解析→初始化→使用→卸载
类加载的时机
关于在什么情况下需要需要开始类加载过程的第一个阶段"加载",虚拟机并没有进行强制约束,这点交给虚拟机的具体实现来自由把握。
但严格规定了有且只有六种情况必须立即对类进行"初始化":
(字节码指令:new、getstatic、putstatic、invokestatic)
1、遇到new关键字实例化对象的时候。
2、读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
3、调用一个类型的静态方法的时候。
4、使用java.lang.reflect包的方法对类型进行反射调用的时候。
5、当初始化类的时候时,发现其父类还没有进行初始化,需要先触发父类的初始化。
6、当虚拟机启动时,用户需要指定一个要执行的主类(包括main()),那么就会触发该类的初始化。
类的主动使用和被动使用
主动使用:对类主动进行初始化。在上述六种情况。
被动使用:被动使用不会初始化类,但可能会导致类的加载。
1、通过子类引用访问父类的静态字段,不会造成子类的初始化。
2、使用数组定义引用类,不会触发引用类的初始化。
Tips:创建数组的字节码指令为**newarray**。
3、访问类的静态常量
常量在编译的时候会存入调用类的常量池中,本质上没有直接引用到定义常量的类
类加载的过程
加载
目的:
1、通过一个类的全限定名来获取描述该类的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
类加载器
设计团队想把类加载阶段中"通过一个类的全限定名来获取描述该类的二进制字节流"放到Java虚拟机外部去做,以便让应用程序自己决定如何去获取所需类。
实现这个动作的代码被称为类加载器。
作用:加载类。
引导类加载器
Bootstrap ClassLoader:是用C语言写的,不是ClassLoader的子类。被称为根装载器。它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib
,-Xbootclasspath
参数指定的路径以及%JAVA_HOME%/jre/classes
中的类。
Bootstrap ClassLoader只加载包名为java、javax、sun等开头的类。
扩展类加载器
Extension ClassLoader:Bootstrap Loader加载ExtClassLoader,并将ExtClassLoader的父加载器设置为Bootstrap Loader。其是用Java写的。ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext
,此路径下的所有classes目录以及java.ext.dirs
系统变量指定的路径中类库。
继承于ClassLoader类。
系统类加载器
Application ClassLoader:Bootstrap Loader加载ExtClassLoader后,就会加载AppClassLoader,并将AppClassLoader的父加载器指定为ExtClassLoader。AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
双亲委派机制
双亲委派机制:指先委托父类装载器去寻找目标类,如果父类装载器寻找不到,再从自己负责的路径下寻找目标类。
双亲委派机制主要在ClassLoader中的loadClass方法中体现。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//用synchronized保证加载时类只加载一个Class类
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过了
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
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派模型的优点:
-
Java类伴随着它的类加载器一起具备了一种带有优先级的层级关系,通过这种层级关系可以避免类的重复加载。
-
保护程序安全,防止核心API被篡改。
双亲委派模型的缺点:
- 顶层的ClassLoader无法访问底层ClassLoader加载的类。
双亲委派模型的破坏:
第一次:还没有出现双亲委派模型之前。
第二次:为了解决顶层ClassLoder无法访问底层ClassLoader加载的类,引入了线程上下文类加载器。
线程上下文加载器:通过Thread类的setContextClassLoader()方法设置,如果创建线程时未设置,那么会从父线程中继承,**如果在应用程序的全局范围都没有设置过,默认为应用类加载器**。
第三次:由于用户对于程序动态性的追求导致的。如:代码热替换、模块热部署等。
验证
目的:确保Class文件字节流中包含的信息符合规范,保证不会危害虚拟机的安全。
文件格式校验:
魔数、版本号、常量池
元数据校验:
类是否有父类(除了Object,所以类都有父类)、类是否实现了接口的所有方法
字节码验证:
目的:通过数据流分析和控制流数据分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:
目的:确保解析行为能正常运行
准备
为类中定义的静态变量分配内存并设置类变量初始值的阶段。
解析
解析过程是将Java虚拟机将常量池内的符号引用替换为直接引用。
符号引用:用字面量来描述所引用的目标。
直接引用:可以直接指向目标的指针。
初始化
初始化阶段就是执行类构造器