【Java基础】java类加载过程

     类的加载过程可以分为以下几个阶段:加载,验证,准备,解析,初始化,使用和卸载。其中验证,准备和解析我们通常称之为连接,如下图所示:

   1.加载

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

    1.1加载器

    当然,在虚拟机做这些事情之前,要有类加载器来把类(类所在的.class文件)加载到虚拟机中,加载器所做的事情就是读取java字节代码(.class文件)病转换成java.lang.Class的一个实例。
    java的加载器分为两类,一类是系统提供的,一类是自定义的。系统提供的有以下三个:
   1) 引导类加载器(bootstrap class loader):用来加载java 的核心库(String 、Integer、List...),在jre/lib/rt.jar路径下的内容,是用C代码来实现的,并不继承自java.lang.ClassLoader
    2) 扩展类加载器(extensions class loader):用来加载java的扩展库(jre/ext/*.jar路径下的内容)java虚拟机的实现会自动提供一个扩展目录。该类加载器在此目录里面查找并加载java类。
    3) 应用程序类加载器(application class loader):根据java应用的类路径(classpath路径),一般来说,java应用的类都是由他来完成加载的。

    1.2加载器的结构和代理模式

    加载器采用树形结构,从上到下依次为:引导类加载器,扩展类加载器,应用程序类加载器和自定义加载器。如下图所示:



    加载器在加载类时采用的是双亲委托机制,就是某个特定的类加载器在收到加载请求之后,先委托父加载器,同理,父加载器也会先委托它的父加载器进行加载,如果加载成功则返回,加载不成功则自己进行加载。加载器还采用了cache机制,在加载成功一个类之后,会存在这个cache之中,以后如果还要请求加载这个类,则会先去cache中检测是否命中。

    2.验证

     这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证 文件格式验证、元数据验证、字节码验证和符号引用验证
    1、文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区中,经过这个阶段的验证后,字节流才会进入内存的方法区中存储,所以后面的三个验证阶段都是基于方法区的存储结构进行的。
    2、元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法……
    3、字节码验证:主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。
    4、符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在“解析阶段”中发生。验证符号引用中通过字符串描述的权限定名是否能找到对应的类;在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问。

    验证阶段对于虚拟机的类加载机制来说,不一定是必要的阶段。如果所运行的全部代码确认是安全的,可以使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。


    3.准备

    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

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

    2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。下图是java中基本数据类型的默认零值。


    2、如果类变量被final和static同时修饰,则系统不会为其赋予默认零值,而是直接赋予初始化的值。

    4.解析

    解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

    符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

     直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。

    5.初始化

    类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

      初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的

    5.1初始化触发条件

    Java虚拟机规范规定了有4种情况必须立即对类进行初始化(加载,验证,准备必须在此之前完成) 
    1)当使用new关键字实例化对象时,当读取或者设置一个类的静态字段(被final修饰的除外)时,以及当调用一个类的静态方法时(比如构造方法就是静态方法),如果类未初始化,则需先初始化。 
    2)通过反射机制对类进行调用时,如果类未初始化,则需先初始化。 
    3)当初始化一个类时,如果其父类未初始化,先初始化父类。 
    4)用户指定的执行主类(含main方法的那个类)在虚拟机启动时会先被初始化。

     除了上面这4种方式,所有引用类的方式都不会触发初始化,称为被动引用。如:通过子类引用父类的静态字段,不会导致子类  初始化;通过数组定义来引用类,不会触发此类的初始化;引用类的静态常量不会触发定义常量的类的初始化,因为常量在编  译阶段已经被放到常量池中了。

    5.2<clinit>()方法的执行规则

    1、<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句中可以赋值,但是不能访问。
    2、<clinit>()方法与实例构造器<init>()方法(类的构造函数)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此,在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。
    3、<clinit>()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
    4、接口中不能使用静态语句块,但仍然有类变量(final static)初始化的赋值操作,因此接口与类一样会生成<clinit>()方法。但是接口鱼类不同的是:执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
    5、虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,那就可能造成多个线程阻塞,在实际应用中这种阻塞往往是很隐蔽的。



本文参考如下博文:
https://www.zhihu.com/question/29125656/answer/74503568
http://www.cnblogs.com/javaee6/p/3714716.html
http://blog.csdn.net/gjanyanlig/article/details/6818655/




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值