目录
我们都知道,.java文件(java源码)成为可以让计算机执行的机器码,都要经过编译,即:
.java文件------javac(Java编译器)编译------>.class文件;
而.class文件通过Classloader(类加载器)加载到JVM(java虚拟机)中。而在.class文件加载到JVM中的过程中间以及之后,都做了什么工作,以及对象实例化的过程,就是这篇文章的主题。
类的加载和初始化
-
加载:载入.class文件到方法区(不一定是从class文件获取,也可能时jar包、动态获取)
在载入.class文件时,JVM会将字节流代表的静态存储结构转化为方法区的运行时数据结构,并在堆中生产一个代表该类的java.lang.Class对象,作为该类方法各种数据的入口;
-
验证:验证class字节流是否符合当前jvm规范,没有安全问题;
-
准备:在方法区为静态变量(类变量/static修饰的变量)分配内存并赋默认值。若为final修饰的静态变量则赋予声明值;
-
解析:将虚拟机中常量池的符号引用(常量名),改为直接引用(内存地址);常量池也在方法区;
-
初始化:执行<clinit>方法(类构造器):为静态变量赋值(更改了默认值,注意与赋默认值区分),执行静态代码块。JVM会保证先处理父类的<clinit>()。JVM会保证一个类的<clinit>方法在多线程环境下正确地加锁和同步。
<clinit>方法是用作构造类信息的,不是构造类对象的构造器(这个由构造方法做)。
<clinit>方法是由编译器自动收集类中的静态变量和静态代码块语句合并产生的。
注:2~4统称连接阶段。
对象的实例化
执行class字节码文件中的<init>方法。<init>由普通变量、普通代码块及对应的实例构造器组成,其处理顺序为:普通变量、普通代码块、构造器。
其中构造器<init>与构造方法(也叫构造函数)的联系与区别:
<init>用于实例化对象及其对象初始化。<init>构造器由编译器自动生成,而非由我们自己写代码写出来。若java代码中没有显式的构造方法(没有写构造方法),那编译器将会添加一个没有参数的、访问性(public、protected或private)与当前类一致的默认构造方法,并根据默认构造方法生成<init>;若有显式的构造方法,则通过该构造方法,生成构造器<init>。
构造方法用于实现功能,和普通方法没有区别。例如,OrderService的有参构造方法,用于在实例化OrderService的对象时,传递id、code的参数:
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderService(1, 1);
}
}
public class OrderService {
private int id;
private int code;
public OrderService(int id, int code) {
this.id = id;
this.code = code;
}
}
存在继承关系时实例化顺序
上面说到,JVM会保证先处理父类的<clinit>(),静态变量、静态代码块的处理顺序又先于普通变量、普通代码块。那么此时,父子两个类的实例化顺序就会是这样: