A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大山哥哥 于 2019-10-26 20:13 编辑

jdk9前后的类加载器功能对比
JVM解释的程序类需要经过类加载器进行加载后(通过IO流读入)才可以执行,JVM提供了3种类加载器如下图所示
BootStrap(系统类加载器):由C++语言编写的类加载器,是在Java虚拟机启动后进行初始化。
在jdk9模块化之前,主要是作用的读取核心类库里面的类。如下目录
在jdk9模块化之后,用于加载启动的基础模块类。运行时内存模型如下截图,如下目录,
在jdk9模块化之前,是ExtClassLoader(拓展类加载器),并不存在PlatformClassLoader,主要加载的是拓展类,因为在JDK的安装目录里面提供有一个ext目录,开发者可以将*.jar文件拷贝到此目录里面,这样就可以直接执行了,但是这样的处理并不安全.最初的时候也是不提倡使用的。如下图
在jdk9模块化之后,主要的作用也是读取核心类库里面的模块/lib/modules。模块下有很多的包,PlatformClassLoader和BootStrap加载的 包是不一样的。他用于加载一些平台相关的模块,父类是BootStrap。运行时内存模型如下截图:
APPClassLoader(应用程序类加载器):主要加载CLASSPATH 下的类(即自定义的类)。
在jdk9模块化之前,主要的作用是读取大家自己编写的程序,自己设置的classpath目录下的类。

在jdk9模块化之后,主要的作用也是读取读取大家自己编写的程序,自己设置的classpath目录下的类。另外也负责用于加载应用级别的模块,父类是PlatformClassLoader,他上面两个加载器加载的包不一样,也就是他们三个各有分工。运行时内存模型如下截图:
为了验证不同的加载器加载不同的类,我们在程序中测试一下。
很明显 java.lang包下的类 是跟类加载器加载的, 而javax.xml.crypto包下的类是平台类加载器加载的,而jdk.javadoc包下的类是由应用类型加载器加载的。而这些包都是jdk核心类库的包,另外应用类加载器还负责加载自定义的类,也就是classpath目录下的类。程序的结果还显示了他们的子父关系,根类加载器之所以打印null,是因为他并不是由java语言编写的,所以在java程序中只能以null形式返回。

双亲委派机制
        介绍到这里相信大家心里有个疑问,程序定义类的目的是在jvm中使用它,那么为什么要划分出3中类加载器,如果直接设计为一个类加载器不是更加方便吗?其实这是为了系统安全,而使用的双亲委派机制,双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?              那么采用这种模式有啥用呢? 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常
[Java] 纯文本查看 复制代码
java.lang.SecurityException: Prohibited package name: java.lang

自定义类加载器
Java最大的特点在于可以方便的提供类加载器的支持,这样使得程序的开发拥有极大的灵活性,除了系统提供的内置类加载器外,也可以利用继承classLoader的方法实现自定义类加载器的定义,
[Java] 纯文本查看 复制代码
/*
定义一个要加载的程序类Demo
将生成的Demo.class 文件保存到D盘(路径D:\Demo.class)
*/
public class Demo {
       publicvoid show() {

              System.out.println("showis running");

       }
}

[Java] 纯文本查看 复制代码
/**
* @author zhangjingxian
* 继承classLoader的方法实现自定义类加载器的定义
*/
public class MyClassLoader extends ClassLoader{

    /**
     *
     * @param fis  用来读取class文件的输入流
     * @param className   类的全路径名  比如 com.itheima.domain.Student
     * @return
     * @throws Exception
     */
    public Class<?>loadAndGetMyClass(InputStream fis, String className) throws Exception {
        // 新建一个 内存输出流
        ByteArrayOutputStreambos = new ByteArrayOutputStream();
        // 把读到的 class文件写到内存中
        fis.transferTo(bos);
        // 把class文件里的每个字节放到内存数组 返回。
        byte[] bytes =bos.toByteArray();

        if (bytes!=null){
            /*
             由于需要将加载的二进制数据文件转为Class类的处理,所以可以使用ClassLoader提供的defineClass()方法实现转换。
              */
            return super.defineClass(className,bytes,0,bytes.length);
        }
        return null;
    }
}

