虚拟机类加载机制概叙2:类加载的过程
虚拟机类加载机制概叙2:类加载的过程
玄影镜心 发表于2年前
虚拟机类加载机制概叙2:类加载的过程
  • 发表于 2年前
  • 阅读 7
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

本篇将逐一描述类加载的过程中,加载、验证、准备、解析、初始化各个阶段的动作。

一:加载

    ⑴:类的加载

    类的加载阶段,虚拟机需要完成3件事:

    1:(获取数据) 通过一个类的全限定名来获取定义此类的二进制字节流

    2:(结构转换)将字节流的静态存储结构 转换成方法区的运行时    

    3:(生成对象)在内存中生成一个代表此类的Class对象,作为方法区此类的数据访问入口。

注意:第一条并没有明确要求二进制字节流的获取途径,它可是一一个class文件,也可以是jar包、网络获取、运行时计算生成等等,非常灵活。

    ⑵:数组的加载

    数组本身不通过类加载器来创建,他由java虚拟机直接创建。但是数组类的元素类型(去掉所有维度的最终类型)需要靠类加载器创建。

    如果数组的组件类型(去掉一个维度的类型)是引用类:递归采用类的加载过程(上面)去加载这个组件。

    如果数组组件不是引用类,java虚拟机会把数组标记与引导类加载器关联。

    可见性:数组的可见性与他的组件类型的可见性一致,如果组件不是引用类型,那么默认public.

    ⑶:加载完成

    加载完成后,虚拟机外部的二进制字节流就按照虚拟机要求的格式存储在方法区,方法区中的数据存储结构是由虚拟机实现自定义的。

二:验证

    验证的目的:确保二进制字节流所包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身安全。所以这个阶段非常重要。

    验证阶段可以分为4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

    四个阶段所验证的校验对象大约可以对应为:文件格式----元数据----方法----类本身以外的信息

    1:文件格式验证:验证字节流是否符合class文件格式的规范,并且能被当前版本的虚拟机理解。(比如魔术、主次版本号、常量池中常量类型等等【很多】)

    2:元数据验证对字节码描述信息进行语义分析,确保其描述的信息符合java语言规范。(如验证是否有父类、这个父类是否允许继承等等)

    3:字节码验证:这个阶段对类的方法进行校验,通过数据流和控制流分析,以确定程序语义是合法的、符合逻辑的。(最复杂的验证阶段)

    4:符号引用验证:发生在虚拟机将符号引用转换为直接引用的时候,这个动作在连接的第三个阶段(解析阶段)中发生。可以看作是对类自身以外的信息(常量池中的各种符号引用)进行匹配校验

    说明:只有通过文件格式验证后,字节流才会进入内存的方法区存储,而后面的3个验证阶段,全部都是基于方法区存储结构进行的,不会在操作字节流。

三:准备

    此阶段将会正式在方法区中为类变量 分配内存 并设置变量的初始值

    注意:1:进行内存分配的仅包括类变量,不含实例变量(实例变量是在对象实例化时,同对象一起分配在java堆中)

              2:设置类变量的初始值,是指数据类型类型的初始值,而不是代码中设定的值(如int默认0 ,double默认0.0d)。

              3:如果类的字段属性表中存在ConstantsValue属性,那么准备阶段变量value就会被初始化为ConstantsValue属性所指定的值。(例如:准备阶段会给常量直接赋最终值)

四:解析

    解析阶段,虚拟机将常量池内的符号引用替换为直接引用

    (略)

五:初始化

    初始化阶段开始真正执行java程序中的代码(字节码),此阶段会根据代码中制定的计划去初始化变量和其他资源

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

    <clinit>()方法是由编译器自动收集类中的“所有类变量的赋值动作”和“静态语句块中的语句”来合并产生的。换句话说<clinit>()方法对一个类或者接口来说,并不是必需的。如果一个类没有静态语句块,也没有对变量的复制操作,那么编译器不会对这个类生成<clinit>()方法。

    <clinit>()不需要显示的调用父类构造器,虚拟机会保证在调用子类<clinit>()之前,父类的<clinit>()方法已经执行完毕。(这意味着:父类中的静态语句块要优先于子类中的变量赋值操作)

    值得注意的是:虚拟机会保证一个类的<clinit>()方法在多线程环境下被正确的加锁、同步,如果有多个线程同时去初始化一个类,那么最终只能有一个线程去执行这个类的<clinit>()方法,其他线程都将阻塞。但是通一个类加载器下,一个类只会被初始化一次,也就是说当这个类执行完<clinit>()方法后,其他线程在被唤醒后并不会再次进入<clinit>()方法。



共有 人打赏支持
粉丝 6
博文 69
码字总数 47521
×
玄影镜心
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: