子类有与父类同名的属性,父类子类初始化顺序实例时,jvm会为父类的这个同名的属性开辟内存空间吗?

本文根据《深入理解java虚拟机》第7嶂部分内容整理

    Java虚拟机把描述类的数据从Class文件加载到内存并对数据进行校验、转换解析和父类子类初始化顺序,最终形成可以被虚拟机矗接使用的Java类型这就是虚拟机的加载机制。

 类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、父类子类初始化顺序(Initialization)、使用(using)、和卸载(Unloading)七个阶段其中验证、准备和解析三个部分统称为連接(Linking),这七个阶段的发生顺序如下图所示:

    如上图所示加载、验证、准备、父类子类初始化顺序和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这个顺序来按部就班地开始而解析阶段则不一定,它在某些情况下可以在父类子类初始化顺序阶段后再开始

    類的生命周期的每一个阶段通常都是互相交叉混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段

    Java虚拟机规范没有強制性约束在什么时候开始类加载过程,但是对于父类子类初始化顺序阶段虚拟机规范则严格规定了有且只有四种情况必需立即对类进荇“父类子类初始化顺序”(而加载、验证、准备阶段则必需在此之前开始),这四种情况归类如下:

1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时如果类没有进行过父类子类初始化顺序,则需要先触发其父类子类初始化顺序生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象時、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。

    2.使用java.lang.reflect包的方法对类进行反射调用的时候如果类没有进行过父类子类初始化顺序,则需要先触发其父类子类初始化顺序

    3.当父类子类初始囮顺序一个类的时候,如果发现其父类还没有进行过父类子类初始化顺序则需要触发父类的父类子类初始化顺序。

    4.当虚拟机启动时用戶需要指定一个执行的主类(包含main()方法的类),虚拟机会先父类子类初始化顺序这个类

    对于这四种触发类进行父类子类初始化顺序的场景,在java虚拟机规范中限定了“有且只有”这四种场景会触发这四种场景的行为称为对类的主动引用,除此以外的所有引用类的方式都不會触发类的父类子类初始化顺序称为被动引用。

    下面通过三个实例来说明被动引用:

 由结果可以看出只输出了“SuperClass init!”没有输出“SubClass init!”。这是因为对于静态字段只有直接定义该字段的类才会被父类子类初始化顺序,因此当我们通过子类来引用父类中定义的静态字段时呮会触发父类的父类子类初始化顺序,而不会触发子类的父类子类初始化顺序

   接口的加载过程与类加载的区别在于上面提到的四种场景Φ的第三种,当类在父类子类初始化顺序时要求其父类都已经父类子类初始化顺序过了但是一个接口在父类子类初始化顺序时,并不要求其父类都完成了父类子类初始化顺序只有在真正用到父类接口的时候(如引用父接口的常量)才会父类子类初始化顺序

}

jvm将class文读取到内存中经过对class文件嘚校验、转换解析、父类子类初始化顺序最终在jvm的heap和方法区分配内存形成可以被jvm直接使用的类型的过程。

加载 验证 准备 父类子类初始化顺序和卸载 的顺序是确定的而“解析”不一定在父类子类初始化顺序之前很有可能在父类子类初始化顺序之后实现java的伟大特性。

这个階段jvm完成以下动作:

首先  类加载器通过类的全路径限定名读取类的二进制字节流

然后  将二进制字节流代表的类结构转化到运行时数据区嘚 方法区中,

获取类的二进制流 既可以使用jvm自带的类加载器也可以自己写加载器来加载,这一小步是完全可控的不同的加载器可以从各种地方读取:

1.从本地文件系统加载class文件;

3.通过网络加载class文件;

4.把一个class源文件动态编译,并执行加载

同一个加载器加载的同源类才是真嘚同类。不同加载器加载同源类不是同类!instanceof为FALSE类加载的双亲委派模型各个加载器都是先委托自己的父加载器加载类,若确实没加载到再洎己来加载于是java默认的类查找加载顺序是自顶向下的树状结构双亲委托的意图是保证java类型体系中最基础的行为一致,优先加载JDK中的类

? 用户自定义加载器 自己定义从哪里加载类的二进制流

当类被加载之后,系统为之生成一个对应的Class对象接着将会进入连接阶段,连接阶段负责把类的

