java class文件的加载

转自 http://blog.csdn.net/wen7280/article/details/53856790

图览全局----Class文件装载经历的各个阶段:

Java应用程序开发中,只有被java虚拟机装载的Class类型才能在程序中使用。只要生成的字节码符合java虚拟机的指令集和文件格式,就可以在JVM上运行,这为java的跨平台性提供条件。

字节码文件的装载过程:加载 、  连接(包括三个步骤:验证  准备   解析)  、初始化,如图所示

-------------------------------------------------------------------------------------------------

类装载的条件:

Java虚拟机不会无条件的装载Class类型。

Java虚拟机规定:一个类或者接口在初次使用时,必须进行初始化

这里的使用指的是主动使用,主动使用有以下几种情况:

当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化方式。

当调用类的静态方法时,即当使用了字节码invokestatic指令

当使用类或者接口的静态字段时(final常量除外,此种情况只会加载类而不会进行初始化),即使用getstatic或者putstatic指令(可以使用jclasslib软件查看生成的字节码文件)

当使用java.lang.reflect包中的方法反射类的方法时

当初始化子类时,必须先初始化父类

作为启动虚拟机、含有main方法的那个类

除了以上情况属于主动使用外,其他情况均属于被动使用,被动使用不会引起类的初始化,只是加载了类却没有初始化。

例1:主动使用(这是三个class文件,而不是一个,此处为方便写在一起。多说一点:因为一个Class文件只能有一个public类和文件名一样,其余类修饰符只能是非pubic)

[java]view plaincopy

publicclassParent{

static{

System.out.println("Parent init");

}

}

publicclassChild{

static{

System.out.println("Child init");

}

}

publicclassInitMain{

publicstaticvoidmain(String[] args){

Child c =newChild();

}

}

以上声明了3个类:Parent Child InitMain,Child类为Parent类的子类。若Parent被初始化,将会执行static块,会打印"Parent init",若Child被初始化,则会打印"Child init"。(类的初始化先于加载,故执行静态代码块后,就表明类已经加载了)

[java]view plaincopy

执行InitMain,结果为:

Parent init

Child init

由此可知,系统首先装载Parent类,接着装载Child类。

符合主动装载中的两个条件:使用new关键字创建类的实例会装载相关的类,以及在初始化子类时,必须先初始化父类。

例2 :被动装载

[java]view plaincopy

publicclassParent{

static{

System.out.println("Parent init ");

}

publicstaticintv =100;//静态字段

}

publicclassChildextendsParent{

static{

System.out.println("Child init");

}

}

publicclassUserParent{

publicstaticvoidmain(String[] args){

System.out.println(Child.v);

}

}

Parent中有静态变量v,并且在UserParent中,使用其子类Child去调用父类中的变量。

[java]view plaincopy

运行代码:

Parent init

100

虽然在UserParent中,直接访问了子类对象,但是Child子类并未初始化,仅仅加载了Child类,只有Parent类进行初始化。所以,在引用一个字段时,只有直接定义该字段的类,才会被初始化

注意:虽然Child类没有被初始化,但是,此时Child类已经被系统加载,只是没有进入初始化阶段。

可以使用-XX:+ThraceClassLoading 参数运行这段代码,查看日志,便可以看到Child类确实被加载了,只是初始化没有进行

例3 :引用final常量

[java]view plaincopy

publicclassFinalFieldClass{

publicstaticfinalString constString ="CONST";

static{

System.out.println("FinalFieldClass init");

}

}

publicclassUseFinalField{

publicstaticvoidmain(String[] args){

System.out.println(FinalFieldClass.constString);

}

}

运行代码:CONST

FinalFieldClass类没有因为其常量字段constString被引用而进行初始化,这是因为在Class文件生成时,final常量由于其不变性,做了适当的优化。验证完字节码文件无误后,在准备阶段就会为常量初始化为指定的值。

分析UseFinalField类生成的Class文件,可以看到main函数的字节码为:

在字节码偏移3的位置,通过Idc将常量池第22项入栈,在此Class文件中常量池第22项为:

#22 = String        #23     //CONST

#23 = UTF8         CONST

由此可以看出,编译后的UseFinalField.class中,并没有引用FinalFieldClass类,而是将FinalFieldClass类中final常量字段直接存放在自己的常量池中,所以,FinalFiledClass类自然不会被加载。(javac在编译时,将常量直接植入目标类,不再使用被引用类)通过捕获类加载日志(部分日志)可以看出:(并没有加载FinalFiledClass类日志)

注意:并不是在代码中出现的类,就一定会被加载或者初始化,如果不符合主动使用的条件,类就不会被加载或者进一步初始化。

详解类装载的整个过程

1)加载类:处于类装载的第一个阶段。

加载类时,JVM必须完成:

通过类的全名,获取类的二进制数据流

解析类的二进制数据流为方法区内的数据结构,也就是将类文件放入方法区中

