【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载

类加载器的命名空间

  • 每个类加载器又有一个命名空间,由其以及其父加载器组成

类加载器的命名空间的作用和影响

  • 每个类加载器又有一个命名空间,由其以及其父加载器组成
  • 在每个类加载器自己的命名空间中不能出现相同类名的类 (此处值得是类的全名,包含包名)
  • 在不同的类命名空间中,可能会出现多个相同的类名的类

如下面的代码示例中, 首先定义一个类加载器 MyClassLoader

  static class MyClassLoader extends ClassLoader {

    private String classLoaderName;

    private String classPath;

    public MyClassLoader(String classPath, String classLoaderName) {
      super(); // 未指定则默认使用应用类加载器
      this.classLoaderName = classLoaderName;
      this.classPath = classPath;
    }

    public MyClassLoader(ClassLoader parent, String classLoaderName) {
      super(parent); // 显式的指定父类加载器
      this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) {
      System.out.println("MyClassLoader.findClass");
      byte[] bytes = null;
      try {
        bytes = loadClassByte(name);
        return defineClass(name, bytes, 0, bytes.length);
      } catch (Exception e) {

        throw new RuntimeException(e);
      }
    }

    private byte[] loadClassByte(String name) throws Exception {
      name = name.replace(".", "/");
      File file = new File(this.classPath + name + ".class");

      try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
          InputStream fileStream = new FileInputStream(file)) {

        int ch;
        while ((ch = fileStream.read()) != -1) {
          byteStream.write(ch);
        }
        return byteStream.toByteArray();
      }
    }
  }

下面我们尝试使用此类加载加载一个磁盘的class文件,代码如下:

  public static void main(String[] args) throws ClassNotFoundException {
    MyClassLoader classLoader = new MyClassLoader("/tmp/", "CustomClassLoader");
    Class<?> aClass = classLoader.loadClass("com.zhoutao.classload.ReferenceExample004");
    ClassLoader loader = aClass.getClassLoader();
    System.out.println(loader);

    System.out.println("----------------------");

    MyClassLoader classLoader2 = new MyClassLoader("/tmp/", "CustomClassLoader");
    Class<?> aClass2 = classLoader2.loadClass("com.zhoutao.classload.ReferenceExample004");
    ClassLoader loader2 = aClass2.getClassLoader();
    System.out.println(loader);
  }

按照之前的理论,由于 ReferenceExample004 类在classLoader 中已经被加载,那么在 classLoader2 中将不会被加载,我们看一下输出的结果

MyClassLoader.findClass
[email protected]
----------------------
MyClassLoader.findClass
[email protected]

总结

可以看到,类加载器的findClass(String) 方法被执行了两次,这是因为加载该类的类加载器是两个不同的对象,在文章的开头提及:每个类加载器又有一个命名空间,由其以及其父加载器组成 上面的两个类加载器显然命名空间不一致,所以findClass() 方法会被执行两次

类的卸载

在内存中的 Class 类没有引用的时候就会被 JVM 卸载,因为 JVM 自带的类加载 启动类加载器,拓展类加载器以及应用类加载器,在三个类加载的实例一直被 JVM 引用,所以JVM 自带的类加载器加载的类一直被其加载器引用,所以不会被卸载
但是自定义的类加载器的实例可以被手动的设置为null,这导致类加载不再被引用,其所加载的 class 类在没有实例其没有类加载器引用的情况下就会被卸载,卸载的时间发生在系统垃圾回收的时候

类的卸载测试

同类的加载一样,要观察到类的卸载,可以通过添加 JVM 参数-XX:+TraceClassUnloading 的方式输入类的卸载信息。

  public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
    MyClassLoader classLoader = new MyClassLoader("/tmp/", "CustomClassLoader");
    Class<?> aClass = classLoader.loadClass("com.zhoutao.classload.ReferenceExample004");

    classLoader = null;
    aClass = null;

    // 手动触发GC 观察类回收的信息

    System.gc();

    TimeUnit.SECONDS.sleep(10);
  }

通过控制台可以观察到类的卸载记录:

