• 【Java虚拟机9】类加载器之命名空间详解


    前言

    前面介绍类加载器的时候,介绍了一下命名空间这个概念。今天就通过一个例子,来详细了解一下【类加载器的命名空间】。然后通过这个例子,我们可以总结一下双亲委托模型的好处与优点。

    例1(不删除classpath下的class文件)

    首先定义一个MyPerson

    package com.jamie.jvmstudy;
    
    public class MyPerson {
     
        private MyPerson myPerson;
     
        public void  setMyPerson(Object obj){
            this.myPerson = (MyPerson)obj;
        }
    }
    

    然后是自定义类加载器

    package com.jamie.jvmstudy;
    
    import java.io.*;
    
    public class CustomizedClassLoader extends ClassLoader {
    
        private String classLoaderName;
    
        private String path;
    
        private String fileExtension = ".class";
    
        public CustomizedClassLoader(String classLoaderName) {
            super();
            this.classLoaderName = classLoaderName;
        }
    
        public CustomizedClassLoader(ClassLoader parent, String classLoaderName) {
            super(parent);
            this.classLoaderName = classLoaderName;
        }
    
        @Override
        public Class<?> findClass(String className) throws ClassNotFoundException {
            System.out.println("findClass invoked : " + className);
            System.out.println("class loader name : " + this.classLoaderName);
            byte[] data = this.loadClassData(className);
    
            return this.defineClass(className, data, 0, data.length);
        }
    
        private byte[] loadClassData(String className) {
            byte[] data = null;
            className = className.replace(".", "/");
            try(InputStream is = new FileInputStream(new File(this.path + className + this.fileExtension));
                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                int ch;
                while(-1 != (ch = is.read())) {
                    baos.write(ch);
                }
                data = baos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return data;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    }
    
    

    测试客户端类

    package com.jamie.jvmstudy;
    
    import java.lang.reflect.Method;
    
    public class TestClassLoaderNameSpace {
        public static void main(String[] args) throws Exception {
            CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1");
            CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2");
     
            Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson");
            Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson");
    
            System.out.println("clazz1的classLoader是" + clazz1.getClassLoader());
            System.out.println("clazz2的classLoader是" + clazz2.getClassLoader());
            System.out.println( clazz1 == clazz2);
     
            Object object1 = clazz1.newInstance();
            Object object2 = clazz2.newInstance();
            Method method = clazz1.getMethod("setMyPerson", Object.class);
            method.invoke(object1, object2);
        }
    }
    

    结果:

    clazz1的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
    clazz2的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
    true

    说明:

    同一个类加载器(本例是应用类加载器)加载同一个类,得到的class对象是相同的。

    例2(基于例1修改,删除classpath下的class文件)

    操作

    为自定义类加载器设置path,然后编译成功后,删除掉classpath下面的MyPerson.class文件,把编译出的MyPerson.class文件移动到D:/temp文件夹里面。

    public class TestClassLoaderNameSpace {
        public static void main(String[] args) throws Exception {
            CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1");
            CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2");
    
            loader1.setPath("D:/temp/");
            loader2.setPath("D:/temp/");
    
            Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson");
            Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson");
    
            System.out.println("clazz1的classLoader是" + clazz1.getClassLoader());
            System.out.println("clazz2的classLoader是" + clazz2.getClassLoader());
            System.out.println( clazz1 == clazz2);
     
            Object object1 = clazz1.newInstance();
            Object object2 = clazz2.newInstance();
            Method method = clazz1.getMethod("setMyPerson", Object.class);
            method.invoke(object1, object2);
        }
    }
    

    代码执行结果

    findClass invoked : com.jamie.jvmstudy.MyPerson
    class loader name : loader1
    findClass invoked : com.jamie.jvmstudy.MyPerson
    class loader name : loader2
    clazz1的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@677327b6
    clazz2的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@7f31245a
    false
    Exception in thread "main" java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:497)
    	at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)
    Caused by: java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:497)
    	at com.jamie.jvmstudy.TestClassLoaderNameSpace.main(TestClassLoaderNameSpace.java:23)
    	... 5 more
    Caused by: java.lang.ClassCastException: com.jamie.jvmstudy.MyPerson cannot be cast to com.jamie.jvmstudy.MyPerson
    	at com.jamie.jvmstudy.MyPerson.setMyPerson(MyPerson.java:8)
    	... 10 more
    
    

    结论

    loader1和loader2分别加载了MyPerson.class,分别给MyPerson.class分配了内存空间,如下图:

    这2个class对象虽然在文件系统是来自于同一个class文件,但是由于他们是被自定义类加载器加载的,并且这2个自定义类加载器是同级的,没有父子关系。所以双亲委派模型中,他们是看不到对方的命名空间的。
    所以clazz1 == clazz2的结果为false

    自己画了一个简图:

    下面这张是偶尔在网上看到的:

    不同类加载器的命名空间关系

    咱们先回顾一下命名空间的概念

    • 每个类加载器都有自己的命名空间。命名空间由该加载器和所有父加载器所加载的类组成。(请结合下图一起看,想明白)
    • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
    • 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

    得出命名空间的关系如下:(请结合下图一起看,想明白)

    • 同一个命名空间的类是相互可见的。
    • 子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载器能看见根类加载器加载的类。
    • 由父类加载器加载的类不能看见子加载器加载的类。
    • 如果两个加载器没有父子关系,那么他们自己加载的类互相不可见。

    类的唯一性
    在运行期,一个类的唯一性是由以下2点共同决定

    1. 该类的完全限定名(binary name)。
    2. 用于加载该类的[定义类加载器],即defining class loader。
      上述2点都一样,才代表该类(可以理解为该类的Class对象)是一样的。
      如果同样的名字,不同的类加载器加载,那么这2个类是不一样的。即使.class文件完全一样,.class文件路径一样,这2个类也是不一样的。

    双亲委托模型的好处

    1. 确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被加载到Java虚拟机当中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么可能会在JVM中存在多个版本的java.lang.Object类,而且这些类还是不兼容的、相互不可见的(因为命名空间的原因)。借助双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是互相兼容的。
    2. 确保Java核心类库提供的类不会被自定义的类所替代
    3. 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加他们即可,不同类加载器所加载的类是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。
  • 相关阅读:
    阶乘
    资金账号,手机号等中间添加*(星号),脱敏
    对象深拷贝
    前端简易服务器
    Codeforces Round #603 (Div. 2) C. Everyone is a Winner! (数学)
    Codeforces Round #603 (Div. 2) B. PIN Codes
    Codeforces Round #603 (Div. 2) A. Sweet Problem(数学)
    Codeforces Round #605 (Div. 3) E. Nearest Opposite Parity(最短路)
    Codeforces Round #605 (Div. 3) D. Remove One Element(DP)
    Codeforces Round #605 (Div. 3) C. Yet Another Broken Keyboard
  • 原文地址:https://www.cnblogs.com/1626ace/p/13493265.html
Copyright © 2020-2023  润新知