2019独角兽企业重金招聘Python工程师标准>>>

Java虚拟机通过装载、连接、初始化来使得一个Java类型可以被Java程序所使用,如下图所示,其中连接过程又分为验证、准备、解析三个部分。其中部分类的解析过程可以推迟到程序真正使用其某个符号引用时再去解析。

解析过程可以推迟到类的初始化之后再进行,但这是有条件的,Java虚拟机必须在每个类或接口主动使用时进行初始化。
以下为主动使用的情况:
(1).(无论直接通过new创建出来的,还是通过反射、克隆、序列化创建的)创建某个类新的实例
(2).使用某个类的静态方法
(3).访问某个类或接口的静态字段
(4).调用JavaAPI中的某些反射方法
(5).初始化某个类的子类(要求其祖先类都要被初始化,否则无法正确访问其继承的成员)
(6).启动某个标明为启动类的类(含有main()方法)
主动使用会导致类的初始化,其超类均将在该类的初始化之前被初始化,但通过子类访问父类的静态字段或方法时,对于子类(或子接口、接口的实现类)来说,这种访问就是被动访问,或者说访问了该类(接口)中的不在该类(接口)中声明的静态成员。
如:
Grandpa的定义如下:

1
2
3
4
5
6
7
packagecom.ice.passiveaccess;
publicclassGrandpa {
static{
System.out.println("Grandpa was initialized.");
}
}

Parent的定义如下:

1
2
3
4
5
6
7
8
packagecom.ice.passiveaccess;
publicclassParentextendsGrandpa{
staticString language ="Chinese";
static{
System.out.println("Parent was initialized.");
}
}

Cindy的定义如下:

1
2
3
4
5
6
7
packagecom.ice.passiveaccess;
publicclassCindyextendsParent{
static{
System.out.println("Child was initialized.");
}
}

现在通过Cindy访问父类的language成员

1
2
3
4
5
6
7
packagecom.ice.passiveaccess;
publicclassPassiveAccessTest {
publicstaticvoidmain(String args[]){
System.out.println(Cindy.language);
}
}

结果如下:

可见这是被动访问,Cindy自身并没有初始化

下面简要介绍装载、验证与初始化过程:
1.装载:
(1).找到该类型的class文件,产生一个该类型的class文件二进制数据流(ClassLoader需要实现的loadClassData()方法)
(2).解析该二进制数据流为方法区内的数据结构
(3).创建一个该类型的java.lang.Class实例
在加载器的相关代码中可以看到,最终通过defineClass()创建一个Java类型对象(Class对象)。
2.验证:
class文件校验器需要四趟独立的扫描来完成验证工作,其中:
第一趟扫描在装载时进行,会对class文件进行结构检查,如
(1).对魔数进行检查,以判断该文件是否是一个正常的class文件
(2).对主次版本号进行检查,以判断class文件是否与java虚拟机兼容
(3).对class文件的长度和类型进行检查,避免class文件部分缺失或被附加内容。
第二趟扫描在连接过程中进行,会对类型数据进行语义检查,主要检查各个类的二进制兼容性(主要是查看超类和子类的关系)和类本身是否符合特定的语义条件
(1).final类不能拥有子类
(2).final方法不能被重写(覆盖)
(3).子类和超类之间没有不兼容的方法声明
(4).检查常量池入口类型是否一致(如CONSTANT_Class常量池的内容是否指向一个CONSTANT_Utf8字符串常量池)
(5).检查常量池的所有特殊字符串,以确定它们是否是其所属类型的实例,以及是否符合特定的上下文无关语法、格式
第三趟扫描为字节码验证,其验证内容和实现较为复杂,主要检验字节码是否可以被java虚拟机安全地执行。
第四趟扫描在解析过程中进行,为对符号引用的验证。在动态连接过程中,通过保存在常量池的符号引用查找被引用的类、接口、字段、方法时,在把符号引用替换成直接引用时,首先需要确认查找的元素真正存在,然后需要检查访问权限、查找的元素是否是静态类成员而非实例成员。
3.准备:
为类变量分配内存、设置默认初始值(内存设置初始值,而非对类变量真正地进行初始化,即类中声明int i = 5,但实际上这里是分配内存并设置初始值为0)
4.解析:
在类的常量池中寻找类、接口、字段、方法的符号引用,将这些符号引用替换成直接引用
5.初始化:
对类变量赋予指定的初始值(这个时候int i = 5就必须赋予i以初值5)。这个初始值的给定方式有两种,一种是通过类变量的初始化语句,一种是静态初始化语句。而这些初始化语句都将被Java编译器一起放在方法中。
如前面所述,一个类的初始化需要初始化其直接超类,并递归初始化其祖先类,初始化是通过调用类的初始化方法完成的。此外,对于接口,并不需要初始化其父接口,而只需要执行该接口的接口初始化方法就可以了。
注意:
(1). 在初始化阶段,只会为类变量(静态全局变量)进行初始化工作,并且当类变量声明为final类型切初始化语句采用了常量表达式方式进行初始化赋值,那么, 也不会对其进行初始化,它将会直接被编译器计算并保存在常量池中,并且对这些变量的使用也将直接将其变量值嵌入到字节码中。
如UsefulParameter类如下:

