Java类加载器
在Java虚拟机中,负责査找并加载类的那部分被称为类加载器子系统。从Java虚拟机的角度来讲,只存在两种不同的类加载器: 一种是启动类加载器(Eootstrap ClassLoader), 这个类加载器使用 C++语言实现,是虚拟机自身的一部分;另外一种就是所有其他的类加载器, 这些类加载器都由Java语言实现, 独立于虚拟机外部, 并且全都继承自抽象装 java.lang.ClassLoader。从Java开发人员的角度来看,类加载器就还可以划分得更细致一些, 绝大部分Java程序都会使用到以下三种系统提供的类加载器:启动类加载器(BootStrap)、扩展类加载器(Extension)、系统类加载器(System)。
Java中的类加载器实质上也是类,功能是把类载入JVM中,由不同的类加载器加载的类将被放在虚拟机内部的不同命名空间中。
类加载器子系统涉及Java虚拟机的其他几个组成部分, 以及几个来自java.Iang库的类。 比如, 用户自定义的类加载器是普通的Java对象, 它的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序提供了访问类加载器机制的接口 。 此外, 对于每一个被加载的类型, Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和所有其他对象一 样, 用户自定义的类加载器以及Class类的实例都放在内存中的堆区, 而加载的类型信息则都位于方法区。
启动类加载器(Bootstrap ClassLoader)
启动类加载器负责将存放在<JAVA_HOME>\lib目录中的, 或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名称,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载) 类库加载到虚拟机内存中 。启动类加载器无法被 Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,就直接使用null代替。
扩展类加载器(Extension ClassLoader)
这个加载器由 sun.misc.Launcher$ExtClassLoader实现, 它负责加载<JAVA_HOME>\lib\ext目录中的, 或者被java.ext.dirs 系统变量所指定的路径中的所有类库 , 开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader)
这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于这个类加载器是 ClassLoader中的getSystemClassLoader( )方法的返回值, 所以一般也称它为系统类加载器。它负责加载用户类路径 (ClassPath)上所指定的类库,开发者可以直接使用这个类加载器, 如果应用程序中没有自定义过自已的装加载器, 一般情况下这个就是程序中默认的类加载器。
类加载机制
JVM的类加载机制主要有如下三种:
- 全盘负责: 所谓全盘负责, 就是说当一个类加载器负责加载某个 CIass的时候, 该 Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
- 父类委托:所谓父类委托则是先让 parent(父)类加载器试图加载该 Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
- 缓存机制: 缓存机制将会保证所有被加载过的 Class 都会被缓存, 当程序中需要使用某个CIass时,类加载器先从缓存中搜寻该 Class, 只有当缓存中不存在该 Class对象时, 系统才会读取该类对应的二进制数据,并将其转换成Class对象存入缓存区。这就是为什么修改了 Class后, 程序必须重启动JVM,所作的修改才会生效的原因。
除了可以使用Java提供的类加载器之外,也可以自定义继承 ClassLoader的加载器,JVM中这四种加载器的层次结构如图:
双亲委派模型
既然类加载器不止一个,那么一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢? 我们使用的就是父类委托机制,也就是常说的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自已的父类加载器, 这里类加载器之间的父子关系一般不会以继承(lnheritance)的关系来实现,而是都使用组合(Composition)关系来复用类加载器的代码 。
其工作过程是:如果一个类加载器收到类加载请求,它首先不会自己去尝试加载这个类, 而是把这个请求委派给父类加载器去完成。每一个层次的类加载器都是如此, 因此所有的类加载请求最终都直接传送到顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求 (它的搜素范围中没有找到所需的类) 时, 子加载器才会尝试自己去加载。
用户自定义类加载器
Java应用程序能够在运行时安装用户自定义的类加载器, 这种类加载器能够使用自定义的方式来加载类,用户自定义的类加载器能够用Java编写, 能够被编译为class文件, 能够被虚拟机装载, 还能够像其他对象一样实例化。 它们实际上只是运行中的Java应用程序可执行代码的一部分。
由于有用户自定义类加载器, 所以不必在编译的时候就知道运行中的Java应i用程序中最终会加入的所有的类。 用户自定义的类加载器使得在运行时扩展Java应用程序成为可能。当它运行时, 应用程序能够决定它需要哪些额外的类, 能够决定是使用一个或是更多的用户定义的类加载器来加载。由于类加载器是使用Java编写的,所以能用任何在Java代码中可以表述的风格来进行类的装载。这些类可以通过网络下载, 可以从某些数据库中获取, 甚至可以动态生成 。
每一个类被加载的时候, Java虚拟机都监视这个类, 看它到底是被启动类加载器还是被用户自定义类装载器装载。 当被加载的类引用了另外一个类时, 虚拟机就会使用加载第一个类的类加载器加载被引用的类。
尽管“用户自定义类加载器''本身是Java程序的一部分, 但类ClassLoader中的四个方法是通往Java虚拟机的通道:
// Four of the methods declared in class java.lang.ClassLoader protected final Class defineClass(String name, byte data[], int offset, int length); protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain ); protected final Class findSystemClass(String name); protected final Class resolveClass(Class c);
任何Java虚拟机实现都必须把这些方法连接到内部的类装载器子系统中 。
两个被重载的defineClass( )方法都要接受一个名为data[]的字节数组作为输入参数,并且在data[offset]到data[offset+length]之间的二进制数据必须符合Java class文件格式——它表示一个新的可用类。 而name参数是个字符串, 它指出该类的全限定名 。 当使用第一个defineClass( )时,该类型将被赋以默认的保护域。使用第二个defineClass( )时,该类型的保护域将由它的protectionDomain参数指定。每个Java虚拟机实现都必须保证ClassLoader类的defineClass( )方法能够把新类型导入到方法区中。
findSystemClass( )方法接受一个字符串作为参数,它指出将被装入类型的全限定名。在版本1.0和版本1.1中,这个方法会通过启动类加载器来加载指定类型。如果启动类加载器装载完成,它会返回对CIass对象(该对象描述了该类型)的引用。如果没有找到相应的class文件,它会抛出ClassNotFoundException异常。在版本1.2中, findSystemClass( )方法使用系统类加载器来装载指定类型。任何Java虚拟机实现都必须保证findSystemClass( )方法能够以这种方式调用启动类装载器(如果运行版本1.0或版本1.1 ),或者系统类装载器(如果运行版本1.2或以上)。
resolveClass( )方法接受一个Class实例的引用作为参数,它将对该Class实例表示的类型执行连接动作。而前面提到的defineClass( )方法则只负责加载。当defineClass( )方法返回一个CIass实例时, 也就表示指定的class文件已经被找到并装载到方法区了, 但是却不一定被连接和初始化。 Java虚拟机实现必须保证ClassLoader类的resolveClass( )方法能够让类加载器子系统执行连接动作。
命名空间
每个类加载器都有自己的命名空间, 其中维护着由它装载的类。所以一个Java程序可以多次装载具有同一个全限定名的多个类。 这样一个类的全限定名就不足以确定在一个Java虚拟机中的唯一性。 因此, 当多个类加载器都装载了同名的类时, 为了唯一地标识该类型,还要在类型名称前加上装载该类型(指出了它所位于的命名空间)的类装载器的标识。
Java虚拟机中的命名空间, 其实是解析过程的结果 。 对于每一个被加载的类型, Java虚拟机都会记录装载它的类加载器。 当虚拟机解析一个类到另一个类的符号引用时, 它需要被引用类的类加载器。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java类加载的过程
JVM和类 当我们调用 Java 命令运行某个 Java 程序时,该命令将会启动一条 Java 虚拟机进程,不管该 Java 程序有多么复杂,该程序启动了多少个线程,它们都处于该 Java 虚拟机进程里。同一个 JVM 的所有线程、所有变量都处于同一个进程里,它们都使用该 JVM 进程的内存区。当系统出现以下几种情况时, JVM 进程将被终止: 程序运行到最后正常接收; 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码结束程序; 程序运行中遇到未捕获的异常或错误结束; 程序所在平台强制结束了JVM进程; 类加载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤: 1、加载:查找和导入Class文件 2、链接:其中解析步骤是可以选择的 (a)检查:检查载入的class文件数据的正确性 (b)准备:给类的静态变量分配存储空间 (c)解析:将符号引用转成直接引用 3、初始化:对静态变量,静态代码块执行初始化工作 类的加载过程 当Java程序需要使用某个类时,如果该类还未被...
- 下一篇
【Java】深入理解Java虚拟机的读书笔记
java虚拟机所管理的内存包括以下几个运行时数据区域【程序计数器】线程私有,是一块较小的内存空间,当前线程执行的字节码的行号指示器,处理分支、循环、跳转、异常处理、线程恢复等基础功能,每个线程都需要有一个独立的程序计数器【虚拟机栈】线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每个方法被执行的时候同时创建一个栈帧,存放局部变量表、操作栈、动态链接、方法出口等信息。每个方法的调用到完成对应了栈帧从入栈到出栈的过程,局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变局部变量表的大小。【本地方法栈】为使用到的Native方法服务,与虚拟机栈所发挥的作用是非常相似的。【Java堆】最大的一块,被所有线程共享的一块内存区域,在虚拟机启动时创建,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此被称为GC堆。Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。【方法区】各个线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。不需要连续的内存,垃圾收集行为在这个区域是比较少出现的,这...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- CentOS8编译安装MySQL8.0.19
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Mario游戏-低调大师作品
- CentOS6,CentOS7官方镜像安装Oracle11G