十六、类加载器实战剖析与疑难点解析

1、jvm自带各级加载器的加载路径
public class MyTest19 {
    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("sun.boot.class.path"));
        System.out.println("=======");
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println("=======");
        System.out.println(System.getProperty("java.class.path"));
    }
}
C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\classes
=======
C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
=======
C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;G:\JVM-lecture\target\classes;L:\idea\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar
2、测试根类加载器是否可以加载自定义类

将生成的类的class文件放入到根类加载器的其中一个加载目录:C:\Program Files\Java\jdk1.8.0_201\jre\classes,然后查看是由哪个加载器加载的。
C:\Program Files\Java\jdk1.8.0_201\jre目录下新建classes目录,在classes目录下新建com/jvm/classloader目录,然后将user.class放入该目录下。

package com.jvm.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyTest17 extends ClassLoader {

    private String classLoaderName;

    private String path; // class文件路径

    private final String fileExtension = ".class";

    public MyTest17(String classLoaderName) {
        super(); // 将系统类加载器作为该类加载器的父类加载器
        this.classLoaderName = classLoaderName;
    }

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

    public MyTest17(ClassLoader parent, String classLoaderName) {
        super(parent); // 显式指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }


    @Override
    public String toString() {
        return "MyTest17{" +
                "classLoaderName='" + classLoaderName + '\'' +
                '}';
    }


    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass:" + className);
        System.out.println("class loader name:" + this.classLoaderName);
        // 父类中findClass方法返回的是一个异常,所以必须要重写
        byte[] data = this.loadClassData(className);
        return this.defineClass(className, data, 0, data.length);
    }

    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        className = className.replace(".", "/");
        try {
            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            System.out.println(this.path + className + this.fileExtension);
            baos = new ByteArrayOutputStream();
            int ch;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}
package com.jvm.classloader;

public class User {
    public static int age = 13;
}
public class MyTest19 {
    public static void main(String[] args) throws Exception {
        MyTest17 loader1 = new MyTest17("loader1");
        loader1.setPath("C:/Users/lifeline张/Desktop/");
        Class<?> aClass = loader1.loadClass("com.jvm.classloader.User");
        System.out.println("aclass: " + aClass.hashCode());
        System.out.println("class loader:" + aClass.getClassLoader());
    }
}

运行结果:

aclass: 1163157884
class loader:null

可以看到,确实是由根加载器进行加载的。

3、判断类是否相等
public class MyPerson {
    private MyPerson myPerson;

    public void setMyPerson(Object ob) {
        this.myPerson = (MyPerson) ob;
    }
}
public class MyTest20 {
    public static void main(String[] args) throws Exception {
        MyTest17 loader1 = new MyTest17("loader1");
        MyTest17 loader2 = new MyTest17("loader2");
        Class<?> aClass1 = loader1.loadClass("com.jvm.classloader.MyPerson");
        Class<?> aClass2 = loader2.loadClass("com.jvm.classloader.MyPerson");
        System.out.println(aClass1 == aClass2);
    }
}

因为两者都会委托给app进行加载,所以是同一个对象。

4、引例
public class MyTest20 {
    public static void main(String[] args) throws Exception {
        MyTest17 loader1 = new MyTest17("loader1");
        MyTest17 loader2 = new MyTest17("loader2");
        Class<?> aClass1 = loader1.loadClass("com.jvm.classloader.MyPerson");
        Class<?> aClass2 = loader2.loadClass("com.jvm.classloader.MyPerson");
        System.out.println(aClass1 == aClass2);
        Object o1 = aClass1.newInstance();
        Object o2 = aClass2.newInstance();
        // 通过反射调用setMyPerson方法
        Method method = aClass1.getMethod("setMyPerson", Object.class);
        // 给O1对象传入O2的实例
        method.invoke(o1, o2);
    }
}

输出结果为ture,并且不会报错。

修改:将MyPerson的class文件放到桌面上:C:\Users\lifeline张\Desktop\com\jvm\classloader,删除工程中的MyPerson的class文件,运行结果如下:

findClass:com.jvm.classloader.MyPerson
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MyPerson.class
findClass:com.jvm.classloader.MyPerson
class loader name:loader2
C:/Users/lifeline张/Desktop/com/jvm/classloader/MyPerson.class
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.jvm.classloader.MyTest21.main(MyTest21.java:19)
Caused by: java.lang.ClassCastException: com.jvm.classloader.MyPerson cannot be cast to com.jvm.classloader.MyPerson
	at com.jvm.classloader.MyPerson.setMyPerson(MyPerson.java:7)
	... 5 more

原因:两个对象都会自己加载MyPerson,但是分别是两个不同的命名空间,所以反射得到的两个类不能相互访问。

总结:
同一个命名空间内的类是互相可见的。
子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。
由父加载器加载的类不能看见子加载器加载的类。
如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类相互不可见。

这个异常很有意思:Caused by: java.lang.ClassCastException: com.jvm.classloader.MyPerson cannot be cast to com.jvm.classloader.MyPerson表面上看竟然是两个类不能相互转换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值