创建对象的流程以及对象中各个组件的加载时机和加载顺序

目录

前言

二、类创建流程详细分析

1.加载阶段

2、链接阶段 

三、类的初始化

3.1 在下面这五种情况下类必须进行初始化

四、类的初始化顺序

总结


前言

  在使用java语言进行开发工作的时候,几乎每天都需要每天都需要使用的一条语句就是

Object object = new Object();

虽然每天都在使用,但是看似一个简单的new其实java编译器、jvm进行了大量的操作。并且类里面定义的一些变量、静态变量、内部类、静态内部类、构造方法等会在什么时刻初始化和执行呢,执行的顺序又是怎么样的呢?这篇文章就来重点的说一下类创建过程是怎么样的,类里面的成员变量或者方法的初始化或者执行时机又是如何。


一、类的生命周期

类的生命周期主要分为五步,加载、链接、初始化、使用、卸载。其中链接又包括验证、准备、解析三个步骤。加载、验证、准备、初始化的先后顺序是一定的,但是解析可以发生在初始化之后,因为java支持运行时绑定。

  Java的动态绑定是由于java存在继承和多态,所以类注入之后,调用类的方法调用的是子类重写的方法,所以初始化的时候有可能并不知道调用的是什么方法,之后运行的时候再先查看子类是否有重写方法,如果子类没有重写则继续查看父类方法,这样一层一层的动态寻找。 

二、类创建流程详细分析

1.加载阶段

加载阶段主要是将.class文件通过二进制字节流的方式读入JVM中,虚拟机需要完成一下三件事:

  1. 通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存
  2. 将字节流所代表的静态存储结构转化为方法区的运行时数据结构。包括类的全限定名,类的静态变量以及方法
  3. 在内存中生成一个该类的Class对象,该对象有指向方法区的类的数据结构的指针。一个类指挥产生一个Class对象,且Class对象存储在堆中。 

2、链接阶段 

  类的链接阶段分为三步:

  1. 验证:用于检验被加载的类是否有正确的内部结构,比如:是否实现了父类的抽象方法、是否重写了父类的final修饰的方法、通过符号引用是否能找到对应的类和方法
  2. 准备:为类的静态变量分配内存并赋默认值。对于static修饰的变量或者final static修饰的非字面值静态常量赋默认值,对于final static修饰的静态字面值常量直接赋初值
  3. 解析:将类的二进制数据中的符号引用替换成直接引用。符号引用使用一组符号描述所引用的目标,这个目标不一定是以及存在内存中的;直接引用则是直接指向目标的指针,是在内存中正式存在的。

三、类的初始化

  类在初始化之前必须以及加载、验证、准备完成之后。

类的初始化主要执行的操作为:静态变量的初始化(为静态变量赋初始值。因为在链接的准备阶段static修饰的变量以及final static修饰的非字面值静态常量赋的是默认值)、静态代码块的执行。执行顺序则跟在代码中出现的顺序决定。

  在编译生成.class文件时,编译器会产生两个方法加于class文件中:clinit方法init方法

  • clinit方法:是Class类的构造方法,主要是在初始化的时候执行,完成静态变量的赋值和静态代码块的执行
  • init方法:主要作用是在类实例化的时候执行,执行内容包括:成员变量的初始化和构造代码块的执行

3.1 在下面这五种情况下类必须进行初始化

  1. 创建类的实例,也就是new一个对象;获取类的静态变量或者静态非字面值常量
  2. 调用类的静态方法
  3. 通过反射获取类的Class对象(Class.forName("xxx"))
  4. 初始化类的字类
  5. 启动程序所使用的main方法所在类

四、类的初始化顺序

public class DemoParent {

    static {
        System.out.println("父类静态代码块!");
    }

    public DemoParent(){
        System.out.println("父类构造方法!");
    }
}

子类类

package com.example.test;

public class DemoSon extends DemoParent {

    private int a=1;

    /**
     *  静态变量
     */
    private static int b=1;

    /**
     *  静态字面值常量
     */
    private final static int c=1;

    /**
     *  静态非字面值常量
     */
    private final static Integer d=new Integer(1);

    private final static DemoMember member = new DemoMember();

    {
        System.out.println("构造代码块执行:a="+a+",b="+b+",c="+c+",d="+d+",e="+e);
    }

    static {
        System.out.println("静态代码块执行:"+"b="+b+",c="+c+",d="+d);
    }

    /**
     *  静态非字面值常量
     */
    private final static Integer e=new Integer(1);


    /**
     *  构造方法
     */
    public DemoSon() {
        System.out.println("构造方法执行:a="+a+",b="+b+",c="+c+",d="+d+",e="+e);
    }

    class innerClass {

        public innerClass() {
            System.out.println("内部类构造方法执行");
        }
    }

    static class staticInnerClass {

        public staticInnerClass() {
            System.out.println("静态内部类构造方法执行");
        }
    }

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public static int getB() {
        return b;
    }

    public static void setB(int b) {
        DemoSon.b = b;
    }

    public static int getC() {
        return c;
    }

    public static Integer getD() {
        return d;
    }

    public static Integer getE() {
        return e;
    }

    public static DemoMember getMember() {
        return member;
    }
}

 

public class DemoMember {

    public DemoMember() {
        System.out.println("DemoMember的构造方法执行");
    }

    static {
        System.out.println("DemoMember的静态代码块执行");
    }
}

在当前DemoSon类中写一个main方法

public static void main(String[] args) {
        System.out.println();
    }

 执行结果为:

父类静态代码块!
DemoMember的静态代码块执行
DemoMember的构造方法执行
静态代码块执行:b=1,c=1,d=1

这就证明 启动程序main方法所在的类必须执行初始化,执行顺序是先执行父类的静态代码块再执行子类静态代码块,并且在类初始化的时候静态非字面值也会进行赋值操作,因为此时DemoMember也实例化了。

public static void main(String[] args) {
        DemoSon demoSon = new DemoSon();
        System.out.println("=================================");
        DemoSon demoSon1 = new DemoSon();
        System.out.println("=================================");
    }

执行结果为:

父类静态代码块!
DemoMember的静态代码块执行
DemoMember的构造方法执行
静态代码块执行:b=1,c=1,d=1
父类构造方法!
构造代码块执行:a=1,b=1,c=1,d=1,e=1
构造方法执行:a=1,b=1,c=1,d=1,e=1
=================================
父类构造方法!
构造代码块执行:a=1,b=1,c=1,d=1,e=1
构造方法执行:a=1,b=1,c=1,d=1,e=1

 new也会执行初始化,并且初始化之后生成Class对象,之后创建对象的时候就不会在进行初始化操作。先调用父类的构造方法再调用字类的构造方法。从运行结果也可以看出,内部类和静态内部类不会进行初始化和实例化。

总结

  本文介绍了类的加载过程,以及类初始化和实例化的流程以及流程的执行顺序。大概可以总结为:类进行实例化之前会先进行加载,加载过程会产生Class对象并且进行Class对象的初始化给静态变量赋值并且调用静态代码块,如果存在父类则会先调用父类的静态代码块。实例化对象的时候会先调用父类的构造方法再调用字类的构造方法。Class类只会初始化一次,所以初始化Class对象的时候所执行的操作只会执行一次,所以无论创建多少个对象,他的静态代码块只会执行一次,并静态变量也是一样的。

本文参考:类的加载机制

                  new一个对象的过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值