Jvm学习笔记(三)

类的初始化和加载


类初始化

  • 遇到new,或读取、修改或调用一个类的static变量时。
  • 反射调用。
  • 初始化一个类时若父类没有被初始化,会初始化父类。
  • 虚拟机启动时,用户指定的运行的主类。

只有遇到这四种情况时才会进行类的初始化,叫做主动引用,其他情况均不会发生类的初始化,叫做被动引用。

被动引用:

  • 通过子类调用父类的static变量,子类不会被初始化,父类会被初始化。
  • 通过类名调用final static 常量,类不会被初始化。
  • 通过数组定义类,如Object [] obj = new Object[5];,不会发生初始化。

接口初始化:

  • 类初始化要求父类必须初始化,接口不要求父接口初始化。
  • 接口里的变量(final static)调用时会进行接口初始化。

ConstantValue属性:

属于类文件结构里的内容。作用是通知虚拟机为静态变量初始化。对于实例变量(非static变量)的初始化在实例构造器里进行。这里所谓的静态变量并不要求有final修饰,但是是对于javac编译器要求要有final修饰。所以一般情况下,ConstantValue针对final static 修饰的基本类型和String。
static 、final、static final修饰字段赋值的区别:

  • static修饰的字段在类加载过程中的准备阶段被初始化为0或null,在初始化阶段(构造器)中被赋予指定的值。
  • final 在运行时被初始化
  • static final 修饰的字段在 Javac 时生成 ConstantValue 属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,可以认为编译时被放进常量池。

类加载机制


类加载过程:

加载——验证——准备——解析——初始化

  • 加载:获取class文件二进制流,并把其中的静态结构写入方法区的运行时数据结构中,在对上生成java.object.class对象,作为方法区对象的访问入口。加载阶段可以用系统提供的加载器,也可以自己实现。在这里,相同的class文件用不同的加载器来实现得到的两个结果不相等。这里的类指的是非数组类,数组类不通过加载器加载,而是通过Java虚拟机直接创建。但是数组中的元素类型是由加载器加载。

  • 验证:

    • 文件格式的验证:验证字节流是否符合Class字节流规范,并能被当前虚拟机处理。目的是为了输入的字节流能被正确地解析并写入方法区。
  • 元数据结构验证:验证各数据类型是否符合规范。

  • 字节码验证:进行数据流和控制流分析,保证在运行的类的方法不会产生危害虚拟机的行为。

  • 符号引用验证:发生在虚拟机将符号引用转换为直接引用时,进行对类和常量池符号引用的匹配校验。

  • 准备:
    为类变量(static)分配空间和初始化阶段。

    • static:进行内存分配,并进行默认赋值。具体赋值发生在出发类的构造器中。
    • final static:生成ConstantValue属性,赋值并放入常量池。
    • 局部变量和final static常量使用前必须赋值,否则编译不通过。
  • 解析:
    将常量池中的符号引用转换成直接引用的过程。解析主要针对类或接口、字段、类方法、接口方法四种符号引用。

    • 类或接口解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。
    • 字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类。
    • 类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
    • 接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。
class Super{  
    public static int m = 11;  
    static{  
        System.out.println(m);  
    }  
}  

class Father extends Super{  
    public static int m = 33;  
    static{  
        System.out.println(m);  
    }  
}  

class Child extends Father{  
    static{  
        System.out.println("Child");  
    }  
}  

public class Test{  
    public static void main(String[] args){  
        System.out.println(Child.m);  
    }  
}  

执行结果:

11
11
33
  • 初始化:
    初始化是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的 Java 程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器<init>()方法的过程。
    • 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句中可以赋值,但是不能访问。

    • 方法与实例构造器<init>()方法(类的构造函数)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<init>()方法执行之前,父类的<init>()方法已经执行完毕。因此,在虚拟机中第一个被执行的<init>()方法的类肯定是java.lang.Object。

    • 方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<init>()方法。

    • 接口中不能使用静态语句块,但仍然有类变量(final static)初始化的赋值操作,因此接口与类一样会生成<init>()方法。但是接口与类不同的是:执行接口的<init>()方法不需要先执行父接口的<init>()方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<init>()方法。

    • 虚拟机会保证一个类的<init>()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<init>()方法,其他线程都需要阻塞等待,直到活动线程执行<init>()方法完毕。如果在一个类的<init>()方法中有耗时很长的操作,那就可能造成多个线程阻塞,在实际应用中这种阻塞往往是很隐蔽的。

class Father{  
    public static int a = 1;  
    static{  
        a = 2;  
    }  
}  

class Child extends Father{  
    public static int b = a;  
}  

public class ClinitTest{  
    public static void main(String[] args){  
        System.out.println(Child.b);  
    }  
}

执行上面的代码,会打印出 2,也就是说 b 的值被赋为了 2。

我们来看得到该结果的步骤。首先在准备阶段为类变量分配内存并设置类变量初始值,这样 a和b 均被赋值为默认值 0,而后再在调用()方法时给他们赋予程序中指定的值。当我们调用 Child.b 时,触发 Child 的()方法,根据规则 2,在此之前,要先执行完其父类Father的()方法,又根据规则1,在执行()方法时,需要按 static 语句或 static 变量赋值操作等在代码中出现的顺序来执行相关的 static 语句,因此当触发执行 Fathe r的()方法时,会先将 a 赋值为 1,再执行 static 语句块中语句,将 a 赋值为 2,而后再执行 Child 类的()方法,这样便会将 b 的赋值为 2。

如果我们颠倒一下 Father 类中“public static int a = 1;”语句和“static语句块”的顺序,程序执行后,则会打印出1。很明显是根据规则 1,执行 Father 的()方法时,根据顺序先执行了 static 语句块中的内容,后执行了“public static int a = 1;”语句。

另外,在颠倒二者的顺序之后,如果在 static 语句块中对 a 进行访问(比如将 a 赋给某个变量),在编译时将会报错,因为根据规则 1,它只能对 a 进行赋值,而不能访问。

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

推荐阅读更多精彩内容