zoukankan      html  css  js  c++  java
  • (转) JVM——Java类加载机制总结

    背景:对java类的加载机制,一直都是模糊的理解,这篇文章看下来清晰易懂。

    转载:http://blog.csdn.net/seu_calvin/article/details/52301541

    1. 类加载器的组织结构

    类加载器 ClassLoader是具有层次结构的,也就是父子关系。其中,Bootstrap是所有类加载器的父亲。

     

    (1)Bootstrapclass loader: 启动类加载器

    当运行Java虚拟机时,这个类加载器被创建,它负责加载虚拟机的核心类库,如java.lang.*等。

    2)Extensionclass loader:标准扩展类加载器

    用于加载除了基本 API之外的一些拓展类。

    (3)AppClassLoader:加载应用程序和程序员自定义的类。

    运行下面的程序,结果也显示出来了:

     

    从运行结果可以看出加载器之间的父子关系,ExtClassLoader的父Loader返回了null

    原因是BootstrapLoader(启动类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式。

     

    2. 类的加载机制

    类被加载到虚拟机内存包括加载、链接、初始化几个阶段。其中链接又细化分为验证、准备、解析

    这里需要注意的是,解析阶段在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。各个阶段的作用整理如下:

    2.1 加载阶段

    加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义类加载器去控制字节流的获取方式。

    ps:可以自定义类加载器,从加密和安全两个角度出发来加载特殊的类。具体参考——JVM——自定义类加载器 

    (1)通过类的全名产生对应类的二进制数据流

    (2)将这些二进制数据流转换为方法区的运行时数据结构

    (3)创建代表这个类的java.lang.Class对象。作为方法区这些数据的访问入口

     

    2.2 链接阶段(实现 Java 的动态性的重要一步)

    (1)验证:验证阶段的主要目的是确保class文件字节流的正确性,要验证比如class文件格式规范、这个类是否继承了final类、不能把一个父类对象赋值给子类数据类型等等。

    (2)准备:准备阶段为方法区中的静态变量分配内存空间。并将其赋值为初始值,所有原始类型的值都为0。如float为0f、 int为0、boolean为0、引用类型为null。

    (3)解析解析阶段把符号引用解析为直接引用。

    符号引用是一个字符串,它唯一标识一个类、一个字段、一个方法等目标。

    直接引用对于类变量、类方法指的是指向方法区的指针,然后对于实例方法、实例对象来说就是偏移量,比如一个实例方法,子类中方法表中的偏移量和父类是一致的,这个偏移量可以确定某个方法的位置。

    2.3 初始化

    到了初始化阶段,才是真正执行用户定义的程序代码。在初始化阶段就是执行类构造器方法的过程,工作包括赋值类变量、静态语句块的合并。

    //定义在静态语句块之后的变量可以赋值,但不能访问  
    public class Test{  
        static{  
            i=0;//給变量赋值,可以通过编译  
            System.out.print(i);//这句编译器会提示非法向前引用  
        }  
        static int i=1;  
    }  

    初始化过程会被触发的条件汇总

    (1)使用new关键字实例化对象、访问一个类的静态字段、静态方法的时候。

    (2)对类进行反射调用的时候。

    (3)当初始化子类时,如果发现其父类还没有进行过初始化,则进行父类的初始化

     

    【关于构造器方法拓展知识】(可以不看)

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

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

    (3)<clinit>()方法不是必须的,如果一个类中没有静态语句,那么编译器可以不为这个类生成<clinit>()方法。

    (4)接口中不能使用静态语句块,和类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。

    (5)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步,可能会导致阻塞。

     

    3. 类加载的三种方式

    (1)由 new 关键字创建一个类的实例。

    (2)调用 Class.forName() 方法,通过反射加载类。

    (3)调用某个ClassLoader实例的loadClass()方法。

     

    三者的区别汇总如下:

    (1)方法1和2都是使用的当前类加载器。方法3是由用户指定的类加载器加载

    (2)方法1是静态加载,2、3是动态加载。

    (3)对于两种动态加载,如果程序需要类被初始化,就必须使用Class.forName(name)的方式。

     

    Class.forName(className);  
    //实际上是调用的是:  
    Class.forName(className, true, this.getClass().getClassLoader());//第二个参为true即默认类需要初始化,初始化会触发目标对象静态块的执行和静态变量的初始化  

     

    ClassLoader.loadClass(className);  
    //实际上调用的是:  
    ClassLoader.loadClass(name, false);//第二个参数即默认得到的class还没有进行链接,意味着不进行初始化等系列操作,即静态代码块不会执行 

     

     

     

     

  • 相关阅读:
    [05] EL表达式
    [03-01] JSP自定义标签
    [04] JSP标准动作
    [03] JSP指令
    Fiddler抓包调试前端脚本代码
    《互联网协议入门》思维导图笔记
    Nodejs学习笔记(十)—与MongoDB的交互(mongodb/node-mongodb-native)、MongoDB入门
    Nodejs学习笔记(九)—与Redis的交互(mranney/node_redis)入门
    Nodejs学习笔记(八)—Node.js + Express 实现上传文件功能(felixge/node-formidable)
    Nodejs学习笔记(七)—Node.js + Express 构建网站简单示例
  • 原文地址:https://www.cnblogs.com/lixuwu/p/8492258.html
Copyright © 2011-2022 走看看