java类加载机制、类加载器、自定义类加载器的案例

类加载机制

java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段。

其中验证、准备和解析三个部分统称为连接(Linking)。

1、加载

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

类的加载通过JVM提供的类加载器完成,类加载器是程序运行的基础。程序在启动的时候,并不会一次性加载程序所要用到的所有class文件,而是根据需要,通过java的类加载器机制(classLoader)来动态加载某个class文件到内存中。

jvm在运行时会产生三个classLoader:

启动类加载器(BootStrap ClassLoader):是java类加载层次中最顶层的类加载器,负责加载jdk中的核心类库。由C++实现,不是classLoader的子类。

扩展类加载器(Extension ClassLoader):负责加载java的扩展类库,比如lib/ext或者java.ext.dirs系统属性指定的目录中的jar包。父类加载器为null。

系统类加载器(App ClassLoader):负责加载来自java命令的-classpath选项、java.class.path系统属性所指定的jar包和类路径。程序可以通过classLoader的静态方法getSystemClassLoader(),来获取系统类加载器。由java语言实现,父类加载器为ExtClassLoader。

除了java默认提供的这三个classLoader之外,用户可以根据需要定义自己的classLoader,这些自定义的classLoader都必须继承自java.lang.ClassLoader类。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据。通常有如下几种情况:

从本地文件系统加载class文件,这是绝大部分实例程序的类加载方式。从jar包加载class类,这种方式也很常见。通过网络加载class类把一个java源文件动态编译,并执行加载,比如jsp。

2、连接

当类被加载之后,系统为之生成一个对应的class对象,接着进入连接阶段(验证-准备-解析),连接阶段负责把类的二进制数据合并到jre中。

验证:用于检测被加载的类是否有正确的内部结构,并和其他类协调一致。

包括四种验证:文件格式验证、元数据验证、字节验证和符号引用验证。准备:负责为类变量分配内存,并设置默认初始值。

解析:将类的二进制数据中的变量进行符号引用替换成直接引用。

3、初始化

在初始化阶段,主要为类的静态变量赋予正确的初始值。其实就是执行类构造器<clinit>()方法的过程。

在java类中对类变量指定初始值有两种方式:a.声明类变量时指定初始值;b.使用静态初始化块为类变量指定初始值。

jvm初始化一个类包含如下步骤:

加载并连接该类先初始化其直接父类依次执行初始化语句当执行第2步时,系统对直接父类的初始化也遵循1~3,以此类推。

当一个类被主动引用后会触发初始化过程:

遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。

生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。

使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要触发父类的初始化。当虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个类。

当使用jdk7+的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发器 初始化。

当一个类如果是被动引用的话,不会触发初始化过程:

通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义该字段的类才会被初始化,因此当我们通过子类来引用父类中定义的静态字段时,只会触发父类的初始化,而不会触发子类的初始化。

通过数组定义来引用类,不会触发此类的初始化。

常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

4、使用

(略)

5、卸载

如果出现下面的情况,类就会被卸载:

该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。加载该类的ClassLoader已经被回收。

该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。

类加载器

类加载器负责加载所有的类。其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。

正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。

在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。

这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

前面我们已经介绍了java中的几种类加载器,下面我们用一张图展示他们的层次关系:

类加载步骤

类加载器加载class大致需要如下8个步骤:

检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。

如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。

请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。

请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。

当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。

从文件中载入Class,成功后跳至第8步。

抛出ClassNotFountException异常。返回对应的java.lang.Class对象。

类加载机制

全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

双亲委派:先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。

缓存机制:保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。

这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

自定义的类加载器

jvm除跟类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过拓展ClassLoader的子类,并重写该ClassLoader所包含的方法实现自定义的类加载器。

ClassLoader有如下两个关键方法:

loadClass(String name,boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类的class对象。

findClass(String name):根据指定名称来查找类如果需要实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass()方法而不是loadClass()方法。

classLoader()方法的执行步骤:

1)findLoadedClass():来检查是否加载类,如果加载直接返回;

2)父类加载器上调用loadClass()方法。如果父类加载器为null,则使用跟类加载器加载;