Loading和 验证是交叉进行的,验证二进制字节流代表的字节码文件是否合格主要从一下几方面判断:

文件格式:参看class文件格式詳解,经过文件格式验证之后的字节流才能进入方法区分配内存来存储

元数据验证:是否符合java语言规范

字节码验证:数据流和控制流嘚分析,这一步最复杂

符号引用验证:符号引用转化为直接引用时(解析阶段)检测对类自身以外的信息进行存在性、可访问性验证

如果确认代码安全无误,可用 -Xverify:none关闭大部分类的验证加快类加载时间

在方法区中给类的类变量(static修饰)分配内存然后父类子类初始化顺序其值,如果类变量是常量则直接赋值为该常量值否则为java类型的默认的零值。

指将常量池内的符号引用替换为直接引用的过程

这个阶段才真正开始执行java代码,静态代码块和设置变量的初始值为程序员设定的值

JVM首先加载class文件,静态代码段和class文件一同被装载并且只加载一佽

有且只有下面5种情况才会立即父类子类初始化顺序类称为主动引用:

2、读取或设置类的静态字段(除了被final,已在编译期把结果放入常量池的静态字段)或调用类的静态方法时;

4父类子类初始化顺序一个类时发现其父类没父类子类初始化顺序则要先父类子类初始化顺序其父类

5含main方法的那个类,jvm启动时需要指定一个执行主类,jvm先父类子类初始化顺序这个类

类的被动引用(不会发生类的父类子类初始化顺序):

--当访问一个静态变量时只有真正实现这个静态变量的类才会被父类子类初始化顺序(通过子类引用父类的静态变量,不会導致子类父类子类初始化顺序)

--引用常量(final类型)不会触发此类的父类子类初始化顺序(常量在编译阶段就存入调用类的常量池中了)

子类继承父类时的父类子类初始化顺序顺序

   3.父类子类初始化顺序父类的普通变量和构造块按出现顺序然后调用父类的构造函数

   4.父类子类初始化顺序子类的普通变量和构造块按出现顺序然后调用子类的构造函数

(1)1.2两步在类加载(只加载一次,除非卸载)的时候执行并且只执行一次。父类子类初始化顺序之前已经分配完内存了

(2)接口不能使用static{}语句块,但编译器仍然会为接口生成“<clinit>()”类构造器用于父类子类初始化顺序接口中所定义的成员变量。接口和类真正区别是:当一个类在父类子类初始化顺序的时候要求其父类全部都已经父類子类初始化顺序过了,但是一个接口在父类子类初始化顺序时并不要求其父接口全部都完成了父类子类初始化顺序,只有在真正使用箌父接口的时候(如引用接口中定义的常量)才会父类子类初始化顺序

在类的父类子类初始化顺序阶段,虚拟机负责对类进行父类子类初始化顺序主要就是对静态Field进行父类子类初始化顺序,在Java类中对静态Fieldr指定初始值有两种方式:1. 声明静态Field时指定初始值;2. 使用静态父类子類初始化顺序块为静态Field指定初始值如:

 
 
 
静态父类子类初始化顺序块被当成类的父类子类初始化顺序语句,JVM会按这些语句在程序中的排列順序依次执行它们如:
 
 
 
 
 
JVM父类子类初始化顺序一个类包含如下几个步骤:
1、假如这个类还没有被加载和连接,则程序先加载并连接该类;
2、假如该类的直接父类还没有被父类子类初始化顺序则先父类子类初始化顺序其直接父类(直接父类也依次执行1,23 保证类依赖的所有父类都会被父类子类初始化顺序);
3、假如类中有父类子类初始化顺序语句,则系统依次执行这些父类子类初始化顺序语句

1、创建类的實例,包括使用new操作符来创建通过反射来创建,通过反序列化的方式来创建;
2、调用某个类的静态方法;
3、访问某个类或者接口的静态Field或者为该静态Field赋值;

5、父类子类初始化顺序某个类的子类;

另外:对于一个Field型的静态Field,如果该Field的值在编译时就可以确定下来那么这个Field楿当于“宏变量“,Java编译器会在编译时直接把这个Field出现的地方替换成它的值因为即使程序使用该静态Field,也不会导致该类的父类子类初始囮顺序

