[Java]理解JVM之二:类加载器

在java里,类加载都是在程序运行期间完成的,这种方式虽然会在类加载时增加增加一些性能开销,但能给java程序提供高度的灵活性。

类加载过程

        Java 编译器把 java 代码(.java文件)编译为java字节码(.class文件),然后JVM把字节码文件加载到内存,并对数据进行校验、转换解析和初始化。

        类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括七个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading),其中验证、准备、解析三个阶段统称为链接。


注:加载、验证、准备、初始化、卸载这五个阶段顺序是一定的,而解析阶段在某些情况下可以在初始化之后再开始。

1、加载(Loading)

        1)、通过一个类的全限定类名来获取定义此类的二进制字节流。

        2)、将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。

        3)、在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

2、验证(Verification)

        目的:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身安全。

        验证过程分为四个阶段:

        1)、文件格式验证

        2)、元数据验证

        3)、字节码验证

        4)、符号引用验证

3、准备(Preparation)

        正式为类变量分配内存并设置类变量初始值阶段,这些内存都将在方法区中进行分配。(实例变量将会在对象实例化时随对象一起分配在Java堆中)

4、解析(Resolution)

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

        解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。

5、初始化(Initialization)

        初始化阶段是执行类构造器<clinit>()方法的过程,类构造器由编译器自动收集类中的所有变量的赋值动作和静态语句块(static{})中的语句合并产生。

        <clinit>()与类的构造函数<init>()不同,不需显示调用父类构造器,虚拟机会保证在子类的<clinit>()执行前,父类<clinit>()已经执行完毕。

        注:执行接口的<clinit>()不需要先执行父接口的<clinit>()。

       

类加载器

        对于任何一个类,都需要由加载它的类加载器和这个类本身来确定其在JVM的唯一性。即两个加载自同一Class文件的,并且被同一个类加载器加载,这两个类才相等。这里的相等指equals()方法、isAssignableFrom()方法、inInstance()方法和instanceof关键字的结果。

        对虚拟机角度来说,由两种类加载器:启动类加载器(Bootstrap ClassLoader)和其他所有的类加载器。前者使用C++语言实现,属于虚拟机自身的一部分,后者是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。

        对Java开发人员来说,一般会用到三种系统提供的类加载器:

        1)、启动类加载器(Bootstrap ClassLoader),负责加载JAVA_HOME\lib目录中能被虚拟机识别的类库到JVM内存中。

        2)、扩展类加载器(Extension ClassLoader),负责加载JAVA_HOME\lib\,该加载器可以被开发者直接调用。

        3)、应用程序类加载器(Application ClassLoader),也称作系统类加载器,负责加载用户类路径上所指定的类库,该加载器可以被开发者直接调用,如果没有自定义过类加载器,一般这个加载器就是程序中默认的加载器。


        如图类加载器之间的层次关系即为加载器的双亲委派模型(Parent Delegation Model)。该模型中除了顶层的启动类加载器外,都有自己的父类加载器。但子类加载器和父类加载器间不是以继承的关系来实现的,而是通过组合的关系来复用父加载器。

        双亲委派模型的工作过程为:如果一个类加载器收到了类加载请求,它首先会把这个请求委派给父类加载器去完成,每一层都是如此,所以任何类加载请求都会传递给顶层的启动类加载器,只有父类反馈无法完成加载该请求时子类才会尝试加载。

        这种模型的好处是无论哪个类加载器去加载一个类,都是最终由启动类加载器进行加载,所以被加载的这个类在程序的各类加载器环境中都是同一个类。如果不使用这个模型,且类存放在classpath中的话,系统中将会出现多个这个类,程序就会很混乱。如果自定义一个与现有类同名的类,则这个类可以被正常编译。但是无法被加载运行。

        这种模型的不足是,当类想去调用回下层的用户代码时无法委派子类加载器进行类加载。为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器。线程上下文类加载器没有遵循双亲委派模型。

        这种模型是通过loadClass()方法来实现的,过程非常简单:先检查类是否已经加载,如果没有则调用父类的loadClass()方法,如果父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,则抛出ClassNotFoundException异常,然后调用自己的findClass()方法进行加载。


自定义类加载器

        要自定义类加载器,需要继承java.lang.ClassLoader类,并且重写findClass()方法。java.lang.ClassLoader类的基本功能是根据一个类的名称,找到或生成对应的字节码,然后根据这些字节码定义出一个Java类,即java.lang.Class类的实例。此外ClassLoader类还负责加载Java应用所需要的资源,如配置文件和图像文件等。在JDK1.2之前,类加载未引入双亲委派模式,实现自定义类加载器一般重写loadClass()方法,提供双亲委派逻辑,JDK1.2之后不需要自己写双亲委派逻辑,所以不需要重写loadClass()方法,推荐重写findClass()方法。


动态加载Jar文件和Class文件

       


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值