zoukankan      html  css  js  c++  java
  • 【深入理解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
    com.zhoutao.classload.ReferenceExample010$MyClassLoader@610455d6
    ----------------------
    MyClassLoader.findClass
    com.zhoutao.classload.ReferenceExample010$MyClassLoader@60e53b93
    

    总结

    可以看到,类加载器的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

    file

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

    关注我的微信公众号

  • 相关阅读:
    单进程架构数据库谨防隐形杀手
    21.2 超时与重传的简单例子
    19日下午三点直播:DevOps体系中数据库端的四大问题及解决之道
    SQL无所不能:DBA宝妈宝爸系列分享
    用Excel做了7天报表,这个领导喜欢的可视化工具,只用了7小时
    从块结构谈表的存储参数与性能之间的关系
    MYSQL SHELL 到底是个什么局 剑指 “大芒果”
    大数据构架师经典学习宝典
    POJ 3171 区间最小花费覆盖 (DP+线段树
    POJ 3171 区间最小花费覆盖 (DP+线段树
  • 原文地址:https://www.cnblogs.com/zhoutao825638/p/12394088.html
Copyright © 2011-2022 走看看