Java 对象初始化详细过程
一、进行类初始化
java类是什么时候初始化的
- 遇到new、get static 和 put static 或 invoke static 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应场景是:使用 new 实例化对象、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法。
- 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
- 虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
发生如上情况,Java类加载器就会去找类的路径,定位已经编译好的 *.class 文件。
二、获得类的资源
然后 jvm 就会载入 *.class,生成一个 class 对象。这个时候如果有静态的方法或者变量,静态初始化动作就会被执行。这个时候要注意了,静态初始化在程序运行过程中只会在 Class 对象首次加载的时候运行一次。这些资源都会放在 jvm 的方法区。
方法区又叫静态区,跟堆一样,被所有的线程共享。
方法区中包含的都是在整个程序中永远唯一的元素,包含所有的 class 和 static 变量。
Java中静态变量(类变量)、普通代码块、静态代码块和构造函数辨析
①静态代码块在类被加载的时候就运行了,而且只运行一次,拥有最高优先级(先父类再子类),静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。
②子类构造方法的第一行存在一个隐式的——super();
- 如果父类中没有定义空参数构造函数,那么子类的构造函数必须用super明确要调用父类的哪个构造函数并且super语句必须要定义在子类构造函数的第一行。因为父类的初始化动作要先完成。
- 当一个类中没有定义构造函数时,系统会给该类中加一个默认的空参数的构造函数,方便该类初始化。只是该空构造函数是隐藏不见的。
- 当在该类中自定义了构造函数,默认构造函数就没有了。如果仍要构造函数,需要在类中手动添加。
- 如果子类构造函数中如果使用this调用了本类构造函数时,那么super就没有了,因为super和this不能同时出现在同一构造函数中,但是可以保证的是,此this调用的构造函数肯定会访问父类的构造函数。
③代码块在编译后实际上是被放到了构造方法中,且是放在了构造方法的第一句
④如果类已经被加载,则静态代码块和静态变量就不用重复执行
class TestTwo{
public TestTwo(){
System.out.println("父类构造方法");
}
{
System.out.println("父类代码块");
}
static {
System.out.println("父类静态代码块");
}
public static void find(){
System.out.println("静态方法");
}
}
public class TestOne extends TestTwo{
public TestOne(){
System.out.println("子类构造方法");
}
{
System.out.println("子类代码块");
}
static {
System.out.println("子类静态代码块");
}
public static void main(String[] args) {
new TestOne();
}
}
输出结果:
父类静态代码块
子类静态代码块
父类代码块
父类构造方法
子类代码块
子类构造方法
三、初始化对象 Dog dog = new Dog()
- 1.第一次创建 Dog 对象先执行上面的一二步
- 2.在堆上为 Dog 对象分配足够的存储空间,所有属性和方法都被设置成默认值(数字为 0,字符为 null,布尔为 false,而所有引用被设置成 null;注意方法内部定义的基本类型是不会默认赋值)
- 3.执行构造函数检查是否有父类,如果有父类会先调用父类的构造函数。
- 4.执行构造函数。
- 5.把这个对象指向这个空间
总结:
如果Dog类没有进行过类初始化,首先进行类初始化,Java类加载器去找Dog类的路径,定位已经编译好的 Dog.class 文件,然后 jvm 就会载入 Dog.class,生成一个Dog的class对象。在此过程中,Dog类有静态的方法或者变量的话,进行静态初始化(静态初始化在程序运行过程中只会在 Class 对象首次加载的时候运行一次)。最后,分配内存空间(实例化对象),执行构造方法(初始化对象),把对象的地址赋值给引用dog