类的加载、连接与初始化过程的详细分析(上)

时间:2021-09-27 17:26:43

        前面一个博客文章介绍过了类从加载到可以被使用经过了加载、连接、初始化三个过程,下面对每个过程所发生的事情做详细分析。


一、类加载阶段

        1.类加载方式

        类的加载指的是将类的.class文件的二进制数据读入内存中,将其放在运行时数据区的方法区内。然后在堆区创建一个java.lang.Class对象,

用来封装类在方法区内的数据结构,该对象是由JVM在加载类时创建的。所以每个类都会对应一个Class类型的对象,通过getClass()来获取,

并且无论生成该类的多个少对象,其Class类型的对象只有一个。Class类的构造方法是私有的,并且Class类的对象只有JVM才能创建,创建时机为加载.class文件时。

        因此Class类是整个反射的入口,因为每个类都会在内存中对应一个描述它的Class类型的对象,使用这个对象就可以获取到目标类所关联的class文件中的数据结构。


        类的加载有以下几种方式(加载.class文件的方式)

        Ø  从本地文件系统直接加载

        Ø  通过网络下载.class文件(java.net.URLClassLoader)

        Ø  从zip、jar等归档文件中加载 .class文件

        Ø  从专有的数据库中提取 .class文件

        Ø  将Java源文件动态编译为.class文件


        类加载的最终产物就是位于堆区中的Class对象(注意此时并没有被加载类的对象存在,刚刚加载类)。Class对象封装了类在方法区中的数据结构,

并且向Java程序员提供了访问方法区内的数据结构的接口,这些接口就是Java反射的相关类和方法。

       

        1. 类加载器

        有两种类型的类加载器

      (1).Java虚拟机自带的类加载器,其中包括以下三种类加载器

                Ø  根类加载器(Bootstrap ClassLoader)

                Ø  扩展类加载器(Extension ClassLoader)

                Ø  系统类加载器(System ClassLoader),又称为应用类加载器

       

        其中第一种类加载器是JVM最底层的类加载器,由C++编写,因此我们无法访问到根类加载器。后面两种其实是基于第一种的,由Java语言实现的类加载器。


       (2).用户自定义的类加载器,可以定义加载的方式,加载时机以及加载过程中做一些事情。

        使用java.lang.ClassLoader的子类,通过ClassLoader来实现自定义的类加载器,这个过程中可以定义类的加载方式,加载时机以及加载过程中做一些事情。


        通过给定ClassLoader一个类的名称,它会将其作为一个文件名称试图去读取该文件内容并根据内容组装一个类的描述。每个类对象其实都包含了一个对定义它的

那个ClassLoader的一个引用,因为任何类都是由类加载器加载的,因此通过该类就可以访问到对应的类加载器。

        通过类对象的getClass().getClassLoader()或类的class属性的getClassLoader()就可以获取到类所对应的类加载器,也就是加载该类的哪个类加载器。

getClassLoader()可能返回一个null,那么就代表该类的类加载器是根类加载器(Bootstrap ClassLoader);换句话说,如果一个类是有根类加载器加载的,

那么就无法获取到该类加载器,此时getClassLoader()返回null,因为根类加载器是使用C++编写的,我们无法在程序中访问它。例如String等类就是由根类

加载器加载的,因此String.class.getClassLoader()将返回null。


        对于JDK内置的类一般都是由根类加载器加载的,因此通过它们调用getClassLoader()返回null;自定义的类一般通过

sun.misc.Launcher$AppClassLoader加载,通过输出getClassLoader()结果即可看到。

       

        对于JDK动态代理的InvocationHandler类的invoke方法,第一个参数是一个ClassLoader类型,就是用于动态的加载第二个参数所传递的类,

然后会根据所加载的类动态的创建出所加载类的对象,然后根据该对象创建出一个该对象的代理对象。


        3.类加载时机

         类加载器并不需要等到某个类被主动使用时才加载它。这与类的初始化不同,上面说过JVM必须在每个类或接口被Java程序首次主动使用时才初始化它们,

注意加载与初始化的却别!

        JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件不存在或有错误,此时并不会报错,

而是类加载器必须等到在程序首次使用该类时才报告错误,这种错误类型为LinkageError。因此如果这个类加载后一直没有被主动使用

那么类加载器就一直不会报告错误。

        

        类被加载后就进入了连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中。所谓的数据合并,因为编译后的每个class文件

在硬盘中都是独立的,但是每个class之间可能存在引用关系以及方法之间存在调用关系,此时就需要根据它们之间的关系将这些class数据合并在一起放入运行时环境中。


        以上介绍的是类加载、连接、初始化中的加载过程。后面的文章将介绍连接过程,包括验证、准备、解析三个过程。最后介绍类的初始化过程。