浅谈java类加载机制

       我们都知道Java源代码会被编译器编译成二进制的 xx.class中间文件,在java程序执行的时候,jvm会把java的class文件加载到内存中,今天就分析一下java类加载的相关知识。类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构,java反射机制的实现就是通过这个Class对象实现的。

 

1.    加载class会有哪些途径呢?主要有下面几种方式。

首先是可以从本地系统中直接加载 class文件,也可以从zip,jar等归档文件中加载.class文件,还能通过网络下载.class文件。

 

2.类加载的步骤

    2.1 加载:这一步做的是查找并且加载类的二进制数据

    2.2 连接 ,连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。这一步又分为三个步骤。

      验证:主要的作用是确保被加载的类的正确性,比如检查magic number是不是0xcafebabe,检查版本号,类文件的结构检查,语义检查,字节码验证,二进制兼容性的验证等等。

      准备:这个阶段是为类的静态变量分配内存,并将其初始化为默认值,注意默认值并不是初始值,例如:

               static int a=2; 

        a是整型,默认值是0,所以准备阶段a的值为0,不是2.

      解析:把类中的符号引用转换为直接引用

   2.3初始化:为类的静态变量赋予正确的初始值,例如上面的例子中,a会被赋值为2.

 

 

 

      3.类初始化的时机,即一个java类 什么时候才会被初始化。

        一个类被jvm加载时,并不会立即对他初始化,原因是节省内存。一个java类或者接口仅会在首次主动使用时才会被jvm初始化。

 

    Java程序对类的使用方式可分为两种一种是主动使用 ,一种是被动使用。

    主动使用主要有六种情况:

a.      创建这个类的实例,就是new一个新的对象时,这个类会被主动使用。

b.      访问某个类或接口的静态变量,或者对该静态变量赋值

c.       调用类的静态方法

d.      反射,例如Class.forName(“xx”)

e.      初始化一个类的子类

f.       Java虚拟机启动时被标明为启动类的类

       其他的所有的情况都不是主动使用,类不会被初始化。所以可以知道,类加载的最终结果是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。可以实现反射。

       需要注意的是:调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。 JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误),如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

 

 

4.类加载器的类型

       按照归属划分,有两种类型的类加载器,分别是jvm自带的加载器和用户自定义的加载器。

      4.1 Jvm自带的加载器又有三个层次。

         根类加载器(Bootstrap),它没有用java语言实现,它负责加载核心的类库,例如 java.lang.*。

         扩展类加载器(Extension) ,它是java语言实现的,是java.lang.ClassLoader的子类,负责加载jre\lib\ext下的类。

         系统类加载器(System) 它也是java语言实现的,是java.lang.ClassLoader的子类,负责从classpath下加载类。

       4.2 用户自定义的类加载器,它是用户自己实现的,是java.lang.ClassLoader的子类,用户可以定制类的加载方式,类加载器并不需要等到某个类被“首次主动使用”时再加载它。像tomcat这样的servlet容器,内部实现就有大量的类加载器。

  类加载器有类似于父子的层级结构,如下图所示:

   5.类加载的父委托机制。

 

        当使用类加载器加载一个类时,这个类加载器首先判断这个类是否已经被加载。如果没有,则请求它的父加载器加载,以此类推,知道根加载器。然后从根加载器开始,若能加载,就加载,否则,再请求子加载器进行加载。如果所有的父加载器和这个加载器本身都不能加载,就抛出一个ClassNotFoundException.这就是类加载的父委托机制。

       加载器的父子关系仅仅是功能职责上的先后关系,并不是java类的继承关系。

       类加载的父委托机制的作用主要是为了保证系统的安全,例如,如果用户自己实现了一个恶意的名为String的类,但是不能用自己定义的类加载器加载,最终加载的只能是jdk的String类,就保证了安全。

 

6.类的卸载

       一个类被加载,链接,初始化后,就开始了它的生命周期。当描述这个类的Class对象不可到达,即没有引用时,这个类在方法区的数据就会被卸载。

       需要注意的是,被jvm自带的类加载器加载的类,由于他们一直保持着对这些类的引用,所以永远不会被卸载。只有被用户自定义的类加载器加载的类才有可能被卸载。

7.jvm的生命周期

    当出现这几种情况时,Java虚拟机的生命周期就结束了。

    7.1执行了System.exit()方法

    7.2程序正常执行结束

    7.3程序在执行过程中遇到了异常或错误而异常终止

    7.4由于操作系统出现错误而导致Java虚拟机进程终止