类加载器的继承层次
java类加载器按照如下图所示的结构组织,各加载器各司其职只加载自己管辖范围内的类。
引导类加载器(Bootstrap):java虚拟机内置的加载器,在虚拟机启动的时候会用这个类加载器来加载 JDK安装目录下的 /JRE/LIB/rt.jar ,也就是系统默认导入的一些类(如下图所示)。不能通过代码直接获取引导类加载器的引用,获取的都是null。
扩展类加载器(ExtClassLoader):这个类加载器加载JDK安装目录下的/JRE/LIB/ext 目录中的类:
系统类加载器(AppClassLoader):根据java程序的classpath加载对应的类。可以通过ClassLoader.getSystemClassLoader()来取得。
public class Demo { public static void main(String[] args) { //系统类加载器 ClassLoader demoClassLoader = Demo.class.getClassLoader(); System.out.println(demoClassLoader); //sun.misc.Launcher$AppClassLoader@5e87512 //扩展类加载器 ClassLoader demoClassLoaderParent = demoClassLoader.getParent(); System.out.println(demoClassLoaderParent); //sun.misc.Launcher$ExtClassLoader@605df3c5 //引导类加载器 ClassLoader objectClassLoader = Object.class.getClassLoader(); System.out.println(objectClassLoader); //null System.out.println(demoClassLoaderParent.getParent()); //null } }
JVM判断两个类相同的准则
只有当类的全限定名相同且加载此类的类加载器(类的定义加载器)相同,jvm才认为两个类相同。不同类加载器加载的类之间不兼容,类A的同一份A.class文件被ClassLoaderA和ClassLoaderB加载后会定义出两个表示类A的java.lang.Class实例,这两个实例是不同的。
代理加载类
如果由用户自定义的类加载器来加载核心库,那么系统中同一个核心类就可能存在多个版本,互相之间不兼容。如果加载核心类的工作交由引导类加载器来完成,就不会出现多版本的问题。试想com.lang.Object由两个不同的类加载器加载,则jvm中存在两个表征Object的Class,因此可能实例化出两种Object对象,但是彼此不相等,terrible! 另一个问题,如果用户自定义java.lang.Object类,且用自定义的类加载器来加载,那么核心库提供的类就被屏蔽了,同样用户也可以对核心库进行任意修改,这都将导致java核心库处于不安全的状态。为了解决这些问题,java类加载器使用向上代理的方式加载类,即发起加载任务的类加载器优先将加载任务代理给其父类加载器,如果父类加载器加载不到就继续代理给上层类加载器,直至引导类加载器,如果还是未加载到,最初发起加载任务的类加载器就自己加载类,这样核心库的类只会被jvm指定的类加载器加载,自定义的java.lang.Object类永远不会被加载。类加载器工作的核心源码如下
类加载器优先将加载任务代理给其父类,所以真正完成加载任务的加载器(类的定义加载器)和发起加载任务的加载器( 初始加载器 )可能不是同一个。类的定义加载器才是jvm判断两个类是否相同的一个准则。如下图, 类A的类定义加载器会发起类B的加载,因此类A的类定义加载器是类B的初始化加载器。
类的初始加载器通过调用loadClass( ) 来启动类加载,此过程可能抛出java.lang.ClassNotFoundException,类的定义加载器通过调用其defineClass( )来加载类,此过程可能抛出java.lang.NoClassDefFoundError。类加载器在成功加载某个类之后,会把得到的 java.lang.Class
类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载,也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass()
方法不会被重复调用。每个Class对象对保留有对定义他的类加载器的引用,通过对类调用getClassloader( )来获取该引用。
当自定义类加载器时,为了保证委托机制,建议重写 findClass(String binaryName) 而不是重写loadClass(String binaryName,Boolean resolve)。重写findClass主要是实现另一种获取字节码的方式。
线程上下文类加载器
java提供了很多服务提供者接口(Servie Provider Interface),例如jdbc、jndi、jaxp等,这些spi接口定义在java核心库中,但是spi的实现(服务提供者)由第三方提供(例如mysql提供mysql数据库驱动,oralce提供oracle提供oracle的驱动),以应用依赖的jar的形式存在,接口由引导类加载器加载,实现由系统类加载器加载,因为引导类加载器(加载器的鼻祖)只加载java核心库,无法向下代理给系统类加载器加载spi的实现类,因此在spi接口中无法加载到spi的实现。怎么解决这个问题呢?为了解决这个问题,java提供了线程上下文类加载器,java应用程序的主线程上下文类加载器是系统类加载,子线程默认继承父线程的上下文的类加载器。不同与向上代理的模式,线程上下文类类加载器相当于父类加载器委托子类加载器代理加载父类加载不到的类。有了线程上下文类加载器,引导类加载器就可以委托它为spi加载相应的实现。可以通过Thread.currentThread().getContextClassLoader( )类获得当前线程的上下文类加载器。
自定义类加载器
自定义类加载器通常是为了通过特殊途径获取字节码或者对字节码进行特殊处理,通常只需要重写Class<?> findClass(String name) 方法即可。以下demo通过自定义列加载器来加载经过DES加密算法加密后的字节码文件,生成相应的类。public class MatrixClassLoader extends ClassLoader { //字节码文件路径 private String clazzFilePath; MatrixClassLoader(String clazzFilePath){ this.clazzFilePath = clazzFilePath; } /** * 重写findClass(String name),实现自己的获取字节码的方式 。 * 此处通过des解密算法对加密的字节码文件解密,并返回解密后的字节码文件的字节数组 */ @Override protected Class<?> findClass(String name) { byte[] clazzByte = null; try { DES des = new DES("123"); //用123解密字节码,用字节数据返回解密后的字节码数据 clazzByte = des.decryp(clazzFilePath); return defineClass(name, clazzByte, 0, clazzByte.length); //将字节码数据交给ClassLoader处理 } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { String path = "C:/Users/Administrator/Desktop/bin/Clazz.class"; //加密前的字节码文件 DES des = new DES("123"); //用123加密字节码,返回加密文件的路径 String encryptedFilePath =des.encryFile(path); MatrixClassLoader classLoader = new MatrixClassLoader(encryptedFilePath); Class clazz = classLoader.loadClass("com.hsh.cl.Clazz"); } }
相关推荐
类加载器的设计遵循双亲委派模型,它分为三个主要部分:启动类加载器、扩展类加载器和应用类加载器。 #### 二、类加载过程 类加载过程主要包括三个步骤: 1. **加载**:通过类的全限定名找到该类的二进制字节流。...
### 类加载器详解 #### 一、类加载器概述 **类加载器(ClassLoader)**是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译好的`.class`文件加载到JVM中,使得这些类可以在Java环境中运行。类加载器不仅能够加载类...
- Java中的类加载器采用双亲委派模型,即一个类首先由启动类加载器Bootstrap ClassLoader尝试加载,如果找不到则交给扩展类加载器Extension ClassLoader,再找不到则交由应用程序类加载器AppClassLoader,最后如果...
在Java编程语言中,类加载器(ClassLoader)是运行时环境的一个重要组成部分,它负责将类的字节码从各种来源加载到Java虚拟机(JVM)中,从而使得程序可以执行。自定义类加载器允许开发人员根据特定需求定制加载类的...
类加载器遵循双亲委派模型,这意味着当一个类加载器尝试加载类时,它首先会将请求委托给其父类加载器,直到到达顶层的Bootstrap ClassLoader,如果父类加载器无法找到该类,子类加载器才会尝试自己加载。 在Tomcat...
Java 类加载器原理 Java 类加载器是Java虚拟机(JVM)的核心组成部分,它负责将类的字节码加载到内存中并转换为可执行的Java类。类加载器的作用不仅仅是加载类,还包括确保类的唯一性,避免重复加载,并且遵循特定...
1. Bootstrap ClassLoader:这是最基础的类加载器,由JVM本身实现,负责加载JRE的`<JAVA_HOME>/lib`目录下的核心类库,或者被`-Xbootclasspath`参数指定的路径中的类。 2. Extension ClassLoader:扩展类加载器,...
类加载器的知识不仅仅局限于基础概念,还包括类加载的时机(静态加载、动态加载)、类加载器的实现(如自定义类加载器)、类的卸载、以及类加载器与安全策略的关系等。深入理解和掌握这些知识点,对于开发高效、安全...
类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...
系统类加载器在加载类时,会先尝试让扩展类加载器加载,如果扩展类加载器无法加载,则再由系统类加载器自己尝试加载。这样的设计是为了保证核心类库的唯一性和安全性,避免用户自定义的类覆盖了 JDK 内置的核心类。 ...
Java开发中的类加载器是Java运行环境的核心组件之一,它负责查找、加载和初始化类文件。在传统的Java应用中,类通常是从硬盘上的类路径(ClassPath)或模块路径(ModulePath)中加载的。然而,随着开发模式的演变,...
Java类加载器是Java虚拟机(JVM)的关键组成部分,它负责查找并加载类到内存中,使得程序能够运行。自定义Java类加载器允许我们根据特定需求扩展默认的加载机制,例如,从非标准位置加载类或者实现动态加载。在Java...
Java 类加载器是Java运行时环境的一个重要组成部分,它的主要职责是将编译后的字节码(.class文件)加载到JVM中,使得程序能够运行。类加载器的机制保证了类的唯一性,同时也提供了灵活性,允许我们自定义加载逻辑。...
在Java中,类加载器按照层次结构进行组织,包括引导类加载器、扩展类加载器和应用程序类加载器,每个都有特定的职责。 1. **引导类加载器(Bootstrap ClassLoader)**:这是最基础的类加载器,由JVM本身实现,主要...
Java的类加载器是Java虚拟机(JVM)的核心组件之一,它负责将类的字节码从磁盘、网络或其他数据源加载到内存中,并转换为可执行的Java对象。类加载器不仅关乎程序的运行,还在实现动态加载、插件系统等方面发挥着...
在Java编程中,静态代码块(Static Block)和类加载器(Class Loader)是两个重要的概念,它们在软件开发中有着广泛的应用。本案例聚焦于如何利用静态代码块结合类加载器来高效地获取资源文件,尤其是属性配置文件。...
默认的类加载器包括bootstrap classloader(引导类加载器)、extension classloader(扩展类加载器)和appclassloader(应用程序类加载器),它们按照双亲委托模型进行工作,从基础到具体逐层尝试加载类。...
在Java编程语言中,类加载器(ClassLoader)是至关重要的组成部分,它负责将类的字节码从磁盘、网络或其他存储介质加载到JVM(Java虚拟机)中,并将其转换为可执行的Java对象。类加载器的学习是深入理解Java运行机制...
类加载器是Java虚拟机(JVM)的重要组成部分,它负责将类的字节码从文件系统或网络中加载到JVM中,并转换为运行时的java.lang.Class对象。类加载器不仅涉及程序的启动,还关系到类的动态加载、类间的隔离以及安全性...