1
2
3
4
Class UsefulParameter{
staticfinalintheight =2;
staticfinalintwidth = height *2;
}

类Area的类变量初始化如下:

1
2
3
4
Class Area{
staticintheight = UsefulParameter.height *2;
staticintwidth = UsefulParameter.width *2;
}

在Area的< clinit>中,将直接把2、4嵌入到字节码中

(2).接口的初始化与类有所不同,在初始化阶段,会为在接口中声明的所有public、static和final类型的、无法被编译为常量的字段进行初始化
6.类实例化
这里需要明白什么是类初始化,什么是类实例化,以及类的实例对象的初始化
如前面所述,类初始化时对类(静态)变量赋予指定的初始值,类初始化之后就可以访问类的静态字段和方法,而访问类的非静态(实例)字段和方法,就需要创建类的对象实例,故类的实例化是在类的初始化之后,是在堆上创建一个该类的对象。
类的静态方法和字段属于类,作为类型数据保存在方法区,其生命周期取决于类,而实例方法和字段位于Java堆,其生命周期取决于对象的生命周期。
  类的初始化会从祖先类到子类、按出现顺序,对类变量的初始化语句、静态初始化语句块依次进行初始化。而对类实例的初始化也类似,会从祖先类到子类、按出现顺序,对类成员的初始化语句、实例初始化块、构造方法依次进行初始化。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
packagecom.ice.init;
publicclassParent {
publicstaticinti = print("parent static:i");
publicintii = print("parent:ii");
static{
print("父类静态初始化");
}
{
print("父类实例初始化");
}
publicParent(String str) {
System.out.println("parent constructor:"+ str);
}
publicstaticintprint(String str){
System.out.println("initial:"+ str);
returni;
}
}

子类Child如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
packagecom.ice.init;
publicclassChildextendsParent{
publicstaticinti = print("child static:i");
publicintii = print("child:ii");
static{
print("子类静态初始化");
}
{
print("子类实例初始化");
}
publicChild(String str) {
super(str);
System.out.println("Child constructor:"+ str);
}
publicstaticintprint(String str){
System.out.println("initial:"+ str);
returni;
}
publicstaticvoidmain(String args[]){
Child child =newChild("cindy");
}
}

其初始化顺序为:

Java编译器为每个类生成了至少一个实例初始化方法< init >,一个< init >方法分为三部分: 另一个初始化方法< init >(),对任意实例成员的初始化的字节码,构造方法的方法体的字节码
< init >方法的调用如下:
若< init >指明从this()方法明确调用另一个构造方法,那么将调用另一个构造方法,否则,若该类有直接超类,那么,若< init >指明从super()方法明确调用其超类的构造方法,那么将调用超类的构造方法,否则,将默认调用超类的无参构造方法。这样,将从其祖先类到该 类,分别完成对应的实例成员的初始化(可能被子类覆盖)
接下来以一道题结束本节:
判断输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
packagecom.ice.init;
classT implementsCloneable{
publicstaticintk =0;
publicstaticT t1 =newT("t1");
publicstaticT t2 =newT("t2");
publicstaticinti = print("i");
publicstaticintn =99;
publicintj = print("j");
{
print("构造块");
}
static{
print("静态块");
}
publicT(String str) {
System.out.println((++k) +":"+ str +"    i="+ i +"  n="+ n);
++n; ++ i;
}
publicstaticintprint(String str){
System.out.println((++k) +":"+ str +"   i="+ i +"   n="+ n);
++n;
return++ i;
}
publicstaticvoidmain(String[] args){
T t =newT("init");
}
}

题解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1).首先T类被加载、连接后进行初始化,会先对字段k、t1、t2、i、n以及static块进行初始化。
(2).t1实例的初始化会初始化实例成员j,(实际上先进行父类实例内容的初始化)先调用静态方法print,并执行实例初始化块{},输出:
1: j i=0n=0(i和n都还没有初始化)
2:构造块 i=1n=1
(3)随后调用t1实例的构造函数,输出:
3:t1 i=2n=2
(4).类似有t2实例的初始化:
4: j i=3n=3
5:构造块 i=4n=4
6:t2 i=5n=5
(5).i的初始化:
7.i i=6n=6
(6).n的初始化和静态块的初始化:
8.静态块 i=7n=99(n已经被初始化)
(7).t实例的初始化:
9.j i=8n=100
10.构造块 i=9n=101
11.init i=10n=102

