父子类执行顺序
- 父类代码块
package Java40.JVM;
/**
* @author 枳洛淮南
* @version 1.0
* @Description 功能
* @Date 2021/7/20 19:22
*/
public class Parent
{
private static String s = "abc";
private String o = "def";
static
{
System.out.println("parent static block");
}
{
System.out.println("parent block");
}
public Parent()
{
System.out.println("parent constructor");
}
}
- 子类代码块
package Java40.JVM;
/**
* @author 枳洛淮南
* @version 1.0
* @Description 功能
* @Date 2021/7/20 19:22
*/
public class Child extends Parent
{
private static String s = "abc";
private String o = "def";
//静态代码块
static
{
System.out.println("Child static block");
}
//实例代码块
{
System.out.println("child block");
}
public Child()
{
System.out.println("Child constructor");
}
}
- Main 函数
package Java40.JVM;
/**
* @author 枳洛淮南
* @version 1.0
* @Description 功能
* @Date 2021/7/20 19:22
*/
public class Main
{
public static void main(String[] args)
{
new Child();
new Child();
}
}
执行顺序如上,类加载规律如下:
- 父类的静态变量 + 静态代码块(书写顺序)
- 子类的静态变量 + 静态代码块(书写顺序)
- 父类的实例代码块
- 父类的成员变量 + 构造方法
- 子类的实例代码块
- 子类的成员变量 + 构造方法
每构造一个实例对象,则实例代码块执行一次
类加载概述
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行 校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类 型,这个过程被称作虚拟机的类加载机制。与那些在编译时需要进行连 接的语言不同,在Java语言里面,类型的加载、连接和初始化过程都是 在程序运行期间完成的,这种策略让Java语言进行提前编译会面临额外 的困难,也会让类加载时稍微增加一些性能开销,但是却为Java应用提 供了极高的扩展性和灵活性,Java天生可以动态扩展的语言特性就是依 赖运行期动态加载和动态连接这个特点实现的。
类加载的时机
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的 整个生命周期将会经历
加载(Loading)、
验证(Verification)、
准备 (Preparation)、
解析(Resolution)、
初始化(Initialization)、
使用 (Using)和
卸载(Unloading)
七个阶段,其中验证、准备、解析三个 部分统称为连接(Linking)。
加载
Tips:加载只是类加载的一个过程
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的 一个阶段。在加载阶 段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数 据结构。(JDK 1.7:方法区位于Java进程块 JDK 1.8:本地内存,名称改为元数据区)
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法 区这个类的各种数据的访问入口。
从任何地方读取都可以,也可以在运行时动态生成
JSP 本质上是一个Servlet
Java 文件(编译)–> Class 文件 --> 类加载的第一个阶段 “加载” 阶段
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变 量)分配内存并设置类变量初始值的阶段
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过 程
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定 位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布 局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式 中。
- 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和 虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目 标必定已经在虚拟机的内存中存在。
初始化
类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类 加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器 的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初 始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主 导权移交给应用程序。
初始化阶段就是执行类构造器()方法的过程。()并不是程序员在 Java代码中直接编写的方法,它是Javac编译器的自动生成物,但我们非 常有必要了解这个方法具体是如何产生的,以及()方法执行过程 中各种可能会影响程序运行行为的细节,这部分比起其他类加载过程更 贴近于普通的程序开发人员的实际工作
类加载机制
类加载机制————双亲委派机制
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass () 方法之中,逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。