3)调用findClass(String)方法查找类。从这边可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Java语言中的自定义类加载器实例解析

    本文研究的主要是Java语言中的自定义类加载器实例解析的相关内容,具体如下. 自己写的类加载器 需要注意的是:如果想要对这个实例进行测试的话,首先需要在c盘建立一个c://myjava的目录.然后将相应的java文件放在这个目录中.并将产生的.clas文件放在c://myjava/com/lg.test目录下,否则是找不到的.这是要注意的.. class FileClassLoader : package com.lg.test; import java.io.ByteArrayOutputSt

  • 源码解析Java类加载器

    参考内容: 深入理解Java虚拟机(JVM高级特性与最佳实践) --周志明老师 尚硅谷深入理解JVM教学视频--宋红康老师 我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化): 关于三层类加载器.双亲委派机制,本文不再板书,读者可自行百度. 那么在JDK的源码中,三层结构的具体实现是怎么样的呢? Bootstrap ClassLoader(引导类加载器) 引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的. 一般大家都

  • 浅谈JAVA 类加载器

    类加载机制 类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个 java.lang.Class 实例.一旦一个类被载入 JVM 中,同个类就不会被再次载入了.现在的问题是,怎么样才算"同一个类"? 正如一个对象有一个唯一的标识一样,一个载入 JVM 中的类也有一个唯一的标识.在 Java 中,一个类用其全限定类名(包括包名和类名)作为标识:但在 JVM 中,一个类用其全限定类名和其类加载器作为唯一标识.例如,如果在 pg 的包中有一个名为 Person 的类,被类加载器 Cl

  • Java虚拟机JVM类加载机制(从类文件到虚拟机)

    一.类加载机制简介 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载机制:所谓的类加载机制就是虚拟机将class文件加载到内存,并对数据进行验证,转换解析和初始化,形成虚拟机可以直接使用的jav

  • JVM入门之类加载与字节码技术(类加载与类的加载器)

    1. 类加载阶段 1.1 加载阶段 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有: _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴 露给 java 使用 _super 即父类 _fields 即成员变量 _methods 即方法 _constants 即常量池 _class_loader 即类加载器 _vtable 虚方法表 _ita

  • SpringMVC超详细介绍自定义拦截器

    目录 1.什么是拦截器 2.自定义拦截器执行流程图 3.自定义拦截器应用实例 1.快速入门 2.注意事项和细节 3.Debug执行流程 4.多个拦截器 1.多个拦截器执行流程示意图 2.应用实例 3.主要事项和细节 1.什么是拦截器 说明 Spring MVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能. 自定义的拦截器必须实现 HandlerInterceptor 接口 自定义拦截器的三个方法 preHandle():这个方法在业务处理器处理请求之前被调用,在该方

  • SpringMvc自定义拦截器(注解)代码实例

    拦截器 自定义拦截器实现HandlerInterceptor接口的三个方法. public class MyInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //拦截内容 //放行 return true;

  • java类加载机制、类加载器、自定义类加载器的案例

    类加载机制 java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(using).和卸载(Unloading)七个阶段. 其中验证.准备和解析三个部分统称为连接(Linking). 1.加载 加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Clas

  • java自定义类加载器代码示例

    如果要使用自定义类加载器加载class文件,就需要继承java.lang.ClassLoader类. ClassLoader有几个重要的方法: protectedClassLoader(ClassLoaderparent):使用指定的.用于委托操作的父类加载器创建新的类加载器. protectedfinalClass<?>defineClass(Stringname,byte[]b,intoff,intlen):将一个byte数组转换为Class类的实例. protectedClass<

  • Java基于自定义类加载器实现热部署过程解析

    热部署: 热部署就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该Class创建的对象.一般情况下,类的加载都是由系统自带的类加载器完成,且对于同一个全限定名的java类,只能被加载一次,而且无法被卸载.可以使用自定义的 ClassLoader 替换系统的加载器,创建一个新的 ClassLoader,再用它加载 Class,得到的 Class 对象就是新的(因为不是同一个类加载器),再用该 Class 对象创建一个实例,从而实现动态更新.如:修改 JSP 文件即生效,就是利用自定

  • 浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系

    JVM自带的类加载器: 其关系如下: 其中,类加载器在加载类的时候是使用了所谓的"父委托"机制.其中,除了根类加载器以外,其他的类加载器都有且只有一个父类加载器. 关于父委托机制的说明: 当生成 一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器将成为该类加载器的父类加载器 下面,自定义类加载器.自定义的类加载器必须继承java.lang.ClassLoader类 import java.io.*; public class MyClassLoader extend

  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    目录 一.类加载器 1.启动类加载器 2.拓展类加载器 3.应用类加载器 4.类的命名空间 二.双亲委派机制 1.类加载机制流程 2.类加载器加载顺序 3.双亲委派机制流程 4.源码分析 5.双亲委派机制优缺点 三.线程上下文类加载器 1.线程上下文类加载器(Context Classloader) 2.ServiceLoader 四.自定义类加载器 一.类加载器 类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个ja

随机推荐