java类加载

本文主要是说明java 类加载的全过程,即JVM把class文件加载到内存,并对数据进行校验、准备、解析、初始化,最终形成JVM可以直接使用的Java类型的过程。

全过程如图,可以看到整个过程还是有很多步骤的,下面就具体步骤进行说明。
java类加载机制

加载

加载阶段很简单,当程序执行到需要的类时,JVM 会通过类加载器将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口。

链接

将Java类的二进制代码合并到JVM的运行状态之中,里面包含验证、准备和解析。

验证

对具体的内容进行校验,确保Class文件的字节流中包含对信息符合Java虚拟机规范,包括验证文件格式、元数据、字节码、符号引用等验证。

准备

正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。但需要注意的是,此时的设置初始值为默认值,具体赋值在初始化阶段完成。

解析

虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。

初始化

初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。在准备阶段,JVM给类的静态字段分配空间和默认值。而在此阶段,就正式执行类的初始化代码,对类进行初始化操作。

注意:当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类,此处经常有面试题,我们后面进行展开。

使用

在程序中使用类或对象。

卸载

当确定对象不再需要使用时,JVM需要对其进行垃圾回收。

类加载器

在加载环节有提到,JVM 通过类加载器将class字节码文件加载到内存中,什么是类加载器呢?
实现通过类的全限定名获取该类的二进制字节流的代码块叫做类加载器。
简单来说,就是用来加载类的,但是其作用不仅仅是加载类,因为对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。

分类

引导类加载器(bootstrap class loader)

负责加载 JAVA_HOME/lib 目录,或通过-Xbootclasspath 参数指定路径,且被虚拟机认可(按文件名识别,如 rt.jar)的类。用来加载 java 核心类库,无法被 java 程序直接引用。

扩展类加载器(extensions class loader)

负责加载 JAVA_HOME/lib/ext 目录,或通过 java.ext.dirs 系统变量指定路径中的类库。它用来加载 Java 的扩展库。

应用程序类加载器(application class loader)

它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。

自定义类加载器

开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

双亲委派机制

上面的几种类加载器是为了给不同的类加载用的,也就是一个类只能有且只有一个类加载器对其进行加载。那么问题就来了,如何保证只有一个类加载器加载呢?

官方说法: 如果一个类加载器收到类加载请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载器请求都应该传送到最顶层的启动类加载器,只有当父级的类加载器反馈自己无法完成这加载请求,子类加载器才会尝试自己去完成加载。

某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(bootstrap class loader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错:ClassNotFoundException。

双亲委派机制
双亲委托机制是为了保证 Java 核心库的类型安全,这种机制保证不会出现用户自己能定义java.lang.Object类等的情况,保证核心库代码安全。

例如,用户定义了java.lang.String,那么加载这个类时最高级父类会首先加载,发现核心类中也有这个类,那么就加载了核心类库,而自定义的永远都不会加载。

值得注意是,双亲委托机制是代理模式的一种,但并不是所有的类加载器都采用双亲委托机制。在tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的,这里不再展开。