创建java.lang.Class类的实例,表示该类型

2)连接

验证字节码文件:当类被加载到系统后,就开始连接操作,验证是连接的第一步。

主要目的是保证加载的字节码是符合规范的。

验证的步骤如图:

准备阶段

当一个类验证通过后,虚拟机就会进入准备阶段。准备阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值,这些内存都将在方法区进行分配。这个时候进行内存分配的仅是类变量,不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆上。为类变量设置初始值是设为其数据类型的“零值”。

比如 public static int num = 12; 这个时候就会为num变量赋值为0

java虚拟机为各种类型变量默认的初始值如表:

类型默认初始值

int0

long0L

short(short)0

char\u0000

booleanfalse

referencenull

float0f

double0f

注意:java并不支持boolean类型,对于boolean类型,内部实现是Int,由于int的默认值是0,故对应的,boolean的默认值是false

如果类中属于常量的字段,那么常量字段也会在准备阶段被附上正确的值,这个赋值属于java虚拟机的行为,属于变量的初始化。在准备阶段,不会有任何java代码被执行。

解析类

在准备阶段完成后,就进入了解析阶段。

解析阶段的任务就是将类、接口、字段和方法的符号引用转为直接引用。

符号引用就是一些字面量的引用。比较容易理解的就是在Class类文件中,通过常量池进行大量的符号引用。

具体可以使用JclassLib软件查看Class文件的结构:::

下面通过一个简单函数的调用来讲解下符号引用是如何工作的。。。

例如:System.out.println();

生成的字节码指令:invokevirtual #24

这里使用了常量池第24项,查看并分析该常量池,可以查看到如图的结构:

常量池第24项被invokevirtual使用,顺着CONSTANT_Methodref #24的引用关系继续在常量池中查找,发现所有对于Class以及NameAndType类型的引用都是基于字符串的,因此,可以认为Invokevirtual的函数调用通过字面量的引用描述已经表达清楚了,这就是符合引用。

但是只有符合引用是不够的,当println()方法被调用时,系统需要明确知道方法的位置。java虚拟机会为每个类准备一张方法表,将其所有的方法都列在表中,当需要调用一个类的方法时,只要知道这个方法在表中的偏移量就可以了。通过解析操作,符合引用就可以转变为目标方法在类中方法表的位置,从而使方法被成功调用。

所以,解析的目的就是将符合引用转变为直接引用,就是得到类或者字段、方法在内存中的指针或者偏移量。如果直接引用存在,那么系统中肯定存在类、方法或者字段,但只存在符合引用,不能确定系统中一定存在该对象。

3)类初始化

如果前面的步骤没有出现问题,那么表示类可以顺利装载到系统中。此时,才会开始执行java字节码

初始化阶段的重要工作是执行类的初始化方法()。其特点:

()方法是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的类变量,定义在其之后的类变量,只能被赋值,不能被访问。比如:

static{

num = 5;//这是合法的

}

static int num = 12;

static{

System.out.println(num);//这样是不合法的

}

static int num = 12;

例如:

[java]view plaincopy

publicclassSimpleStatic{

publicstaticintid =1;

publicstaticintnumber;

static{

number =4;

}

}

java编译器为这段代码生成如下的:

0 iconst_1

1 putstatic #2

4 iconst_4

5 putstatic #3

8 return

函数中,整合了SimpleStatic类中的static赋值语句以及static语句块

改段JVM指令代码表示:先后对id和number两个成员变量进行赋值

()方法与类的构造器函数()方法不同,它不需要显示的调用父类的()方法,虚拟机会保证在子类的()方法执行之前,父类的()方法已经执行完毕。故父类的静态语句块会先于子类的静态语句块执行。

[java]view plaincopy

publicclassChildStaticextendsSimpleStatic

{

static{

number =2;

}

publicstaticvoidmain(String[] args){

System.out.println(number);

}

}

[java]view plaincopy

运行代码:

2

表明父类的总是在子类之前被调用。

注意java编译器并不是为所有的类都产生初始化函数,如果一个类既没有类变量赋值语句,也没有static语句块,那么生成的函数就应该为空,因此,编译器就不会为该类插入函数

例如:

public class StaticFinalClass{

public static final int i=1;

public static final int j=2;

}

由于StaticFinalClass只有final常量,而final常量在准备阶段被赋值,而不在初始化阶段处理,因此对于StaticFinalClass类来说,就无事可做,因此,在产生的class文件中没有该函数存在。

虚拟机保证一个类的()方法在多线程环境中被正确的加锁和同步,如果多个线程同时去初始化一个类,只有一个线程去执行这个类的()方法,其他线程都会被阻塞,直到指定线程执行完()方法。

--------------------------------------------------------------------------------------------------------------------------------------------------

趁着意犹未尽,来看看对象初始化流程:包括成员变量和构造器调用的先后顺序,子类构造器和父类之间的先后顺序等等。通过字节码文件指令直接的展示这个过程

