一、加载过程
类加载过程加载,连接(验证,准备,解析),初始化,使用,卸载。
1,加载
class字节码文件描述类的各种信息。根据类全限定名,将二进制字节流加载到内存,可以是class文件外的其他渠道(如网络、动态生成),在堆生成一个代表类的Class对象,访问方法区类型信息数据的入口。
2,连接
验证,文件格式,元数据,字节码,符号引用。
准备,类变量分配内存,设置默认初始值0,(非final的static类型)。final类变量设置常量目标值。
解析,常量池符号引用转为直接引用。
3,初始化
类变量初始化语句和静态代码块,编译时,放在收集器,在<clinit>方法,类加载过程由JVM调用。优先初始化父类,仅允许一个线程对类初始化,没有静态变量和代码块时,可以没有<clinit>方法。
类信息
完整有效名,修饰符,类型直接接口的有序列表和父类,常量池,Field域信息,Method方法信息,静态变量static,classloader引用。
二、类初始化
触发条件
创建一个类的对象实例,new关键字、反射、序列化。
读取一个类或接口public静态变量,(final除外)。
调用一个类的静态方法。
使用java.lang.reflect包的方法对类反射调用。
初始化一个类的派生类时,先初始化该类。
jvm启动包含main方法的启动类。
public class Tea {
public static int a = 10 ;
public Tea(){
System.out.println("构造方法代码");
}
static {
System.out.println("类初始化代码");
}
public static int b = 100 ;
static {
System.out.println("类初始化代码2");
}
}
Tea类,静态变量a和b赋值,静态代码块,反编译Tea结果。
反编译Tea类编译器会自动搜集类变量的赋值动作与静态代码块语句,按照源文件出现的顺序,合并生成static{}方法。
外部访问Tea.a变量时,Tea类初始化,按照出现的顺序赋值和执行。如果Tea有父类,父类初始化在子类前执行。
Tea2类,继承Tea类。
public class Tea2 extends Tea{
public static int c = 10 ;
public static final int d = 10 ;
public Tea2(){
System.out.println("构造方法代码");
}
static {
System.out.println("类初始化代码3");
}
}
1,外部访问Tea2.a变量,仅Tea类初始化,Tea2不会初始化,子类引用父类静态变量,仅父类初始化,子类不会初始化。
2,外部访问Tea2.c变量,Tea2类初始化,先初始化Tea类。
3,外部访问Tea2.d变量,Tea2类不会初始化,final常量在编译阶段存储在类常量池,不会导致该类初始化。
4,外部定义一个Tea2类型数组,Tea2类不会初始化。
public class Tea3 {
public static int e = 10 ;
public static int b = 12;
public static Tea3 f = new Tea3() ;
public Tea3(){
System.out.println("构造方法代码:a="+a+" b="+b);
}
static {
b+=1;
System.out.println("类初始化代码");
}
public int a = 11;
public static Tea3 g = new Tea3() ;
}
外部引用Tea3.e变量,导致Tea3类初始化,流程:
1,初始化e,b变量值,
2,Tea3实例f,a实例变量初始化11,构造方法,打印a=11,b=12,
3,初始化static{}代码,b值自增。
4,初始化Tea3实例g,a实例变量初始化11,构造方法,打印a=11,b=13。
因此,实例初始化不一定在类初始化结束才开始,类初始化时,遇到静态对象赋值,将会进行实例初始化。
将实例化嵌入到类初始化流程中,导致实例化发生在某些类变量初始化之前。初始化本质是赋值,创建对象后,按顺序如果某个static变量还未初始化值就是0。
三、总结
1,实例初始化不一定在类初始化结束后开始。
2,虚拟机保证类构造器方法的加锁和同步,在同一个类加载器,一个类型只会被初始化一次。
3,如果没有类变量或静态代码块,可以不产生类构造器。
4,子类引用父类静态变量,仅父类初始化,子类不会初始化。
任重而道远
网友评论