zoukankan      html  css  js  c++  java
  • JVM的类加载

    一、基本类加载机制介绍

           大体引用一下《深入理解Java虚拟机》一书中对类加载的定义:虚拟机将描述类的二进制字节流(即Class文件)加载到内存中,并对其进行验证、准备、解析、初始化,最终

    生成可以直接被虚拟机使用的Java类型(即已经校验合格且有clinit执行完clinit方法的Class对象),这就是JVM的类加载机制。

            一个好的定义,就应该是这样准确而简练的。下面先罗列一下类的生命周期:

            加载  ->  验证  ->  准备 ->  解析  ->  初始化  ->  使用  -> 卸载

            其中加载、验证、准备、初始化、卸载一定会严格按照上面的顺序进行。既然知道了类加载的大体流程,那么问题来了,什么时候会触发类的加载呢?

            虚拟机规范中并未作出明确的要求,但是会要求有且仅有下面五种情况发生的时候,会触发类的初始化(言外之意就是初始化之前的动作必须在此之前完成),这五种情况分别为:

    1、遇到new、getStatic、putStatic、invokeStatic字节码指令时;

    2、通过java.lang.reflect包对类进行反射调用的时候;

    3、初始化子类时,如果父类没初始化,则先初始化父类(接口除外,接口初始化规则是调用哪个接口初始化哪个,不看继承关系);

    4、虚拟机启动时,会自动初始化带有main方法的类;

    5、使用动态语言支持时,如果一个MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄且对应的类未初始化时(恕BZ学识浅薄,目前还没

    理解这种情况对应实际代码中的哪种情形,只能先放放,好在这东西一般也遇不到 (><) )。

        额外需要说明两点,这两点也经常在那种仿佛是考脑筋急转弯的编程题中遇到:一是在new一个引用类数组时不会触发这个类的初始化;二是对于常量的引用不会触发类初始化(static final)。

    尤其是第二点要特别说明一下,如果A类中有一个常量叫VALUE,B类中引用了这个常量,那么在编译的时候,编译器会把这个VALUE直接放入B类的运行时常量池中供其后续使用,就是说在

    编译之后,A类与B类已经没有任何交互了,所以也就不会触发初始化了。

    二、类加载的各个过程简介

     1、加载

          类加载的第一个阶段叫加载,主要做的是三步:

    1)、通过类的全限定名定位到对应的class文件;

    2)、将二进制字节文件转化为方法区中的运行时数据;

    3)、在内存中生成一个Class对象,作为方法区中访问运行时数据(即访问2)中数据)的入口。

    此阶段自由度较高,我们可以自定义一个类加载器,加载class文件

    2、验证

          验证主要包括四部分:文件格式校验、源数据校验(包括语义校验)、字节码校验、字符引用校验

    3、准备

          在此阶段,虚拟机会为类变量(即static修饰的成员变量)分配内存并赋初始的默认值。例如一个类变量private static int a = 2,在此阶段中,会给赋初值,即a=0。实际的2这个值是在

    初始化阶段赋予。而如果是static final类型的变量,在此阶段会直接赋终值。

    4、解析

          将要加载的那个类的运行时常量池内的符号引用替换为直接引用

    5、初始化

          初始化的过程就是执行类构造器<clinit>()方法的过程。此方法是在编译阶段由编译器收集类中的静态变量赋值动作、静态方法、静态块的语句合并而成的。

    虚拟机会保证执行<clinit>()方法前会先执行父类的<clinit>方法,同样如果是接口则不用先执行父类的类构造器。

    三、类加载器

        Java中一共定义了三种类加载器:启动类加载器  <--   扩展类加载器   <--  应用程序类加载器,继承关系如箭头所示,而我们自定的类加载器,都是以应用程序类加载器为父类。

        类加载器很重要,因为JVM中定义两个类是不是同一个类,只用两个判断条件:一个是类的全限定名是否相同,另一个是加载这个类的类加载器是否相同。如果类加载器不同,

    即使类的全限定名一样,虚拟机一样会判定这是两个不同的类。

        Java中的类加载器加载类,采用双亲委派模型,即类加载器C要加载类A时,会先让父加载器去加载,一直往上传递,除非父类加载器中搜不到这个类,才会让子类去加载,并且

    每个类只能被加载一次。这样做的好处就是可以限定核心类不会被替换篡改。比如一种常见的面试题,如果你重新编写了一个路径为java.lang.String的类,能否调用到这个自定的类

    中的方法?在这里很明显答案是不会,因为JVM中已经加载了那个系统的java.lang.String类,当用应用程序类加载器加载我们自定义的String类时,由于双亲委派模型的存在,加载

    任务会被委派给父类,一直往上传递,最后到启动类加载器那里,然后发现已经加载过一次这个全路径名的类了,不能重复加载,所以自定义的String类就无法被加载调用了。(有没

    有发现这个剧情跟真假美猴王很相似,一直传递到佛祖那,才辨别出了真假 。。。)

          类加载就暂时到这里,书中还提到一些双亲委派模型被破坏的场景,由于了解的不够透彻,就先不乱发言了。学习之路,还是要坚持死磕啊,加油!

  • 相关阅读:
    Windows自动更新所需要连接的网站列表
    DFX 9.303 for QQMusic 2010
    在VPC 2007 SP1中安装Ubuntu 10.04 desktop (完成)
    穷人把钱存入银行,实际上是补贴富人。
    清理Windows右下角图标
    阿里镜像pull 加速器
    k8s 安装flannel网络插件
    k8s pull.sh
    kubeadm1.10.00 安装k8s集群
    虚拟机vmware centos7 扩展磁盘空间
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/10127717.html
Copyright © 2011-2022 走看看