命名空间与类的卸载

命名空间介绍

 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成。也就是说,命名空间就是一个空间,空间里就是可以所有可以加载到的类。
 命名空间的性质如下:
  ①在同一个命名空间中,不会出现类的二进制名字相同的两个类。
  ②在不同的命名空间中,有可能会出现类的二进制名字相同的两个类。
 实例如下:
  验证性质一

public class MyTest extends ClassLoader {
    private String classLoaderName;
    private String fileExtension = ".class";
    private String path;

    public MyTest(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public MyTest(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass invoke: "+name);
        System.out.println("class loader name: "+classLoaderName);
        byte[] classData = getClassData(name);
        return defineClass(null, classData, 0, classData.length);
    }

    public byte[] getClassData(String className) {
        byte[] bytes = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;

        className=className.replaceAll("\\.","\\\\");
        try {

            File file=new File(this.path,className+fileExtension);

            is = new FileInputStream(file);
            bos = new ByteArrayOutputStream();

            int ch = 0;
            while ((ch = is.read()) != -1) {
                bos.write(ch);
            }

            bytes = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyTest myTest=new MyTest("MyLoader");
        myTest.setPath("桌面路径");
        //使用加载器myTest加载的类
        Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
        Object instance = loadClass.newInstance();
        System.out.println(loadClass.hashCode);
        System.out.println(instance);
        
        System.out.println("------");
        
        //将myTest1加载器的父加载器设置为myTest
        MyTest myTest1=new MyTest(myTest,"MyLoader1");
        myTest1.setPath("桌面路径");
        //使用加载器myTest1加载的类
        Class<?> loadClass1 = myTest1.loadClass("com.minato.jvm.chapter15.People");
        Object instance1 = loadClass.newInstance();
        System.out.println(loadClass1.hashCode);
        System.out.println(instance1);
    }
}

打印结果
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader //调用了myTest的findClass方法
21685669 //myTest加载器加载出的类对象的hashCode
com.minato.jvm.chapter15.People@7f31245a
------
21685669 //myTest1加载器加载出的类对象的hashCode
com.minato.jvm.chapter15.People@6d6f6e28

两个hashCode值相同,并且findClass方法只执行了一次,证明了加载流程执行了一次。

类加载器的执行顺序
    调用findLoadedClass方法检查欲加载的类是否已经加载
    调用父加载器的loadClass,如果父加载器为空,那么就会用系统类加载加载
    调用findClass去find类

按照双亲委托模型,加载器myTest1当执行findLoadedClass时,会返回true,
后续的顺序并不会执行,因此执行流程只执行一次,返回的值是已经加载的Class,
因此两次hashCode值相同。

  验证性质二

public class MyTest extends ClassLoader {
    private String classLoaderName;
    private String fileExtension = ".class";
    private String path;

    public MyTest(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public MyTest(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass invoke: "+name);
        System.out.println("class loader name: "+classLoaderName);
        byte[] classData = getClassData(name);
        return defineClass(null, classData, 0, classData.length);
    }

    public byte[] getClassData(String className) {
        byte[] bytes = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;

        className=className.replaceAll("\\.","\\\\");
        try {

            File file=new File(this.path,className+fileExtension);

            is = new FileInputStream(file);
            bos = new ByteArrayOutputStream();

            int ch = 0;
            while ((ch = is.read()) != -1) {
                bos.write(ch);
            }

            bytes = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        MyTest myTest=new MyTest("MyLoader");
        myTest.setPath("桌面路径");
        //使用加载器myTest加载的类
        Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
        Object instance = loadClass.newInstance();
        System.out.println(loadClass.hashCode);
        System.out.println(instance);
        
        System.out.println("------");
        
        MyTest myTest1=new MyTest("MyLoader1");
        myTest1.setPath("桌面路径");
        //使用加载器myTest1加载的类
        Class<?> loadClass1 = myTest1.loadClass("com.minato.jvm.chapter15.People");
        Object instance1 = loadClass.newInstance();
        System.out.println(loadClass1.hashCode);
        System.out.println(instance1);
    }
}

打印结果
findClass invoke: com.minato.jvm.chapter15.People 
class loader name: MyLoader // 调用了加载器myTest的findClass方法
21685669 //加载出的类的hashCode
com.minato.jvm.chapter15.People@7f31245a //创建出的实例
------
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader1 // 调用了加载器myTest1的findClass方法
1173230247 //加载出的类对象的hashCode
com.minato.jvm.chapter15.People@330bedb4

以上的加载器myTest和myTest1是两个不同的类加载器,也就是说有两个不用命名空间,
因此完整的走了两次加载流程,hashCode的值不同

类的卸载

 由JVM自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。JVM自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。JVM本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类Class对象,因此这些Class对象始终是可触及的。

    以上的关键是 JVM本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类Class对象

 由用户自定义的类加载器所加载的类是可以被卸载的,因为JVM不会始终保持自定义类加载器的引用。
 实例
  配置JVM参数 -XX:+TraceClassUnloading 追踪类的卸载

    方法是 将引用断开
    public class MyTest extends ClassLoader {
    private String classLoaderName;
    private String fileExtension = ".class";
    private String path;

    public MyTest(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public MyTest(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass invoke: " + name);
        System.out.println("class loader name: " + classLoaderName);
        byte[] classData = getClassData(name);
        return defineClass(null, classData, 0, classData.length);
    }

    public byte[] getClassData(String className) {
        byte[] bytes = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;

        className = className.replaceAll("\\.", "\\\\");
        try {

            File file = new File(this.path, className + fileExtension);

            is = new FileInputStream(file);
            bos = new ByteArrayOutputStream();

            int ch = 0;
            while ((ch = is.read()) != -1) {
                bos.write(ch);
            }

            bytes = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

    public static void test(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> loadClass = classLoader.loadClass("com.minato.jvm.chapter15.People");
        Object instance = loadClass.newInstance();
        System.out.println(instance.getClass().getClassLoader());
        System.out.println(instance);
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        MyTest myTest = new MyTest("MyLoader");
        myTest.setPath("桌面路径");
        //使用加载器myTest加载的类
        Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
        Object instance = loadClass.newInstance();
        System.out.println("------");

        //引用断开
        myTest = null;
        loadClass = null;
        instance = null;
        //回收  生命周期终止
        System.gc();

        myTest = new MyTest("MyLoader");
        myTest.setPath("桌面路径");
        //使用加载器myTest加载的类
        loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
        instance = loadClass.newInstance();
        System.out.println("------");
    }
}

结果如下:
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader
------
[Unloading class com.minato.jvm.chapter15.People //类被卸载 0x0000000100061828]
findClass invoke: com.minato.jvm.chapter15.People //累被重新加载
class loader name: MyLoader
------
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容