JVM类加载器分为四种:

  • 根类加载器(Bootstrap ClassLoader): 加载 JRE/lib/rt.jar 或者 Xbootclasspath选项指定的jar包,由C++实现,不是ClassLoader子类

  • 扩展类加载器(Extension ClassLoader): 加载JRE/lib/ext/*.jar 或者 -Djava.ext.dirs 指定目录下的jar包

  • 系统类加载器(App ClassLoader): 加载ClASSPATH或者-Djava.class.path指定目录下的类和jar包

  • 用户自定义类加载器(Custom ClassLoader): 通过java.lang.ClassLoader的子类自定义加载class

系统类加载器和扩展类加载器都定义在sun.misc.Launcher类种,作为静态内部类呈现。

类加载器的父亲委托机制:

  1. 自底向上检查类是否已经加载
  2. 自顶向下尝试加载类

类加载器与初始化

请看如下代码:

package classloader1;public class ClassLoaderTest {public static void main(String[] args) throws ClassNotFoundException {ClassLoader classLoader = ClassLoader.getSystemClassLoader();Class<?> clazz1 = classLoader.loadClass("classloader1.C");System.out.println("-----------------------------");Class<?> clazz = Class.forName("classloader1.C");System.out.println(clazz1 == clazz);}
}
class C {static {System.out.println("class C static block");}
}

输出结果:

-----------------------------
class C static block
true

结论: 调用classLoader.loadCLass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。而反射Class.forName(“…”) 是对类主动使用的一种方式,会导致类的初始化。

获取ClassLoader的方式

  • 获得当前类的classLoader: clazz.getCLassLoader()

  • 获得当前线程上下文的classLoader: Thread.currentThread().getContextClassLoader();

  • 获得系统的classLoader: ClassLoader.getSystemClassLoader();

如何自定义类加载器

自定义类加载器步骤:

  1. 继承ClassLoader抽象类,重写findClass方法,该方法调用defineClass方法返回class对象
  2. 自定义loadClassData方法,该方法将类名字符串转换为字节数组
  3. 在真正去加载类的时候,应该调用loadClass方法,返回class对象

看一个具体自定义加载器的实例:

public class MyClassLoader extends ClassLoader {public MyClassLoader() {super(); //将系统类加载器作为该类加载器的父类加载器}public MyClassLoader(ClassLoader parent) {super(parent); //将传入的类加载器作为该类加载器的父类加载器}/*** 一定要重写ClassLoader抽象类的findClass方法** @param name* @return*/@Overrideprotected Class<?> findClass(String name) {byte[] bytes;try {bytes = loadClassData(name);//通过调用ClassLoader类的defineClass方法,将类的字节数组传入return this.defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {e.printStackTrace();return null;}}/*** 自定义私有方法,将磁盘上的class文件转换为字节数组** @param className* @return* @throws IOException*/private byte[] loadClassData(String className) throws IOException {className = className.replace(".", File.separator);FileInputStream fis = new FileInputStream(new File(className));ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1000];int len;while (-1 != (len = fis.read(buffer, 0, buffer.length))) {baos.write(buffer, 0, len);}baos.close();fis.close();return baos.toByteArray();}public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {MyClassLoader myClassLoader = new MyClassLoader();Class<?> clazz = myClassLoader.loadClass("classloader1.ClassLoaderTest");Object object = clazz.newInstance();System.out.println(object);System.out.println(object.getClass().getClassLoader());}
}

输出结果如下:

classloader1.ClassLoaderTest@4554617c
sun.misc.Launcher$AppClassLoader@18b4aac2

 可以看到,虽然自定义了类加载器,但是最后的Object实例的类加载器是AppClassLoader, 而不是我们自定义的类加载器。

