类加载
-
类加载的流程
- 加载
通过加载器将字节流读入内存。
- 验证
验证当前读入的class文件字节流是否符合字节码标准,如版本号符合不之类的。
- 准备
将类中定义的 static 属性初始化0值,如果使用了final修饰,会直接初始化为我们指定的值。
- 解析
将符号引用全部替换为直接引用。
- 初始化
执行 <clinit> 方法,即我们在类中定义的 static 语句块,且执行的顺序和我们的定义顺序一致,注意,我们可以理解将所有的 static 块按顺序收拢到了一个 static 块中!。
-
五种会触发初始化的 主动引用 情况
- 遇到new、getstatic、putstatic、invokestatic等指令时,如果类没初始化,则会触发初始化。
- 当使用反射API调用一个类时,如果没有初始化,则也会触发初始化。
- 当初始化一个类时,发现其父类未初始化,则需要先初始化其父类。
- 当虚拟机启动时,用户需要一个需要执行的主类,即包含 main 方法的类,虚拟机会最先初始化该类。
- 与jdk1.7相关,不说明
//父类
class Father
{
//静态字段
public static int val=10;
static
{
System.out.println("Father init");
}
public Father()
{
System.out.println("Father constructer...");
}
}
class Sub extends Father
{
static
{
System.out.println("Sub init");
}
public Sub()
{
System.out.println("Sub constructer...");
}
}
- 不会执行当前类初始化的情况
System.out.println(Sub.val);
//输出
Father init
10
/**
* 原因应该是编译优化,Sub引用了Father的一个static变量,并且该变量不涉
* 及修改啥的,不存在什么依赖,所以就相当于直接引用了 Father.val 而不是
* Sub.val
*/
- 正常情况
new Sub();
//输出
Father init
Sub init
Father constructer...
Sub constructer...
对象创建
-
大致流程
- 加载:碰到new等时,判断当前方法区是否有我们需要使用的类的 class 对象,如果没有,则通过类加载器进行加载,流程如上面....(注意,会涉及对父类的加载)
- 分配空间:因为在加载完一个类之后,我们就知道了它所需要的空间,所以我们需要在堆上分配该大小的空间,主要采用 指针碰撞 、 空闲列表 的方式,但是因为涉及线程安全的问题,所以需要采用 CAS 或 TLAB 解决。
- 初始化零值:该步骤是保证类中的实例变量可以不初始化就使用的步骤。
- 设置对象头:对象头包含两部分,即运行时数据(markword)和类型指针两部分。
- 执行 <init> 方法,按我们的需求初始化对象(构造函数)。
-
举例
1.以上文的代码段举例,当我们执行 new Sub() 时的流程。
2.当遇到 new Sub() 时,先去方法区寻找能不能找到它的符号引用,能找到则执行下一步,不能,则执行上文提到的类加载。当执行到类加载的初始化的时候,会发现 Father 没加载,则加载 Father (递归加载)。然后会先执行 Father 的 static 语句块,其次是 Sub 的 static 语句块。(由于 Father 的父类 Object 早就加载加载和初始化了的,不提了)
3.Sub 和 Father 都加载和初始化完毕,最后到执行方法了,会先调用父类的 ,所以 Father 的 会先被调用,再其次是Sub的,即构造结束。
以上就是大致流程。