MyClassLoader.findClass
[Unloading class com.zhoutao.classload.ReferenceExample004 0x00000007c0061028]

或者通过JVisual 工具观察到类的卸载过程, 为了便于观察,适当加长 延时的时间 TimeUnit.SECONDS.sleep(100);, 终端或者CMD终端 输入 jvisualvm 命令启动,启动测试程序,在 jvisualvm 工具中接连到该线程,可以看到下面的信息:

已卸载的总数为: 1

欢迎关注微信公众号获取更多开发技巧干货

原文地址:https://www.cnblogs.com/zhoutao825638/p/12394088.html

时间: 2024-11-08 18:39:46

【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载的相关文章

深入理解java虚拟机-----&gt;垃圾收集器与内存分配策略(下)

1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾收集器与内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题: 给对象分配内存; 回收分配给对象的内存. 对象的内存分配,往大方向上讲就是在堆上的分配,对象主要分配在新生代的Eden区上.少数也可能分配在老年代,取决于哪一种垃圾收集器组合,还有虚拟机中的相关内存的参

深入java虚拟机-类加载器

此系列为深入java虚拟机(周志明著)学习笔记 通过一个类的全限定名来获取描述此类的二进制字节流的代码模块称为类加载器. 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性.通俗的理解:比较两个类是否"相等",只有在这两个类是同一类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载她们的类加载器不同,那这两个类必定不相等.这里的"相等",包括类的Class对象的equals()

深入理解JAVA虚拟机 垃圾收集器和内存分配策略

引用计数算法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用的. 客观地说,引用计数算法(Reference Counting)的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软的COM(Component Object Model)技术.使用ActionScript 3的FlashPlayer.Python语

深入理解java虚拟机第二版(六)类文件结构

一.class类文件的结构 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在. 根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表. 无符号数属于基本的数据类型,以u1.u2.u4.u8来分别代表1个字节.2个字节.4个字节和8个字节的无符号数,无符号

[深入理解Java虚拟机]&lt;垃圾收集器与内存分配策略&gt;

Overview 垃圾收集考虑三件事: 哪些内存需要回收? 什么时候回收? 如何回收? 重点考虑Java堆中动态分配和回收的内存. Is Object alive? 引用计数法 给对象添加一个引用计数器. 该方法实现简单,判定效率高.但是它很难解决对象之间相互循环引用的问题,因此几乎很少有JVM选用该方法.eg: public class ReferenceCountingGC { public Object instance = null; // 占点内存,以便在GC日志中看清楚是否被回收过

(转)《深入理解java虚拟机》学习笔记6——类加载机制

Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需要完成以下3件事: a.通过一个类的全限定名来获取定义此类的二进制字节流. b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构. c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口. Java虚拟机的类加载是通过类加载器实现的, Java中

JAVA 不同类加载器命名空间的理解

            以前一直有这样一个疑惑: 都说在JAVA中,由不同类加载器加载的类在虚拟机中位于不同的命名空间下,不同命名空间下的类相互不可见. 这让我产生了一个迷惑:如果有一个类A使用了java.util.List类,为什么在运行时会没有错误.因为按照类加载的双亲委派机制,自己写的类A一般由系统类加载器加载,而java.util.List肯定是由启动类加载器(也叫Root类加载器)加载的,所以这两个类应该不在一个命名空间下.那在运行时为什么类A还 是能访问到java.util.List

Java内存管理-掌握虚拟机类加载器(五)

勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇介绍虚拟机类加载机制,讲解了类加载机制中的三个阶段,分别是:加载.连接(验证.准备.解析).初始化 ,知道了类加载的机制.下面我们就要知道类到底是通过什么方式加载到内存中的,也就是本文要介绍的类加载器,类加载器就是加载类的信息到内存中. 本文地图 : 一.什么是类加载器(ClassLoader) 虚拟机设计团队把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流“

《深入理解Java虚拟机》:类加载的过程

<深入理解Java虚拟机>:类加载的过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段. 下面详细讲述类加载过程中每个阶段所做的工作. 加载 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一