work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、自定义类加载器在复杂类情况下的运行分析

1、使用之前创建的类加载器

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;

    }

   


}

  

2、创建MyCat类

public class MyCat {
    public MyCat(){
        System.out.println("MyCat is loaded by:" + this.getClass().getClassLoader());
    }
}

  

3、创建MySample类

public class MySample {

    public MySample(){
        System.out.println("MySample is loaded by:" + this.getClass().getClassLoader());
        new MyCat();

    }
}

  

4、创建测试类

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

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

        System.out.println("class:" + clazz.hashCode());

        //如果注释掉改行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
        //因此不会实例化MyCat对象,即没有对MyCat进行主动使用,这里就不会加重MyCat Class
        Object object = clazz.newInstance();



    }
}

  

打印结果

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

  

增加-XX:+TraceClassLoading后的打印结果

 

 

如果去掉Object object = clazz.newInstance();

打印结果为

说明:如果注释掉Object object = clazz.newInstance();该行,那么并不会实例化MySample对象,即MySample构造方法不会被调用

         因此不会实例化MyCat对象,即没有对MyCat进行主动使用,这里就不会加载MyCat Class。

         而且这个例子说明MyCat没有被预先加载

 

 

二、对一的Code进行改造

在一的基础上,新建一个测试类

public class MyTest17_1 {
    public static void main(String[] args)  throws  Exception{
        MyTest16 loader1 = new MyTest16("loader1");
        loader1.setPath("D:/temp/");
        Class<?> clazz = loader1.loadClass("com.example.jvm.classloader.MySample");

        System.out.println("class:" + clazz.hashCode());

        //如果注释掉该行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
        //因此不会实例化MyCat对象,即没有对MyCat进行主动使用,这里就不会加载MyCat Class
        Object object = clazz.newInstance();



    }
}

  里面增加了一个方法loader1.setPath("D:/temp/");

 

然后将MyCat.class 和MySample.class 剪切到D:/temp/目录下,如下面两图

 

 

打印结果:

findClass invoked:com.example.jvm.classloader.MySample
class loader name: loader1
class:2133927002
MySample is loaded by:[loader1]
findClass invoked:com.example.jvm.classloader.MyCat
class loader name: loader1
MyCat is loaded by:[loader1]

  

三、类加载命名空间

在二的基础上,MyCat.java 增加一行代码System.out.println("from MyCat:" + MySample.class);

public class MyCat {
    public MyCat(){
        System.out.println("MyCat is loaded by:" + this.getClass().getClassLoader());

        System.out.println("from MyCat:" + MySample.class);
    }
}

  然后重新build,打印结果如下:

class:1735600054
MySample is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
from MyCat:class com.example.jvm.classloader.MySample

  

现在将build路径下的classloader文件夹拷贝到D:\temp\com\example\jvm\classloader下,删除build路径下的MySample.class

打印输出结果:

findClass invoked:com.example.jvm.classloader.MySample
class loader name: loader1
class:2133927002
MySample is loaded by:[loader1]
MyCat is loaded by:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/jvm/classloader/MySample
	at com.example.jvm.classloader.MyCat.<init>(MyCat.java:10)
	at com.example.jvm.classloader.MySample.<init>(MySample.java:10)
	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.example.jvm.classloader.MyTest17_1.main(MyTest17_1.java:16)
Caused by: java.lang.ClassNotFoundException: com.example.jvm.classloader.MySample
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 8 more

  说明:MySample由自定义loader加载,MyCat由AppClassLoader。两个类是不同的loader加载,处于不同的命名空间。

     所以System.out.println("from MyCat:" + MySample.class); 抛出异常。 子加载器所加载的类能够访问父加载器所加载的类。反之,父加载器所加载的类无法访问子加载器所加载的类