细说JVM系列:虚拟机类加载机制

来源:互联网 发布:java开发软件有哪些 编辑:程序博客网 时间:2024/05/19 16:28

虚拟机类加载机制

上一篇我们介绍了了Class文件存储格式的具体细节,在Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用。而虚拟机如何加载这些Class文件?正是本部分要说明的内容。

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

一.类加载流程

Class文件通常以文件的形式存在(当然,任何二进制流都可能是Class类型),只是被java虚拟机装载的Class类型才能在程序中使用。系统装载Class类型可以分为加载、连接和初始化3个步骤,其中,连接又可以分为:验证、准备和解析3步,如下所示:

二.触发类加载的时机

Class文件只有在要使用的时候才会被加载,java虚拟机不会无条件的加载Class类型。java虚拟机并未规定加载、验证、准备、解析的发生时机,但是规定了初始化的时机,而加载、验证、准备、解析都是在初始化之前的。java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的”使用“,是指主动主动使用,主动使用只有下列几种情况:

  • 通过new创建对象;
  • 读取、设置一个类的静态成员变量(不包括final修饰的静态变量);
  • 调用一个类的静态成员函数。
  • 使用java.lang.reflect进行反射调用的时候,如果类没有初始化,那就需要初始化;
  • 当初始化一个类的时候,若其父类尚未初始化,那就先要让其父类初始化,然后再初始化本类;
  • 当虚拟机启动时,虚拟机会首先初始化带有main方法的类,即主类;

除了以上情况属于主动使用,其他的情况均属于被动使用。被动使用不会引起类的初始化。

三.类加载流程详解

1.加载

加载和类加载是不一样的,加载只是类加载的一个阶段而已。

加载阶段虚拟机需要完成三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

2.连接

1)验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证 :文件格式验证、元数据验证、字节码验证和符号引用验证。

2)准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中

3)解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。

3.初始化

初始化是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的 Java 程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器()方法的过程。

类的初始化的重要工作是执行类的初始化方法.方法是有编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。

四.ClassLoader

前面讲到,在类加载流程的加载阶段,需要做的事情如下:

加载阶段虚拟机需要完成三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

这里ClassLoder的作用就是做第一步的工作:从系统外部获取Class二进制数据流。

ClassLoader是java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入系统,然后交给java虚拟机进行连接、初始化等操作。因此ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的连接和初始化行为。

从代码层面上看,ClassLoader是一个抽象类,它提供了一些重要的接口,用于自定义ClassLoader的加载流程和加载方式,ClassLoader的重要方法如下:

Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口点.给定一个类名,加载一个类,返回代表这个类的Class实例,如果找不到类,则返回ClassNotFoundException异常。  defineClass() 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。  findSystemClass() 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。  resolveClass()可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值  findLoadedClass() 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法.一般load方法过程如下:  调用 findLoadedClass 来查看是否存在已装入的类。  如果没有,那么采用某种特殊的神奇方式来获取原始字节。(通过IO从文件系统,来自网络的字节流等)  如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。  如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。  如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。  如果还没有类,返回 ClassNotFoundException。  否则,将类返回给调用程序。  

1.ClassLoader的分类

在标准java程序中,java虚拟机会创建3类ClassLoader为整个应用程序服务。他们分别是:BootStrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)、App ClassLoader(应用类加载器,也成为系统类加载器),此外,每一个应用程序还可以拥有自定义的ClassLoader,扩展java虚拟机获取Clas数据的能力。

在这些加载器中,最特别的是启动类加载器,他是完全有C代码编写的,并且在java中没有对象与之对应,系统的核心类都是由启动加载器进行加载的,他是虚拟机的核心组件,扩展类加载器和应用类加载器都有对应的java对象可用使用。

1.Bootstrap ClassLoader

由C++写的,由JVM启动.

启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

2.Extension ClassLoader

Java类,继承自URLClassLoader

扩展类加载器,对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等

3.App ClassLoader

Java类,继承自URLClassLoader

系统类加载器,对应的文件是应用程序classpath目录下的所有jar和class等

2.ClassLoader的双亲委托模式

系统中的ClassLoader在协同工作时,默认会使用双亲委托模式。即在类加载的时候,系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。

3.双亲委托模式的弊端

应用类访问系统类没有问题,但是系统类访问应用类就会出现问题。

4.突破委托模式

OSGI。

5.热替换的实现

利用ClassLoader。

0 0