JVM 类加载器命名空间深度解析与实例分析

Posted linlf03

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM 类加载器命名空间深度解析与实例分析相关的知识,希望对你有一定的参考价值。

一、创建Sample

1、创建实例

public class MyPerson 

    private MyPerson myPerson;

    public void  setMyPerson(Object obj)
        this.myPerson = (MyPerson)obj;
    

  

2、创建测试类

public class MyTest20 

    public static void main(String[] args) throws Exception 
        MyTest16 loader1 = new MyTest16("loader1");
        MyTest16 loader2 = new MyTest16("loader2");

        Class<?> clazz1 = loader1.loadClass("com.example.jvm.classloader.MyPerson");

        Class<?> clazz2 = loader2.loadClass("com.example.jvm.classloader.MyPerson");

        System.out.println( clazz1 == clazz2);

        Object object1 = clazz1.newInstance();
        Object object2 = clazz2.newInstance();

        Method method = clazz1.getMethod("setMyPerson", Object.class);
        method.invoke(object1, object2);



    

  

3、MyTest16类和之前的一致

技术图片
public class MyTest16  extends  ClassLoader

    private String className;

    //目录
     private String path;

    private final String fileExtension = ".class";

    public MyTest16(String classLoadName)
        super(); //将系统类加载器当做该类加载器的父加载器
        this.className = classLoadName;
    

    public MyTest16(ClassLoader parent, String classLoadName)
        super(parent); //显示指定该类加载器的父加载器器
        this.className = classLoadName;
    

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

    @Override
    public String toString() 
        return "[" + this.className + "]";
    

    @Override
    protected Class<?> findClass(String clasName) throws ClassNotFoundException 
        System.out.println("findClass invoked:" + clasName);
        System.out.println("class loader name: " + this.className);
        byte[] data = this.loadClassData(clasName);
        return  this.defineClass(clasName,data, 0, data.length);
    

    private byte[] loadClassData(String className)
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        try
            className = className.replace(".","//");
            //System.out.println("className:" +this.className);
            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            baos = new ByteArrayOutputStream();
            int ch = 0;
            while ( -1 != (ch = is.read()))
                baos.write(ch);
            
            data = baos.toByteArray();

        catch (Exception ex)
            ex.printStackTrace();
        finally 
            try 
                is.close();
                baos.close();
            catch (Exception ex)
                ex.printStackTrace();
            
        

        return  data;

    

  


View Code

 

打印结果

true

  

二、修改Sample

在一的基础上修改代码,设置path

loader1.setPath("D:/temp/");

loader2.setPath("D:/temp/");

public class MyTest21 

    public static void main(String[] args) throws Exception 
        MyTest16 loader1 = new MyTest16("loader1");
        MyTest16 loader2 = new MyTest16("loader2");

        loader1.setPath("D:/temp/");
        loader2.setPath("D:/temp/");

        Class<?> clazz1 = loader1.loadClass("com.example.jvm.classloader.MyPerson");

        Class<?> clazz2 = loader2.loadClass("com.example.jvm.classloader.MyPerson");

        System.out.println( clazz1 == clazz2);

        Object object1 = clazz1.newInstance();
        Object object2 = clazz2.newInstance();

        Method method = clazz1.getMethod("setMyPerson", Object.class);
        method.invoke(object1, object2);



    

  然后将class文件所在的build下的com文件夹拷贝到D:\\temp 下,删除build下的MyPerson.class  文件

打印结果:

findClass invoked:com.example.jvm.classloader.MyPerson
class loader name: loader1
findClass invoked:com.example.jvm.classloader.MyPerson
class loader name: loader2
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:498)
	at com.example.jvm.classloader.MyTest21.main(MyTest21.java:27)
Caused by: java.lang.ClassCastException: com.example.jvm.classloader.MyPerson cannot be cast to com.example.jvm.classloader.MyPerson
	at com.example.jvm.classloader.MyPerson.setMyPerson(MyPerson.java:11)
	... 5 more

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

技术图片

 

loader1和loader2是两个不同的命名空间。 

所以System.out.println( clazz1 == clazz2);的结果为false

 这里可以回顾下命名空间的概念

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

在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。

在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

 

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

同一个命名空间内的类是相互可见的

子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。

由父加载器加载的类不能看见子加载器加载的类。

如果两个类加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。

 

所以上面抛出异常的原因为:如果两个类加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见

以上是关于JVM 类加载器命名空间深度解析与实例分析的主要内容,如果未能解决你的问题,请参考以下文章

JVM进阶之类加载器详解

JVM:Java类加载原理深度解析

JVM:Java类加载原理深度解析

Java中类加载器的分析与理解!详细解析类的加载过程

JVM虚拟机 类加载过程与类加载器

JVM虚拟机 类加载过程与类加载器