 什么原因呢?本质跟ClassLoader抽象类的loadClass(String name, boolean resolve)方法有关:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {//只要父加载器不为空,递归调用该方法,委托父加载器去加载c = parent.loadClass(name, false);} else {//父加载器为空,那么当前类加载器为根类加载器c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {//如果还没加载成功,那么才会通过用户自定义的类加载器去加载c = findClass(name);//...}}if (resolve) {resolveClass(c);}return c;}

 该方法简短的代码,已经将类加载器的双亲委托机制说明的很清楚了。

结果分析:
自定义类加载器MyClassLoader的默认父加载器是AppClassLoader, classloader1.ClassLoaderTest是自定义类,在classPath下,最终会被AppClassLoader加载成功, 根本轮不到MyClassLoader去加载该类。

问题来了, 如何才能让自己定义的类加载器去加载一个类呢?

打破规则就行,即让AppClassLoader加载失败。假如我们加载类A, 首先,我们将需要加载的类的包路径目录和类的class文件挪到classpath路径之外,并且删除classpath路径下类A产生的class文件(想想为啥)。

将代码稍稍改造一下:

public class MyClassLoader extends ClassLoader {private String basePath;private String extension = ".class";public MyClassLoader() {super(); //将系统类加载器作为该类加载器的父类加载器}public MyClassLoader(String basePath) {this.basePath = basePath;}public MyClassLoader(ClassLoader parent) {super(parent); //将传入的类加载器作为该类加载器的父类加载器}@Overrideprotected Class<?> findClass(String name) {System.out.println("MyClassLoader findClass invoked");byte[] bytes;try {bytes = loadClassData(name);return this.defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {e.printStackTrace();return null;}}private byte[] loadClassData(String className) throws IOException {//通过绝对路径指定一个class文件的位置className = basePath + className.replace(".", File.separator) + extension;FileInputStream fis = new FileInputStream(new File(className));ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1000];int len;while (-1 != (len = fis.read(buffer, 0, buffer.length))) {baos.write(buffer, 0, len);}baos.close();fis.close();return baos.toByteArray();}public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\classes\\");Class<?> clazz = myClassLoader.loadClass("classloader1.ClassLoaderTest");System.out.println(clazz);Object object = clazz.newInstance();System.out.println(object.getClass().getClassLoader());}
}

运行结果如下:

MyClassLoader findClass invoked
class classloader1.ClassLoaderTest
classloader2.MyClassLoader@4554617c

嗯,自定义类加载器终于用上了。

稍微改下main方法的代码:

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\classes\\");Class<?> clazz = myClassLoader.loadClass("classloader1.ClassLoaderTest");System.out.println(clazz.hashCode());//再定义一个自定义的类加载器MyClassLoader myClassLoader1 = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\classes\\");Class<?> clazz1 = myClassLoader1.loadClass("classloader1.ClassLoaderTest");System.out.println(clazz1.hashCode());}

这里的执行结果分两种情况:
1. 当ClassPath目录下存在classloader1.ClassLoaderTest的class文件时,输出的两个class对象的hashCode值相同, 因为本质上是由AppClassLoader去加载的, 而且只会加载一次;
2. 当ClassPath目录下不存在classloader1.ClassLoaderTest的class文件时,输出的两个class对象的hashCode值不相同, 因为他们是通过自定义类加载器加载的。这也说明了一个问题:同一个类被加载了两次。如果myClassLoader1 的父类加载器是myClassLoader ,那么输出结果又会不一样,因为类加载器在同一个命名空间。

命名空间

每个类加载器都有自己的命名空间,命名空间由该类加载器及父类加载器所加载的类组成。

同一个命名空间下,不会出现相同的两个类(包名类名都相同)。
不同命名空间下,有可能会出现相同的两个类(包名类名都相同)。

类加载器的双亲委托机制本质上是一种包含关系。

类的卸载

  • 由JVM自带的类加载器(前面提到的三种)所加载的类,在JVM生命周期中,始终不会被卸载。

  • 用户自定义的类加载器所加载的类是可以被卸载的。

JVM参数: -XX:+TraceClassUnloading 可以看出程序运行哪些类被JVM卸载了。

JVM学习笔记3_类加载器相关推荐

  1. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  2. JVM学习笔记 之 类加载子系统

    类加载子系统 本篇学习笔记基于bilibili尚硅谷的jvm课程整理而来. 概述 下面是之前在第一张看到的类加载子系统简图: 完整图如下: 其中类加载过程分为三个阶段: 加载阶段:使用引导加载器.扩展 ...

  3. JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制

