类加载机制(二)、类加载的过程

上篇文章提到过,类加载一共七步骤:加载、验证、准备、解析、初始化、使用、卸载。现在讲解前五步骤。

一、加载
在类加载阶段,虚拟机需要完成以下三件事:
1)通过一个类的全限定名来获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区对这个类的各种数据的访问入口

总结:查找并加载二进制字节码到方法区,然后在内存中实例化一个java.lang.Class类的对象

二、验证
1、文件格式验证

1)是否以魔数0xCAFEBABE开头
2)主次版本号是否在当前虚拟机处理范围之内
3)常量池的常量中是否有不被支持的常量类型
4)是否有指向不存在的常量或不符合类型的常量
5)CONSTANT_Utf8_info型的长两种是否有不符合UTF8编码的数据
6)Class文件中各个部分及文件本身是否又被删除的或附加的其他信息
...

该阶段的主要目的是保证输入的字节流能正确的解析并存储于方法区之内

2、元数据验证

1)这个类是否有父类(除了java.lang.Object之外,其他所有的类都应该有父类)
2)这个类的父类是否继承了不允许被继承的类(被final修饰的类)
3)如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
4)类的字段,方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,比如方法参数都一样,但返回值却不同。)
...

该阶段主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。

3、字节码验证

1)保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现:在操作栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中
2)保证跳转指令不会跳转到方法体以外的字节码指令上
...

该阶段主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

4、符号引用验证

1)符号引用中通过字符串描述的全限定名是否能找到对应的类
2)在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
3)符号引用中的类、字段、方法的访问性是否可以被当前类访问
...

该阶段的目的是确保解析动作能正常执行,否则将会抛出一个java.lang.IncompatibleClasschangeError异常的子类

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

三、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

1)这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。

2)这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显示的赋予的值。
假设一个类变量的定义为:
public static int value = 3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。

3)如果类字段的字段属性表中存在ConstantValue属性,即同时被static 和 final修饰,那么在准备阶段变量value就会会初始化为ConstantValue属性所指定的值。
假设上面的变量value被定为:
public static final int value = 3;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。

总结:为类的静态变量分配内存,并将其初始化为默认值

四、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

总结:把类中的符号引用转换为直接引用

五、初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
①声明类变量是指定初始值
②使用静态代码块为类变量指定初始值

初始化也是执行类构造器<clinit>()方法的过程。

1)<clinit>()方法是由编译器自动收集类种所有变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在他之后的变量,在前面的静态语句块可以赋值,但是不能访问,如下:

package com.designmodel.javap;

/**
 * 测试
 * 
 * @author 15620646321@163.com
 * @date 2017年5月9日
 */
public class TestCLInit {
    
    static {
        i = 0;
        //Cannot reference a field before it is defined
//      System.out.println(i);
    }
    
    static int i = 1;
}

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

3)由于父类的<clinit>()方法优先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作,如下

package com.designmodel.javap;

/**
 * 测试
 * 
 * @author 15620646321@163.com
 * @date 2017年5月9日
 */
public class TestCLInit2 {
    
    static class Parent {
        public static int A = 1;
        
        static {
            A = 2;
        }
    }
    
    static class Sub extends Parent {
        public static int B = A;
    }
    
    public static void main(String[] args) {
        System.out.print(Sub.B);
    }
}

输出结果:

2
package com.designmodel.javap;

/**
 * 测试
 * 
 * @author 15620646321@163.com
 * @date 2017年5月9日
 */
public class TestCLInit2 {
    
    static class Parent {
        
        static {
            A = 2;
        }
        public static int A = 1;
    }
    
    static class Sub extends Parent {
        public static int B = A;
    }
    
    public static void main(String[] args) {
        System.out.print(Sub.B);
    }
}

输出结果:

1

从结果发现,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。

若有兴趣,欢迎来加入群,【Java初学者学习交流群】:458430385,此群有Java开发人员、UI设计人员和前端工程师。有问必答,共同探讨学习,一起进步!
欢迎关注我的微信公众号【Java码农社区】,会定时推送各种干货:


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

推荐阅读更多精彩内容