java类的加载与初始化_java类的加载与初始化总结

1、触发类加载的原因(主动调用与被动调用):

六种主动调用:

1)、创建类的实例(new操作、反射、cloning、反序列化)

2)、调用类的静态方法

3)、使用或对类/接口的static属性赋值(不包括static final的与在编译期确定的常量表达式(包括常量、字符串常量))

4)、调用API中的反射方法,Class.forName()等。

5)、子类被初始化

6)、被设定为JVM启动时的启动类(含main方法的主类)

其它都为被动引用:被动引用不会触发类的初始化操作(只会加载、链接),如仅申明一个类的引用、通过数组定义引用类等。

2、类的加载的完整生命过程

加载、链接(验证、准备、解析)、初始化、使用、卸载

1)、加载

i)、java编译器加载类的二进制字节流文件(.class文件),如果该类有基类,向上一直加载到根基类(不管基类是否使用都会加载)。

ii)、将二进制字节码加载到内存,解析成方法区对应的数据结构。

iii)、在java逻辑堆中生成该类的java.lang.Class对象,作为方法区中该类的入口。

类加载器:分默认加载器和用户自定义加载器

Bootstrap ClassLoader:顶层加载器,由c++实现。负责JVM启动时加载JDK核心类库以及加载后面两个类加载器。

Extension ClassLoader:继承自ClassLoader的类,负责加载{JAVA_HOME}/jre/lib/ext目录下的所有jar包。

App ClassLoader:上面加载器的子对象,负责加载应用程序CLASSPATH目录下的class文件和jar包。

Customer ClassLoader:用户继承自ClassLoader类的自定义加载器,用来处理特殊加载需求。如Tomcat等都有自己实现的加载器。

类加载器采用双亲委托(自底向上查询)来避免重复加载类,而加载顺序却是自顶向下的。

2)、链接

i)、验证:字节码完整性、final类与方法、方法签名等的检查验证。

ii)、准备:为静态变量分配存储空间(内存单元全置0,即基本类型为默认值,引用类型为null)。

iii)、解析(这步是可选的):将常量池内的符号引用替换为直接引用。

类的加载和链接只执行一次,故static成员也只加载一次,作为类所拥有、类的所有实例共享。

3)、初始化

包括类的初始化、对象的初始化。

类的初始化:

初始化静态字段(执行定义处的赋值表达式)、执行静态初始化块。

注:有父类则先递归的初始化父类的。

对象的初始化:

如果需要创建对象,则会执行创建对象并初始化:

i)、在堆上为创建的对象分配足够的存储空间,并将存储单元清零,即基本类型为默认值,引用类型为null。

i)、初始化非静态成员变量(即执行变量定义处的赋值表达式)。

ii)、执行构造方法。

注:如果有父类,则先递归的初始化父类成员,最后才是本类。

4)、使用

5)、卸载

对象的引用(栈中)在超出作用域后被系统立即回收;对象本身(堆中)被gc标记为垃圾,在gc下次执行垃圾处理时被回收。

总结:

一个类最先初始化static变量和static块;

然后分配该类以及父类的成员变量的内存空间,再赋值初始化,最后调用构造方法;

在父类与子类之间,总是优先创建、初始化父类。

即:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器,其中基类总是优先于子类的。

详细分析例题:

1、static静态成员初始化细节

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classTest8 {2

3 public static voidmain(String[] args) {4 System.out.println(Super.a);5 System.out.println(Super.b);6 System.out.println(Super.bb);7 System.out.println(Super.c);8 System.out.println(new Super("cc").c);9 //对于“初始化”的意思,应该包括初始化和执行赋值表达式(如果有的话)。例如static成员的初始化实际上包括两步:准备阶段(JVM链接)中的内存分配(全置0,即基本类型成默认值,引用类型为null)和初始化中的static成员赋初值操作(即如果有的话,执行static字段定义处的赋值表达式)10 //对于a、b 因为有赋初值的表达式,故会得到自定义的初始值。对于c则采用准备阶段的null。11 //只有创建对象才会调用构造方法(执行构造方法中的动作)。b的值被重新设置。

12

13 Super sup2 = new Super("ccc"); //相当于每次新建对象都对'实例所共享、类所有的'b重新设值(是重新给b赋值,不是新建)。

14 System.out.print(Super.c);15 }16

17 }18

19

20 classSuper{21 staticString a;22 static String b =getB();23 static String bb =getC();24 static String c = "c";25

26 Super(String s){27 c =s;28 }29

30 staticString getC(){31 returnc;32 }33

34 staticString getB(){35 return "b";36 }37 }

View Code