    一 类加载器与类加载过程 类加载子系统作用 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识. ClassLoader只负责class文件的加载,至于 ...

  4. JVM 学习三:类加载器

    类加载器 1 类加载器的分类 JVM 支持两种类型的类加载器:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader) 从概念上来 ...

  5. JVM 学习二:类加载器子系统

    1 类加载器子系统的作用 类加载器子系统负责从文件系统或者网络中加载 Class 文件,Class 文件在文件开关有特定的文件标识 ClassLoader 只负责 Class 文件的加载,至于它是否可 ...

  6. JVM学习笔记-03-类加载器及双亲委派机制

    JVM学习笔记-03-类加载器及双亲委派机制 文章目录 JVM学习笔记-03-类加载器及双亲委派机制 1. 类加载器 视频链接-最新JVM教程IDEA版[Java面试速补篇]-03-类加载器及双亲委派 ...

  7. jvm学习笔记(一)

    jvm学习笔记(一) 文章目录 jvm学习笔记(一) 1.全部笔记链接 3.类加载器 作用 类别 加载步骤 获得类加载器 4.双亲委派机制 5.沙箱安全机制 沙箱概念 JAVA沙箱的基本组件 基本组件 ...

  8. JVM学习笔记-01-JVM的学习方式

    JVM学习笔记-01-JVM的学习方式 文章目录 JVM学习笔记-01-JVM的学习方式 JVM探究 最新JVM教程IDEA版[Java面试速补篇]-01-JVM的学习方式 JVM探究 请你谈谈对JV ...

  9. 【JVM学习笔记】内存回收与内存回收算法 就哪些地方需要回收、什么时候回收、如何回收三个问题进行分析和说明

    目录 一.相关名词解释 垃圾收集常用名词 二.哪些地方需要回收 本地方法栈.虚拟机栈.程序计数器 方法区 Java堆 三.什么时候回收 1. 内存能否被回收 内存中的引用类型 引用计数算法 可达性分析 ...

最新文章

  1. 跨平台工具、组件和框架的汇总
  2. 赠票 | 来智源大会,聆听张钹院士、Michael I. Jordan等大咖分享!
  3. 安振平老师的4911号不等式问题的证明
  4. 手把手教你实现SVM算法(二)
  5. 黄聪:如何使用CodeSmith批量生成代码(原创系列教程)
  6. error: ‘to_string’ is not a member of ‘std’———已解决
  7. cpu win10 安装yolo_Win10+YOLOv3完整安装过程(亲测可运行)
  8. c语言结构-的优先级,c语言运算符号优先级
  9. 从输入URL到页面渲染完成 -戈多编程
  10. 施密特:下个千亿美元市值公司将出在哪个行业
  11. 《教练型领导力》--司铭宇老师
  12. 计算机新加一个固态硬盘,老电脑卡顿不一定没救了 加一块SSD就能焕发新生
  13. supervisor的使用与管理
  14. 谷歌404页面html,简洁404页面HTML好看的404错误页源码
  15. 处理ios软键盘弹起和收起时页面滚动问题
  16. python 日期大小比较
  17. Maven使用教程和开发经验总结
  18. 区别传统广告联盟,穿山甲的新角色
  19. 2022建筑电工(建筑特殊工种)操作证考试题库及答案
  20. 打印a4 的文档到 a3 双面

热门文章

  1. 简约不简单:高级时钟插件Advanced Clock Widget Pro
  2. 计算机网络华为路由器配置实验,计算机网络 路由器基本命令操作实验报告格式 华为.doc...
  3. python教学视频m_python学习(33)----Python 中 -m 的典型用法、原理解析与发展演变(转)...
  4. 概率统计——三扇门游戏与贝叶斯定理
  5. unity怪物攻击玩家减血_像素地牢开发(unity) 第一篇(并不是第一天)
  6. Vectorworks 2023,3D建筑设计软件
  7. linux 下vi与vim区别以及vim的使用
  8. 维盟无线ap服务器地址,维盟无线AP划分不同SSID与VLAN组网设置教程
  9. Dubbo基本原理与超时机制
  10. MT6739芯片处理器,MT6739套片开发资料汇集下载