ClassLoader 是 Java 虚拟机 (JVM) 的类装载子系统,它负责将 Java 字节码装载到 JVM 中, 并使其成为 JVM 一部分。 JVM 的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块 , 而不影响系统其他功能模块的正常运行。本文将分析 JVM 中的类装载系统,探讨 JVM 中类装载的原理、实现以及应用。
类装载就是寻找一个类或是一个接口的字节码文件并通过解析该字节码来构造代表这个类或是这个接口的 class 对象的过程。在 Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
1. 装载:查找和导入类或接口的字节码;
2. 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
l 校验:检查导入类或接口的二进制数据的正确性;
l 准备:给类的静态变量分配并初始化存储空间;
l 解析:将符号引用转成直接引用;
3. 初始化:激活类的静态变量的初始化 Java 代码和静态 Java 代码块。
至于在类装载和虚拟机启动的过程中的具体细节和可能会抛出的错误,请参看《 Java 虚拟机规范》以及《深入 Java 虚拟机》。 由于本文的讨论重点不在此就不再多叙述。
JVM 中类的装载是由 ClassLoader 和它的子类来实现的。 Java ClassLoader 是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入Java 字节码。
在 Java 中, ClassLoader 是一个抽象类,它在包 java.lang 中。可以这样说,只要了解了 ClassLoader 中的一些重要的方法,再结合上面所介绍的 JVM 中类装载的具体的过程,对动态装载类这项技术就有了一个比较大概的掌握,这些重要的方法包括以下几个:
1. loadCass 方法: loadClass(String name ,boolean resolve) 其中 name 参数指定了 JVM 需要的类的名称 , 该名称以类的全限定名表示,如Java.lang.Object ; resolve 参数告诉方法是否需要解析类,在初始化类之前,应考虑类解析,并不是所有的类都需要解析,如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。这个方法是 ClassLoader 的入口点。
2. defineClass 方法 这个方法接受类文件的字节数组并把它转换成 Class 对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。
3. findSystemClass 方法 findSystemClass 方法从本地文件系统装入 Java 字节码。它在本地文件系统中寻找类文件,如果存在,就使用defineClass 将字节数组转换成 Class 对象。当运行 Java 应用程序时 , 这是 JVM 正常装入类的缺省机制。
4. resolveClass 方法 resolveClass(Class c) 方法解析装入的类,如果该类已经被解析过那么将不做处理。当调用 loadClass 方法时 , 通过它的resolve 参数决定是否要进行解析。
5. findLoadedClass 方法 当调用 loadClass 方法装入类时 , 调用 findLoadedClass 方法来查看 ClassLoader 是否已装入这个类 , 如果已装入 ,那么返回 Class 对象 , 否则返回 NULL 。如果强行装载已存在的类 , 将会抛出链接错误。
一般来说,我们实现自定义的 ClassLoader 需要继承抽象类 java.lang.ClassLoader ,其中必须实现的方法是 loadClass(String name) ,对于这个方法需要实现如下操作:
(1) 确认类的名称;
(2) 检查请求要装载的类是否已经被装载;
(3) 检查请求加载的类是否是系统类;
(4) 尝试从类装载器的存储区获取所请求的类;
(5) 在虚拟机中定义所请求的类;
(6) 解析所请求的类;
(7) 返回所请求的类。
所有的 Java 虚拟机都包括一个内置的类装载器,这个内置的类库装载器被称为根装载器 (bootstrap ClassLoader) 。根装载器的特殊之处是它只能够装载基本的 Java 类,如 rt.jar 中的 class 。当应用程序可以使用用户自定义的 ClassLoader 来加载特定 ClassPath 下的类。下面的例子是 JDK5.0的 URLClassLoader 的实现。
public class URLClassLoader extends SecureClassLoader {
……
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return (Class)
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws ClassNotFoundException {
// 由类的全限定名得到物理路径
String path = name.replace('.', '/').concat(".class");
// 从 URLs ClassPath 中取得相应的字节码
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 由 Java 字节码创建一个 Class 对象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
……
}
|
2.3 Java 虚拟机的类装载原理
前面我们已经知道,一个 Java 应用程序使用两种类型的类装载器:根装载器 (bootstrap) 和用户定义的装载器 (user-defined) 。根装载器是 Java虚拟机实现的一部分。根装载器以某种默认的方式将类装入,包括 Java API 的类。在运行期间,一个 Java 程序能使用用户自己定义的类装载器。根装载器是虚拟机固有的一部分,而用户定义的类装载器则不是,它是用 Java 语言写的,被编译成 class 文件之后然后再被装入到虚拟机,并像其它的任何对象一样可以被实例化。 Java 类装载器的体系结构如下所示:
Java 的类装载的体系结构
Java 的类装载模型是一种代理 (delegation) 模型。当 JVM 要求类装载器 CL(ClassLoader) 装载一个类时 ,CL 首先将这个类装载请求转发给他的父装载器。只有当父装载器没有装载并无法装载这个类时 ,CL 才获得装载这个类的机会。这样 , 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器 (bootstrap ClassLoader) , 在 JVM 中它以 "null" 表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装载器时 , 如果没有显式地给出父装载器 , 那么 JVM 将默认系统装载器为其父装载器。 Java 的基本类装载器代理结构如图 2 所示:
Java 类装载的代理结构
下面针对各种类装载器分别进行详细的说明。
根 (Bootstrap) 装载器:该装载器没有父装载器,它是 JVM 实现的一部分,从 sun.boot.class.path 装载运行时库的核心代码。
扩展 (Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯 Java 代码实现的,它从 java.ext.dirs ( 扩展目录 ) 中装载代码。
系统 (System or Application) 装载器:装载器为扩展装载器,我们都知道在安装 JDK 的时候要设置环境变量 (CLASSPATH ) ,这个类装载器就是从 java.class.path(CLASSPATH 环境变量 ) 中装载代码的,它也是用纯 Java 代码实现的,同时还是用户自定义类装载器的缺省父装载器。
小应用程序 (Applet) 装载器:父装载器为系统装载器,它从用户指定的网络上的特定目录装载小应用程序代码。
在设计一个类装载器的时候,应该满足以下两个条件:
对于相同的类名,类装载器所返回的对象应该是同一个类对象
如果类装载器 CL1 将装载类 C 的请求转给类装载器 CL2 ,那么对于以下的类或接口, CL1 和 CL2 应该返回同一个类对象:
a) S 为 C 的直接超类;
b) S 为 C 的直接超接口;
c) S 为 C 的成员变量的类型;
d) S 为 C 的成员方法或构建器的参数类型;
e) S 为 C 的成员方法的返回类型。
每个已经装载到 JVM 中的类对象都含有装载它的类装载器的信息。 Class 类的方法 getClassLoader 可以得到装载这个类的类装载器。一个类装载器认识的类包括它的父装载器认识的类和它自己装载的类,由此可见类装载器认识的类是它自己装载的类的超集。注意,我们可以得到类装载器的有关的信息,但是已经装载到 JVM 中的类是不能更改它的类装载器的。
Java 中的类的装载过程也就是代理装载的过程。比如: Web 浏览器中的 JVM 需要装载一个小应用程序 TestApplet 。 JVM 调用小应用程序装载器 ACL(Applet ClassLoader) 来完成装载。 ACL 首先请求它的父装载器 , 即系统装载器装载 TestApplet 是否装载了这个类,由于 TestApplet 不在系统装载器的装载路径中 , 所以系统装载器没有找到这个类 , 也就没有装载成功。接着 ACL 自己装载 TestApplet 。 ACL 通过网络成功地找到了TestApplet.class 文件并将它导入到了 JVM 中。在装载过程中 , JVM 发现 TestAppet 是从超类 java.applet.Applet 继承的。所以 JVM 再次调用 ACL 来装载 java.applet.Applet 类。 ACL 又再次按上面的顺序装载 Applet 类 , 结果 ACL 发现他的父装载器已经装载了这个类 , 所以 ACL 就直接将这个已经装载的类返回给了 JVM , 完成了 Applet 类的装载。接下来 ,Applet 类的超类也一样处理。最后 , TestApplet 及所有有关的类都装载到了 JVM 中。
2.4 总结
类的动态装载机制是JVM的一项核心技术, 也是容易被忽视而引起很多误解的地方。本文介绍了JVM中类装载的原理、实现以及应用,分析了ClassLoader的结构、用途以及如何利用自定义的ClassLoader装载并执行Java类,希望能使大家对JVM中的类装载有一个比较深入的理解。
相关推荐
关于类加载器的 上课ppt -java虚拟机自带的加载器 根类加载器(Bootstrap) c++写的看不到扩展类加载器(extension) 系统类加载器(System) AppClassLoad 用户自定义的类加载器 Java.lang.ClassLoader的子类
【JAVA内存结构】 Java内存结构主要分为五个主要区域:堆(JAVA HEAP)、虚拟机栈(JAVA VIRTUAL MACHINE STACKS)、本地方法栈(NATIVE METHOD STACK)、方法区(METHOD AREA)和程序计数器(RUNTIME CONSTANT ...
Java 类加载机制是Java语言的核心特性之一,它允许程序在运行时动态加载和解析类,极大地增强了灵活性和可扩展性。本文将详细讲解类加载的基础知识、Java虚拟机(JVM)如何使用类加载器以及如何自定义类加载器。...
在java中 当你用一个类加载器 去加载一个类的时候 这个类里面所有的出现的过得类 都会通过这个类加载器加载在这个类后续 的操作中,只要是有由这个类衍生出的 那么涉及的类 都由这个类加载器进行加载 该类加载器为...
面试必考之HashMap源码分析与实现 探索JVM底层奥秘ClassLoader源码分析与案例讲解 面试必备技能之Dubbo企业实战 分布式框架Zookeeper之服务注册与订阅 互联网系统垂直架构之Session解决方案 分库分表之后分布式下...
同时,通过注册事件回调,代理可以在特定事件发生时接收通知,例如`jvmtiEvent`中的`VMStart`、`ClassLoad`或`AllocationEvent`等。 以下是一个简单的示例,展示如何使用JVMTI来获取Java应用程序的线程和内存使用...
破解java加密的ClassLoader.java,在classloader植入破解代码
本资料将通过源码分析,讲解如何实现这一功能。 首先,我们需要理解Java的类加载机制。在Java中,类加载器负责查找并加载类的字节码文件(.class)。默认情况下,系统类加载器会根据类的全限定名在ClassPath中查找...
在《Java虚拟机类加载机制》一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的后留了一个悬念给各位,这里来揭开这个悬念。建议先看完《Java虚拟机类加载机制》这篇再来看这个,印象会比较深刻...
在Java虚拟机(JVM)中,类加载器(ClassLoader)是至关重要的组成部分,它负责查找和加载类的字节码文件。理解ClassLoader的工作机制对于深入掌握Java应用程序的运行至关重要。这里我们将详细讨论ClassLoader的运行...
《深入理解Maven类加载器插件:解决插件中的类加载问题》 在Java开发领域,Maven作为一款强大的项目管理和构建工具,扮演着举足轻重的角色。然而,随着项目的复杂度增加,类加载问题时常困扰着开发者。...
"深入Spring Boot之ClassLoader的继承关系和影响" Spring Boot中的ClassLoader继承关系是一种复杂的机制,在Spring Boot应用程序中扮演着重要角色。ClassLoader是Java中的一种机制,用于加载类和资源。...
JavaSE ...│ ├─classLoad │ │ ├─ClassCypher.java │ │ ├─ClassLoadDemo.java │ │ ├─EncryptedCalss.java │ │ └─MyClassLoad.java │ └─designPattern │ ├─decorator <d
基于SAF和第三方Plugin库的 插件1 依赖的plugin库目前此插件项目支持SAF框架,但是不能使用SAF框架中的基类如SAFapp等,可能导致和宿主基类冲突(同名,但是不属于一个Classload)不能识别。建议可以在plugin项目中...
通过上述方法,我们不仅能够利用 Java 反序列化漏洞来执行命令,还能够接收到执行命令的结果反馈,这对于进一步的渗透测试或安全分析非常有用。需要注意的是,这些技巧应当仅限于合法的安全测试场景中使用,切勿滥用...
JBoss 5是一款知名的Java应用服务器,其在类加载(Class Loading)方面有着独特的处理方式。随着OSGi(Open Services Gateway Initiative)风格的类加载机制逐渐流行,以及新的Java模块和类加载规范的出现,JBoss对...
类加载工具,用于java项目中动态加载需要的类库
classload是用类加载器实现jar细节隐藏的一个想法,其中EvlJar为替换用到的文件 go-socket为golang实现socket,包含TCP和UDP实现 出现了找不到类加载器的问题,似乎是环境问题2021-2-12 已解决参数错误2021-2-13 ...