关于类加载、对象生成小记

关于类加载、对象生成小记

Scroll Down

类加载

  1. 类加载的流程

  1. 加载

通过加载器将字节流读入内存。

  1. 验证

验证当前读入的class文件字节流是否符合字节码标准,如版本号符合不之类的。

  1. 准备

将类中定义的 static 属性初始化0值,如果使用了final修饰,会直接初始化为我们指定的值。

  1. 解析

将符号引用全部替换为直接引用。

  1. 初始化

执行 <clinit> 方法,即我们在类中定义的 static 语句块,且执行的顺序和我们的定义顺序一致,注意,我们可以理解将所有的 static 块按顺序收拢到了一个 static 块中!。

  1. 五种会触发初始化的 主动引用 情况

  • 遇到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...

对象创建

  1. 大致流程

  • 加载:碰到new等时,判断当前方法区是否有我们需要使用的类的 class 对象,如果没有,则通过类加载器进行加载,流程如上面....(注意,会涉及对父类的加载)
  • 分配空间:因为在加载完一个类之后,我们就知道了它所需要的空间,所以我们需要在堆上分配该大小的空间,主要采用 指针碰撞 空闲列表 的方式,但是因为涉及线程安全的问题,所以需要采用 CASTLAB 解决。
  • 初始化零值:该步骤是保证类中的实例变量可以不初始化就使用的步骤。
  • 设置对象头:对象头包含两部分,即运行时数据(markword)和类型指针两部分。
  • 执行 <init> 方法,按我们的需求初始化对象(构造函数)。
  1. 举例

1.以上文的代码段举例,当我们执行 new Sub() 时的流程。
2.当遇到 new Sub() 时,先去方法区寻找能不能找到它的符号引用,能找到则执行下一步,不能,则执行上文提到的类加载。当执行到类加载的初始化的时候,会发现 Father 没加载,则加载 Father (递归加载)。然后会先执行 Father 的 static 语句块,其次是 Sub 的 static 语句块。(由于 Father 的父类 Object 早就加载加载和初始化了的,不提了)
3.Sub 和 Father 都加载和初始化完毕,最后到执行 方法了,会先调用父类的 ,所以 Father 的会先被调用,再其次是Sub的,即构造结束。

以上就是大致流程。