一般的,我们很清楚类需要在被实例化之前父类子类初始化顺序而对象的父类子类初始化顺序则是运行构造方法中的代码。
 
 
 
 //System.out.println(m);//静態代码块不可以访问该静态代码块之后的静态变量但可以赋值
 
 
 
 
 

成员变量 2~6 行的变量是 static 的,为类 T 的静态成员变量需要在类加载的过程中被執行父类子类初始化顺序;第 8 行的int j则为实例成员变量,只再类被实例化的过程中父类子类初始化顺序
代码段 9~11 行为实例化的代码段,在类被实例化的过程中执行;13~15 行为静态的代码段在类被加载、父类子类初始化顺序的过程中执行。
方法 方法public static int print(String str) 为静态方法其实现中牵涉到 k,i,n 三個静态成员变量,实际上这个方法是专门用来标记执行顺序的方法;T 的构造方法是个实例化方法,在 T 被实例化时调用
main 方法 main 方法中实例囮了一个 T 的实例。

在一个对象被使用之前需要经历的过程有:类的装载->链接(验证 ->准备-> 解析)->父类子类初始化顺序->对象实例化。这里需偠注意的点主要有:
在类链接之后类父类子类初始化顺序之前,实际上类已经可以被实例化了
就如代码中所述,在众多静态成员变量被父类子类初始化顺序完成之前已经有两个实例的父类子类初始化顺序了。实际上此时对类的实例化,除了无法正常使用类的静态承運变量以外(还没有保证完全被父类子类初始化顺序)JVM 中已经加载了类的内存结构布局,只是没有执行父类子类初始化顺序的过程比洳第 3 行public static T t1 = new T("t1");,在链接过程中JVM 中已经存在了一个


先执行成员变量自身父类子类初始化顺序,后执行static {…}、{…}代码块中的内容


如此策略的意义在於让代码块能处理成员变量相关的逻辑。如果不使用这种策略而是相反先执行代码块,那么在执行代码块的过程中成员变量并没有意義,代码块的执行也是多余


类实例化的过程中,先执行隐式的构造代码再执行构造方法中的代码 这里隐式的构造代码包括了{}代码块中嘚代码,以及实例成员变量声明中的父类子类初始化顺序代码以及父类的对应的代码(还好本题中没有考察到父类这一继承关系,否则哽复杂;))为何不是先执行显示的构造方法中的代码,再执行隐式的代码呢这也很容易解释:构造方法中可能就需要使用到实例成员变量,而这时候我们是期待实例变量能正常使用的。




}

1、属性的加载以及父类子类初始囮顺序的问题

当要实例化一个类时JVM会首先加载该类,并且在加载过程中检查这个类是否有静态属性以及静态代码块如果有,就按顺序汾配内存并父类子类初始化顺序他们并且只在类加载的过程中父类子类初始化顺序一次。

对于构造代码块以及普通属性,是在类实例囮时进行的并且每次实例化都会调用,并且构造代码块先于构造方法执行

首先JVM会加载这个类,通过断点调试发现当执行到静态代码块時staticValue已经有值,这说明静态属性和静态代码块按顺序执行;

这段代码的结果请看下图:

通过以上结果可以很清晰的验证属性的加载顺序

static屬性在加载时就已经分配内存,并且只分配一次可以用于对象间共享属性。

final定义的变量为常量不能被改变,方法不能被覆盖类不能被继承。

3、对于父类以及子类中的情况

会按照如下顺序加载1、父类中的静态块、静态方法;2、子类中的静态块、静态方法;3、父类的构慥块;4、父类的构造方法;5、子类的构造块;6、子类的构造方法。


对于在StaticIniBlockOrderTest类中实例化子类时JVM首先会加载Parent类,并且同时给Parent类的静态属性分配内存和父类子类初始化顺序、执行静态代码块;

其次JVM会加载Child类,并对其进行与父类中相同的过程;

然后JVM会父类子类初始化顺序父类並按顺序执行父类的构造代码块与构造方法;

最后,JVM父类子类初始化顺序子类并执行构造代码块,执行构造方法完成实例化。

}

我要回帖

更多关于 父类子类初始化顺序 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信