转载于:https://my.oschina.net/u/1583086/blog/536404

Java类的连接与初始化 (及2013阿里初始化笔试题解析)相关推荐

  1. 微软2013暑假实习生笔试题解析

    所有题目为不定项选择 1. Which of the following calling convention(s) support(s) support variable-length parame ...

  2. 360 2013校园招聘笔试题(含参考答案)

    360 2013校园招聘笔试题(含参考答案) 参考答案: 1.D  5*5*5=125 2.C  排除法 3.A  仅个人意见 4.A 5.D   ABC三语句一定保留,D不确定,EF一定删除 6.不 ...

  3. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  4. 网易2013校园招聘笔试题集锦

    第一部分(必做):计算机基础类 (所有的选择题都是多项选择) 1.假设进栈次序是e1, e2, e3, e4,那可能的出栈次序是() A.e2, e4, e3, e1 B.e2, e3, e4, e1 ...

  5. Java相邻数值后比大小后并分组_笔试题:给出一个数组,排序后,取相邻两值差的最大值...

    ε=(´ο`*)))   一个笔试题,就这样了,不想优化了   能出答案就行 import java.util.Scanner; /** * 需求:给出一个数组,排序后,取相邻两值差的最大值 */ p ...

  6. 2013阿里巴巴软件测试笔试题

    2013年阿里巴巴招聘笔试题目--软件测试工程师 2014-02-18 21:10:36|  分类: 找工作|举报|字号 订阅 这份是阿里巴巴2013年秋季校园招聘软件测试工程师的笔试题目,软件测试的 ...

  7. 阿里巴巴2013年实习生笔试题A

    一.单项选择题 1.下列说法不正确的是:(B) A.SATA硬盘的速度速度大约为500Mbps/s B.读取18XDVD光盘数据的速度为1Gbps C.前兆以太网的数据读取速度为1Gpbs D.读取D ...

  8. java线程工作内存在栈中吗_JVM常见面试题解析

    前言 总结了JVM一些经典面试题,分享出我自己的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新. 1.什么情况下会发生栈内存溢出. 思路: 描述栈定义,再描述为什么会溢出 ...

  9. 以下对java中的接口的描述错误的是_Java笔试题

    所有内容都是在网上查找的 1.下面哪些是Thread类的方法? A.Start() B.run()方法 C.exit()方法 D. getPriority()方法 解析:exit()方法 是 Syst ...

最新文章

  1. Android老版本项目导入到新版SDK提示错误
  2. 利用yum下载rpm包并批量安装
  3. c++读取和写入TXT文件的整理
  4. C语言模拟实现标准库函数之qsort()
  5. oracle 数据库_操作事项_05
  6. 出道25年,那些乘风破浪的编程语言们
  7. Latex写分段函数
  8. SQL数据库“单个用户”不能访问,设置为多个用户的解决方法
  9. 数据库设计(三)——数据库设计规范
  10. CSS文字排版终极指南
  11. 双向链表(double linked list)
  12. Android Browser默认主页网址(验证于KK,L,M,N,O)
  13. 什么是白箱测试、黑箱测试、回归测试?
  14. Java中beimage_GitHub - beconf/ImageBlurring: Android 中通过 Java 与 JNI 分别进行图片模糊;并且进行比较其运算速度。...
  15. android高仿微信的图片查看
  16. 如何消除原生Android,如何消除原生Android网络状态上的惊叹号
  17. PyGobject(十九)布局容器之Alignment
  18. javafx 制作五子棋游戏——简单MVC框架
  19. H3C/华为网络设备常规命令集
  20. 程序员难以逃避的几个坎儿及解决方法

热门文章

  1. 城科会刘朝晖:从互联网大脑模型看城市大脑
  2. Science:若DTC基因检测达2%成年人群,几乎所有人的身份或将无所遁形
  3. 三份研究报告,聚焦 AI 的三大主要话题
  4. 未来货运:无人驾驶技术和卡车司机如何配合?
  5. 搭乘“云原生”硬核实践之舟,移动云助力开发者畅游未来创新之旅
  6. Java 面试宝典全公开!
  7. Kafka源码剖析 —— 网络I/O篇 —— 浅析KafkaSelector
  8. 初探莫比乌斯反演及欧拉反演
  9. mysql之索引组织表
  10. 算法题解:对于输入数字串,给出另一种数字排列,使得字典序增加尽可能小...