[Java] 纯文本查看 复制代码
public class MyClassLoaderTest {
    public static void main(String[] args) throws Exception {
        //创建自己定义的类加载器
        MyClassLoader mcl = new MyClassLoader();
        // 创建一个 从Demo.class读取的输入流
        FileInputStream fis = new FileInputStream("d:/Demo.class");
        //读取class文件并 返回该jvm能识别的class对象
        Class<?> demo = mcl.loadAndGetMyClass(fis, "Demo");
        //创建该类对象
        Object o = demo.getDeclaredConstructor().newInstance();
        //获取该类的show方法
        Method show = demo.getDeclaredMethod("show");
        //show方法执行
        show.invoke(o);
    }
}
自定义类加载器的应用
自定义类加载器可以远程访问其他服务器的class文件,从而执行远程的类,下面我们使用tcp协议来模拟一下 如何使用远程的class类。
[Java] 纯文本查看 复制代码
/*
定义一个要加载的程序类Demo
将生成的Demo.class 文件保存到D盘(路径D:\Demo.class)
*/
public class Demo {
publicvoid show() {

System.out.println("showis running");

}
}

[Java] 纯文本查看 复制代码
/**
* @author zhangjingxian
* 继承classLoader的方法实现自定义类加载器的定义
*/
public class MyClassLoader extends ClassLoader{

/**
*
* @param fis 用来读取class文件的输入流
* @param className 类的全路径名 比如 com.itheima.domain.Student
* @return
* @throws Exception
*/
public Class<?>loadAndGetMyClass(InputStream fis, String className) throws Exception {
// 新建一个 内存输出流
ByteArrayOutputStreambos = new ByteArrayOutputStream();
// 把读到的 class文件写到内存中
fis.transferTo(bos);
// 把class文件里的每个字节放到内存数组 返回。
byte[] bytes =bos.toByteArray();

if (bytes!=null){
/*
由于需要将加载的二进制数据文件转为Class类的处理,所以可以使用ClassLoader提供的defineClass()方法实现转换。
*/
return super.defineClass(className,bytes,0,bytes.length);
}
return null;
}
}

[Java] 纯文本查看 复制代码
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建Socket对象 用于访问 本机 上的8888端口服务端
        Socket s = new Socket("127.0.0.1",8888);
        // 获取输出流
        OutputStream outputStream = s.getOutputStream();
        //创建读取Demo.class的输入流
        FileInputStream fis = new FileInputStream("d:/Demo.class");
        //把Demo.class的字节 通过输出流 发给服务端
        fis.transferTo(outputStream);
        //关闭资源
        fis.close();
        s.close();
    }
}

[Java] 纯文本查看 复制代码
public class ServerDemo {
    public static void main(String[] args) throws Exception {
        // 在本机上创建 服务端,绑定端口8888
        ServerSocket ss = new ServerSocket(8888);
        //接收 客户端的请求
        Socket s = ss.accept();
        //创建类加载器
        MyClassLoader mcl = new MyClassLoader();
        //把从客户端读入的字节 通过自定义类加载器的处理 返回一个 jvm识别的Class对象
        Class<?> demo = mcl.loadAndGetMyClass(s.getInputStream(), "Demo");
        //创建该类对象
        Object o = demo.getDeclaredConstructor().newInstance();
        //获取该类的show方法
        Method show = demo.getDeclaredMethod("show");
        //show方法执行
        show.invoke(o);
        //关闭资源
        s.close();
        // 关闭服务器
        ss.close();


    }
}



更多图片 小图 大图
组图打开中,请稍候......

2 个回复

倒序浏览
加油吧!
回复 使用道具 举报
辛苦分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马