java继承关系中类加载及初始化

版权声明:见贤思齐焉 https://blog.csdn.net/weixin_28906733/article/details/82710327

开发中碰到在继承体系中基类与派生类之间的类加载、初始化及形参引用问题,在此假定有两个模块,派生类OrderModule与基类Module:

/**
 * Created by ruyin on 2018/9/14.
 * 
 * 继承关系下的类加载以及初始化
 * 
 */
public class Module {
    //唯一ID
    protected String domainId = "2018xxxxSD";
    //状态,派生类也存在同样参数
    protected int state;

    public Module(){
        System.out.println("DomainId:" + domainId + ", state:" + state);

        state = 100;
    }

    private static long moduleInvokeTime = logInvokeTime("static Module init!");

    //静态变量和方法在对象加载之后,对象实例化之前初始化
    static long logInvokeTime (String mark){
        System.out.println(mark);
        return System.nanoTime();
    }

    //形参类型引用
    static void logCreateTime (Module module){
        System.out.println("DomainId:" + module.domainId + ", state:" + module.state + ", createTime:" + System.nanoTime());
    }
}

/** 派生类型 */
class OrderModule extends Module {

    protected long state;

    private long orderModuleInvokeTime1 = logInvokeTime("OrderModule init!");

    public OrderModule(){
        System.out.println("OrderModuleInvokeTime = " + orderModuleInvokeTime1);
        System.out.println("ModuleState = " + state);
    }

    private static long orderModuleInvokeTime2 = logInvokeTime("OrderModule init!");

    public static void main(String[] args) {
        System.out.println("OrderModule constructor");
        //1、验证类的加载动作只发生一次,并且可知道在类的的第一个对象被创建或者对static成员访问均可能引起类的加载
        //System.out.println("OrderModule constructor");
        /*
            static Module init!
            OrderModule init!
            OrderModule constructor
            OrderModule constructor
        */

        //2、验证类的静态成员在加载的时候就已经初始化完毕,对应多态中动态绑定概念:类的静态变量、方法与类绑定而不与实例对象绑定,
        //   这时候也不会发生对象实例的创建,全部取的jvm方法区、本地方法区中常量池参数
        //System.out.println(OrderModule.orderModuleInvokeTime2);
        /*
             static Module init!
             OrderModule init!
             OrderModule constructor
             658006488350710
         */

        //3、验证类的加载以及基类派生类的实例化
        //OrderModule orderModule = new OrderModule();
        /*
            static Module init!
            OrderModule init!
            OrderModule constructor
            DomainId:2018xxxxSD, updateTime:0
            OrderModule init!
            OrderModuleInvokeTime = 658330288195983
            ModuleUpdateTime = 658330288168887
        */


        //4、与3一起使用可看出在OrderModule背后还存在一个基类Module实例,在基类与派生类参数无异议情况下派生类继承的属性地址与基类相同,
        //反之则不同,在此例中state属性基类与派生类均存在并且方法logCreateTime()的形参是Module,故使用基类中的属性值
        //orderModule.domainId = "2018xxxxDS";
        //orderModule.state = 200;
        //logCreateTime(orderModule);
        /*
            static Module init!
            OrderModule init!
            OrderModule constructor
            DomainId:2018xxxxSD, state:0
            OrderModule init!
            OrderModuleInvokeTime = 659099346250147
            ModuleState = 0
            DomainId:2018xxxxDS, state:100, createTime:659099346463742
        */
    }
}

以下是针对上面执行结果的解释:

  1. OrderModule类中通过main()函数运行java时,加载器开始启动并找出OrderModule类的编译代码OrderModule.class。在对其进行加载过程中发现其还有基类(extends),于是继续加载基类,不管加载的类存不存在继承关系该动作总是会触发。如果基类还存在继承,则被继承的基类继续加载,以此类推。紧接着基类中的static初始化将会被执行,然后是派生类中的static字段或方法,以此类推。至此必要的类就全部加载完毕;
  2. 通过前一步类已经全被加载,此时调用静态变量(不存在赋值操作)或方法将不会触发对象的创建,获取到的参数也仅仅是方法区及本地方法区中的参数;
  3. 此时对象就可以被创建了,首先对象中所有的基本类型都会被设置为默认值,对象引用被设置为null–通过将对象内存地址设置为二进制零值。然后基类的构造器将会被调用,可以默认调用也可通过super关键字指定对基类构造器的调用。基类构造器和派生类构造器一样以相同的顺序经历相同的流程。在基类构造器完成之后,实例变量按其次序依次被初始化,之后构造器的其余部分被执行;
  4. 同3;

java运行时内存结构:
方法区、栈内存、堆内存、本地方法区几种

java对象初始化jvm层面理解:

Java虚拟机规范中并没有强制约束何时开始类加载过程,但对于初始化阶段,虚拟机规范严格规定了有且只有5中情况必须立即对类进行”初始化”(加载、验证、准备自然在此之前):

  1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指定的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候;
  2. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指定的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候;
  3. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化;
  4. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;
  5. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类;
  6. 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;

可在程序启动时添加以下参数查看类加载的库及其顺序:
-XX:+TraceClassLoading


参考书籍:周志明 《深入理解Java虚拟机》

猜你喜欢

转载自blog.csdn.net/weixin_28906733/article/details/82710327