编辑几个类,包括一个子类一个父类,其中子类和父类中都包含了成员变量、非静态代码块、构造器函数以及前面讲到的静态代码块和静态变量:

[java]view plaincopy

packagecom.classextends;

publicclassFuZiDemo {

publicstaticvoidmain(String[] args) {

newZiClass();//测试类,创建子类对象

}

}

[java]view plaincopy

classFuClass {

intfuOwer =120;//成员变量一

static{

System.out.println("Fu clinit()");//静态代码块

}

staticintnum =22;//静态变量

{//非静态代码块

fuName ="tempValue";

System.out.println(fuOwer);

intc =23;

}

String fuName ="dali";//成员变量二

FuClass(){//父类构造函数

System.out.println("Fu init()");

fuOwer =100;

}

}

[java]view plaincopy

classZiClassextendsFuClass {

intziOwer =82;//成员变量一

static{//静态代码块

System.out.println("Zi clinit()");

}

staticintnum =2;//静态变量

{//非静态代码块

ziName ="tempValue";

System.out.println(ziOwer);

intc =23;//局部变量

}

String ziName ="urocle";//成员变量二

ZiClass(){//子类构造函数

ziOwer =23;

System.out.println("Zi init()");

}

}

分析:

一、类的加载和初始化

首先FuziDemo这个测试类要加载,然后执行main指令时会new 子类对象,故要去加载子类的字节码文件,但是会发现子类有一个直接继承类FuClass,于是就会先去加载FuClass的字节码文件,接着会初始化父类,执行FuClass类的方法:执行输出语句以及为静态成员赋值,其字节码指令为:

0 getstatic #13

3 ldc#19

5 invokevirtual #21

8 bipush22

10 putstatic#27

13 return

完成父类的初始化工作之后,紧接着加载子类的字节码文件并且执行其()方法。其字节码指令类似于父类的:

0 getstatic #13

3 ldc#19

5 invokevirtual #21//调用println()方法输出 #19也就是 Zi clinit()

8iconst_2

9 putstatic#27//为静态变量赋值

12 return

二、子类和父类成员变量初始化,以及构造函数执行顺序

测试类main函数的字节码指令:

0 new #16

3 invokespecial #18>         //调用子类的初始化函数

6 return

下面看看子类ZiClass的()函数的字节码指令:

0 aload_0

1 invokespecial #32>//首先会去调用父类的()函数

4 aload_0

5 bipush82

7putfield#34//为成员变量 ziOwer赋值为82

10 aload_0

11 ldc #36

13putfield#38//执行非静态代码块,临时为成员变量ziName赋值

16 getstatic #13//调用System.out输出函数

19 aload_0

20 getfield #34//获取成员变量 ziOwer的值

23 invokevirtual #40//打印输出

26 bipush 23

28 istore_1

29 aload_0

30 ldc#43

32putfield#38//为成员变量ziName赋值为urocle

35 aload_0

36bipush 23//取出 23 ,意味着实例初始化过程中先初始化成员变量及执行非静态代码块,最后执行构造

38putfield#34//为成员变量ziOwer赋值为23

41 getstatic #13

44 ldc #45

46 invokevirtual #21

49 return

同样FuClass类的实例初始化函数()如下,此处不再解释:

0 aload_0

1 invokespecial #32>

4 aload_0

5 bipush 120

7 putfield #34

10 aload_0

11 ldc #36

13 putfield #38

16 getstatic #13

19 aload_0

20 getfield #34

23 invokevirtual #40

26 bipush 23

28 istore_1

29 aload_0

30 ldc #43

32 putfield #38

35 getstatic #13

38 ldc #45

40 invokevirtual #21

43 aload_0

44 bipush 100

46 putfield #34

49 return

三  给出程序执行的结果

Fu clinit()

Zi clinit()        //静态代码块输出

120                 //非静态代码块输出

Fu init()         //构造函数输出

82

Zi init()

总结:

(1)父类加载初始化先于子类,父类的优先于子类的函数执行

(2)如果创建一个子类对象,父类构造函数调用先于子类构造器函数调用。在执行构造器函数首先会初始化类中成员变量或者执行非静态代码块(这二者执行的先后顺序依赖于在源文件中出现的顺序)然后再调用构造函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,085评论 0 62
  • Win7下如何打开DOS控制台? a:开始--所有程序--附件--命令提示符 b:开始--搜索程序和文件--cmd...
    逍遥叹6阅读 1,590评论 4 12
  • ❤ 傍晚,我在等熟悉的1路车,准备拉拉筋骨,发现有个正在等车的孩子很眼熟,上前问...
    猫猫家的三角梅阅读 1,004评论 0 0
  • 2015年最后一个月,今天很冷,再加上下雨,心情很糟糕,上课没什么激情。 这节课以往都是很无聊的,没几个人听,但...
    loveofmylife阅读 252评论 0 0