类加载器的作用是完成类加载过程中的装载步骤,即将.class文件加载到JVM
有两种类型的类加载器
1、JVM自带的类加载器
根类加载器(Bootstrap)
扩展类加载器(Extension)
系统类加载器(System)
2、用户自定义类加载器
扩展自java.lang.ClassLoader
这些类加载器以父子关系的形式存在
最顶层的类加载器,根类加载器,负责加载java的核心类,它加载的路径是jre/lib/rt.jar,我们用到的java核心类如集合框架类ArrayList以及String类等,都是在rt.jar中,即这些类是被BootStrap ClassLoader加载的;BootStrap ClassLoader 是用C++实现的;
Extension ClasssLoader负责加载jre的扩展类,它加载的路径是jre/lib/ext,它的父类加载器是BootStrap ClassLoader;
System ClassLoader从CLASSPATH环境变量指定的路径加载类,一般我们自己编写的类都是由System ClassLoader加载,System ClassLoader的父类加载是Extension ClasssLoader。
除了BootStrap ClassLoader外,其他的ClassLoader(包括用户自定义的)都需要继承..ClassLoader
类加载器在加载类时,采取父委托机制,即加载类时,先请求父类加载器去加载,如果能加载,则在父类加载器加载完后直接返回,如果父类加载器无法加载,则自身尝试去加载,比如,加载我们自己编写的....User这个类,首先由System ClassLoader加载,System ClassLoader委托父类加载器Extension ClasssLoader加载,Extension ClasssLoader委托BootStrap ClassLoader加载,BootStrap ClassLoader加载不到,Extension ClasssLoader也加载不到,System ClassLoader尝试自己加载,从CLASSPATH环境变量指定的路径加载,如果该路径下有这个类,则加载进来,如果找不到,抛出..ClassNotFoundException
JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误,如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类的加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。
同一个命名空间内的类是互相可见的
子加载器的命名空间包含所有父加载器的命名空间,因此,由子加载器加载的类能看见父加载器加载的类,例如,系统类加载器加载的类能看到根类加载器加载的类
由父加载器加载的类不能看见子加载器加载的类
如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见
接下来看几个测试
创建两个类User、Person,代码如下:
public class User {
public User() {
System.out.println("User loaded by " + this.getClass().getClassLoader());
new Person();
}
}
public class Person {
public Person() {
System.out.println("Person loaded by " + this.getClass().getClassLoader());
}
}
分别在构造器里输出加载当前类的ClassLoader对象,在User的构造器中创建了Person对象
测试1:
@Test
public void testClassLoader1() {
System.out.println(this.getClass().getClassLoader());
new User();
System.out.println("String loaded by " + String.class.getClassLoader());
}
控制台输出如下:
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1
String loaded by null
可以看出,当前测试类以及User、Person类都是由系统类加载器加载的,而String类是由根类加载器加载的,由于根类加载器是由C++实现的,所以在代码中获取会返回null
测试2:
@Test
public void testClassLoader2() {
ClassLoader classLoader = this.getClass().getClassLoader();
while(true) {
System.out.println(classLoader);
if (null == classLoader) {
break;
}
classLoader = classLoader.getParent();
}
}
从加载当前测试类的类加载器开始,输出父类加载器,控制台输出如下:
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$ExtClassLoader@42e816
null
可以看出,类加载器采用父子关系
接下来自定义一个类加载器,从指定的loadPath路径加载class,代码如下:
public class MyClassLoader extends ClassLoader {
private String name;
/**加载路径*/
private String loadPath;
private static final String EXTENSION = ".class";
public MyClassLoader(String name, String loadPath) {
super();
this.name = name;
this.loadPath = loadPath;
}
public MyClassLoader(String name, String loadPath, ClassLoader parent) {
super(parent);
this.name = name;
this.loadPath = loadPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassData(name);
if (null == b || b.length == 0) {
throw new ClassNotFoundException();
}
return defineClass(name, b, 0, b.length);
}
/**
* 加载读取class文件
* @param name
* @return
*/
private byte[] loadClassData(String name) {
String dir = name.replace(".", File.separator);
String realPath = this.loadPath + File.separator + dir + EXTENSION;
ByteArrayOutputStream baos = null;
DataInputStream dis = null;
int end = 0;
try {
baos = new ByteArrayOutputStream();
dis = new DataInputStream(new FileInputStream(realPath));
while ((end = dis.read()) != -1) {
baos.write(end);
}
} catch (IOException e) {
//e.printStackTrace();
} finally {
if (null != baos) {
try {
baos.close();
} catch (IOException e) {
}
}
if (null != dis) {
try {
dis.close();
} catch (IOException e) {
}
}
}
return baos.toByteArray();
}
@Override
public String toString() {
return this.name;
}
}
类加载器在加载类时,会调用ClassLoader的loadClass(String name),查看jdk源码,loadClass方法中会调用父类加载器去加载,如果加载不到,会调用findClass(String name)自行加载,因此自定义的类加载器都需要覆盖findClass(String name)方法,以下为jdk中ClassLoader的loadClass方法的部分源码:
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
创建测试类,如下:
public class ClassLoaderTest1 {
public static void main(String[] args) {
MyClassLoader classLoader1 = new MyClassLoader("loader1", "E:\\classloader\\path1");
Class<?> clazz1;
try {
System.out.println("---------1----------");
clazz1 = classLoader1.loadClass("com.jiangnan.classloader.classloader.User");
clazz1.newInstance();
} catch (Exception e) {
System.out.println(e.getClass().getName());
}
System.out.println("---------1----------");
MyClassLoader classLoader2 = new MyClassLoader("loader2", "E:\\classloader\\path2", classLoader1);
Class<?> clazz2;
try {
System.out.println("---------2----------");
clazz2 = classLoader2.loadClass("com.jiangnan.classloader.classloader.User");
clazz2.newInstance();
} catch (Exception e) {
System.out.println(e.getClass().getName());
}
System.out.println("---------2----------");
}
}
创建了两个MyClassLoader的实例,其中classLoader1是classLoader2的父类加载器,分别从指定的路径加载User类,运行此main方法,控制台输出如下:
---------1----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------
两次都是由应用类加载器加载的,因为classLoader2的父加载器为应用类加载器,根据父委托机制,应用类加载器在CLASSPATH路径下能加载到User类
将工程bin目录下的User.class文件删除,放到 E:\\classloader\\path1,注意,需要按照指定的包目录存放,例如,在我的环境中,包名是com.jiangnan.classloader.classloader,因此,User.class被放在E:\classloader\path1\com\jiangnan\classloader\classloader目录下
现在的情况是,测试类ClassLoaderTest1、自定义类加载器类MyClassLoader以及Person类位于工程bin目录下,二User类位于path1路径下,运行main方法,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------
User类是由classLoader1实例加载的,因为此时系统类加载器在bin目录下加载不到User类
在main方法中增加classLoader3实例,如下:
MyClassLoader classLoader3 = new MyClassLoader("loader3", "E:\\classloader\\path3", null);
Class<?> clazz3 = null;
try {
System.out.println("---------3----------");
clazz3 = classLoader3.loadClass("com.jiangnan.classloader.classloader.User");
clazz3.newInstance();
} catch (Exception e) {
System.out.println(e.getClass().getName());
}
System.out.println("---------3----------");
System.out.println("clazz1 == clazz2 is " + (clazz1 == clazz2));
System.out.println("clazz1 == clazz3 is " + (clazz1 == clazz3));
指定classLoader3的父类加载器为null,即classLoader3的类加载器为BootStrap根类加载器,同时判断clazz1和clazz2、clazz3是否相等,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------
---------3----------
User loaded by loader3
Person loaded by loader3
---------3----------
clazz1 == clazz2 is true
clazz1 == clazz3 is false
clazz1与clazz3不相等,因为它们是被不同的类加载器加载,它们在内存中的存在形式如下:
测试代码位于https://github.com/ywu2014/ClassLoader
相关推荐
类加载器的设计遵循双亲委派模型,它分为三个主要部分:启动类加载器、扩展类加载器和应用类加载器。 #### 二、类加载过程 类加载过程主要包括三个步骤: 1. **加载**:通过类的全限定名找到该类的二进制字节流。...
#### 三、类加载器的基本概念 1. **Bootstrap ClassLoader**: 这是由JVM内部实现的类加载器,主要用于加载核心Java类库(即所有以`java.*`开头的类)。它是用本地代码实现的,并不继承自`java.lang.ClassLoader`。 ...
- Java中的类加载器采用双亲委派模型,即一个类首先由启动类加载器Bootstrap ClassLoader尝试加载,如果找不到则交给扩展类加载器Extension ClassLoader,再找不到则交由应用程序类加载器AppClassLoader,最后如果...
这个层次结构通常由三个主要的类加载器组成: 1. **bootstrap class loader**:这是最基础的类加载器,由C++实现,负责加载JRE核心库,如rt.jar,它包含了Java语言的基础类库,如`java.lang.Object`等。 2. **...
类加载器是Java虚拟机(JVM)的重要组成部分,它负责将类的字节码从文件系统或网络中加载到JVM中,并转换为运行时的java.lang.Class对象。类加载器不仅涉及程序的启动,还关系到类的动态加载、类间的隔离以及安全性...
系统类加载器在加载类时,会先尝试让扩展类加载器加载,如果扩展类加载器无法加载,则再由系统类加载器自己尝试加载。这样的设计是为了保证核心类库的唯一性和安全性,避免用户自定义的类覆盖了 JDK 内置的核心类。 ...
1. Bootstrap ClassLoader:这是最基础的类加载器,由JVM本身实现,负责加载JRE的`<JAVA_HOME>/lib`目录下的核心类库,或者被`-Xbootclasspath`参数指定的路径中的类。 2. Extension ClassLoader:扩展类加载器,...
Java中的类加载器主要有三种:Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。它们按照双亲委派模型工作,从根加载器开始,逐级尝试加载类。当我们想要对类进行加密时,可以创建一个自定义的类...
默认的类加载器包括bootstrap classloader(引导类加载器)、extension classloader(扩展类加载器)和appclassloader(应用程序类加载器),它们按照双亲委托模型进行工作,从基础到具体逐层尝试加载类。...
在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt.jar;Extension ClassLoader则加载JRE扩展目录下的jar文件...
Java开发中的类加载器是Java运行环境的核心组件之一,它负责查找、加载和初始化类文件。在传统的Java应用中,类通常是从硬盘上的类路径(ClassPath)或模块路径(ModulePath)中加载的。然而,随着开发模式的演变,...
java类加载器学习三、类加载器的委托模式
本部分我们将深入探讨JVM中的类加载器,特别是根类加载器、扩展类加载器和系统类加载器。 首先,让我们了解类加载的基本过程。当JVM启动时,会触发类加载。这个过程分为三个阶段:加载、链接和初始化。加载阶段,类...
#### 三、类加载器模式 在WebSphere Application Server中,有两种主要的类加载器模式: 1. **PARENT_FIRST**:这种模式下,父类加载器会先于子类加载器加载类。这种方式有利于保持应用程序的一致性,但是可能导致...
Java的类加载器体系结构由三个主要类型的类加载器组成: 1. **引导类加载器(Bootstrap ClassLoader)**:这是最基础的类加载器,负责加载Java的核心库类,如`java.lang`包下的类。 2. **扩展类加载器(Extension ...
类加载器的工作原理可以分为三个阶段:加载、链接和初始化。在加载阶段,类加载器会查找和加载指定的类。在链接阶段,类加载器会对加载的类进行链接,包括验证、准备和解析。在初始化阶段,类加载器会对加载的类进行...
Java中有三种类加载器:引导类加载器、扩展类加载器和系统类加载器。 1. 引导类加载器:负责加载jdk中的系统类,如String、Integer等,这些类都是由引导类加载器加载的。引导类加载器是用C语言实现的,在Java程序中...
除了这三种系统类加载器,还可以自定义类加载器,满足特定的加载需求,例如加载网络上的类或加密后的类文件。 类加载器的代理模式体现在父子加载器的关系上。当一个类加载器尝试加载类时,它会先委托给父类加载器,...
本文将深入探讨三种核心的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)以及应用程序类加载器(Application ClassLoader)。此外,还将详细介绍双亲委托模型及其工作...
在Java编程语言中,类加载器(ClassLoader)是至关重要的组成部分,它负责将类的字节码转换为可执行的Java对象。理解类加载器的工作原理对于深入掌握Java虚拟机(JVM)运行机制和进行高效的问题排查至关重要。在这个...