2、‘单例模式’中静态成员初始化问题

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classTest9 {2

3 public static voidmain(String[] args) {4 System.out.println(Single.b);5 System.out.println(Single.c);6 //在Single类加载的链接阶段静态字段都置默认值(基本类型为默认,引用为null),所以sin、b、c首先为null、0、0。7 //然后按照定义的顺序执行初始化赋值,先执行sin的赋值,因为用new创建对象,所以会执行构造方法,然后b=1,c=1。这时因为static字段只加载一次,所以b、c只是做赋值操作(有赋值表达式的话),所以b没操作,c重新赋值为0。8

9

10 //那么,如果交换静态字段sin和c的位置,上面输出?

11 }12

13 }14

15

16 classSingle{17 private static Single sin = newSingle();18 public static intb;19 public static int c = 0;20

21 privateSingle(){22 b++;23 c++;24 }25

26 publicSingle getInstance(){27 returnsin;28 }29 }

View Code

3、构造方法内的多态对初始化的影响

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classTest10 {2

3 public static voidmain(String[] args) {4 newSubTest();5

6 //输出为:SuperTest() before draw()7 //SubTest() ,i = 08 //SuperTest() after draw()9 //SubTest() ,i = 110

11 //分析:因为没有静态成员,所以在用new创建子类对象时,先在堆中为该对象分配足够空间(内存空间全置二进制的0,即基本类型为默认值,引用类型为null),12 //然后,调用父类构造方法(有实例变量会先初始化实例变量),但draw()调用的是子类的重写方法,那么问题是,这时候子类实例变量i只分配了内存空间(默认值为0),13 //还没有初始化,所以输出的i为0。直到子类初始化实例变量时,i才被赋值为1,最后执行子类的构造方法,所以输出i为1。

14 }15

16 }17

18

19 classSuperTest{20 SuperTest(){21 System.out.println("SuperTest() before draw()");22 draw(); //调用子类的重写方法(多态)

23 System.out.println("SuperTest() after draw()");24 }25 voiddraw(){26 System.out.println("super draw");27 }28

29 }30

31 class SubTest extendsSuperTest{32 private int i = 1;33 SubTest(){34 System.out.println("SubTest() ,i = "+i); }35 @Override36 voiddraw(){37 System.out.println("SubTest() ,i = "+i);38 }39 }

View Code

总结:类的加载与初始化顺序上面已经总结了。但实际判断时任然需要谨慎。

i)、对于很多书上说的和大家挂在嘴边的“初始化”一词,如‘初始化‘静态变量、‘初始化’实例变量。这里的初始化我的更细入的理解是,‘初始化’包括“分配内存空间”和“执行赋值表达式”两步。

ii)、“分配内存空间”,即将获取到的内存单元全部置为二进制的0(对于基本类型自然就是默认值,对于引用类型都为null),而这一步是不管变量定义处的赋值表达式的。如int a ; int b =1; 在这一步都是一样置为二进制的0的。

“执行赋值表达式”,即是在变量“分配内存空间”后对变量的赋值操作。如 int a;int b =1; 在这一步a没有赋值操作,b就有赋值操作了,然后a依然还是分配内存空间后的默认值,而b就重新赋值为1了。

iii)、“初始化”即先分配内存空间,再对变量执行赋值表达式(如果有的话)。这样分先后的意义保证了对变量的赋值前,变量已经获取到了正确的初始内存空间。如static变量的初始化,实际上在’准备阶段‘就分配好内存单元,

在’初始化阶段‘的第一步才执行定义处的赋值表达式。这就是例一中考察的重点,在分配内存空间后与执行定义处的赋值操作后得到的值不一样。又如实例变量的初始化,他的所谓“初始化”也是分两个阶段的,不过他的两个

阶段间相隔的操作不多,所以当作一个概念通常不会出问题,但遇到例三的情况就出问题了。参考《Thinking In Java》中的建议就是“尽量在构造方法中慎用非final或private(隐式为final)方法”

iiii)、对于我的理解把“初始化”细化为“分配内存空间”和“执行赋值表达式”两步,其实也挺纠结的。’分配内存空间’即包括内存空间的初始分配,然后变量也自然得到初始值了(对于基本类型自然就是默认值,对于引用类型都为null),

这不就是“初始化”的意思嘛?而“执行赋值表达式”更像是用户根据自己的程序需要设置自定义的初始值,而不是分配内存空间后的默认值(这应该就是通常意义的“初始化”了吧)。而这个设置自定义初始值的行为,

即可以是在变量的定义处,也可以是在构造方法中,或者在需要时刻的方法调用中(惰性初始化)。而这种设置自定义初始值的行为的正确保证,就是上面总结的“类的加载与初始化顺序”的严格顺序执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值