4.jvm类加载器命名空间实战剖析与透彻理解

类加载器命名空间实战剖析与透彻理解

命名空间

  • 每个类加载器都有自己的命名空间,命名空间是由该加载器及所有的父加载器加载的类组成
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间中,有可能出现类的完整名字(包括类的包名)相同的两个类

关于命名空间的重要说明
1、子加载器所加载的类能够访问到父加载器所加载的类
2、父加载器所加载的类无法访问到子类加载器所加载的类

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

下面是验证这两个重要的说明的。

举例分析

首先说明一下我们下面写的代码的作用是干什么。我们要定义两个类Mysample和Mycat这两个类,我们要把这两个类由不同的类加载器来加载他们,比如我们让AppclassLoader(系统类加载器)加载Mysample,让我们自定一个类加载器MyTest16来加载Mycat,然后在Mysample的构造方法里面实例化Mycat,看输出的结果是什么,然后在Mycat的构造方法里面实例化Mysample看输出的结果是什么。

public class MyTest17_1 {
    public static void main(String[] args)  throws Exception{
        MyTest16 loader1 = new MyTest16("loader1");
        loader1.setPath("E:\\data\\classes\\");
        Class<?> clazz = loader1.loadClass("com.twodragonlake.jvm.classloader.MySample");
        System.out.println("class :"+clazz.hashCode());
        //如果注释掉改行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
        //因此不会实例化MyCat对象,既没有对MyCat进行主动使用,这里就不会加载MyCat class
        Object object = clazz.newInstance();
    }
}
public class MyCat {
    public MyCat(){
        System.out.println("MyCat is loaded by : "+this.getClass().getClassLoader());
        System.out.println("from MyCat : "+MySample.class);//加入打印MySample的一行代码
    }
}
public class MySample {
    public MySample(){
        System.out.println("MySample is loaded by : "+this.getClass().getClassLoader());
        new MyCat();
        System.out.println("form MySample :"+MyCat.class);
    }
}

程序的运行结果

class :1735600054
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2

因为这两个类还在classpath下面,所以系统类加载器会加载。

现在我们把classes目录复制到E:\data目录下,删除classpath下的Mysample.class和MyCat.class这两个class文件,

程序的运行结果:

findClass invoked com.twodragonlake.jvm.classloader.MySample【加载MySample时MyTest16的打印】
 this.classLoaderName : loader1                             【MySample是由MyTest16加载,MyTest16的打印】
class :2133927002                                           【MyTest17_1的打印】
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6   【MySample构造器】
findClass invoked com.twodragonlake.jvm.classloader.MyCat   【MySample的构造器new MyCat的时候要先加载MyCat】
 this.classLoaderName : loader1                              【MyCat是由MyTest16加载的】
MyCat is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6  【MyCat构造器的打印】

两个类都是有MyTest16这个类加载器加载的,这没什么问题,并且在MySample的构造方法里面初始话MyCat也是没问题的。

现在我们再修改一下,重新build工程,只删除classpath下的MyCat.class 此时MySample在当前工程和【E:\data\classes\】下都有一份,而MyCat只在【E:\data\classes\】有一份.

程序的运行结果:

class :1735600054
Exception in thread "main" java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MyCat
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
    at com.twodragonlake.jvm.classloader.MySample.<init>(MySample.java:6)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at com.twodragonlake.jvm.classloader.MyTest17_1.main(MyTest17_1.java:11)
Caused by: java.lang.ClassNotFoundException: com.twodragonlake.jvm.classloader.MyCat
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more

分析:首先Mysample.class会被AppClassLoader加载,当调用 Object object = clazz.newInstance(); 这一行代码的时候,就是要初始化Mysample,执行MySample的构造方法,new MyCat()的时候报java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MyCat。

再修改一下 我们只删除classpath下的MySample.class 此时MyCat在当前工程和【E:\data\classes\】下都有一份,而MySample只在【E:\data\classes\】有一份.

程序的运行结果:

findClass invoked com.twodragonlake.jvm.classloader.MySample
 this.classLoaderName : loader1
class :2133927002
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
form MySample :class com.twodragonlake.jvm.classloader.MyCat

分析:

首先MySample的class不在当前工程下,因此会使用自定义加载器MyTest16加载,MySample的构造器里边出现new MyCat,因此会使用子加载器MyTest16的父加载器应用类加载器加载MyCat,之后【new MyCat()】这行代码是在子类加载器MyTest16里边出现,由于子类加载器可以看到父类加载器加载的类,因此不会抛出异常。

自定义类加载器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;
 
    }
 
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值