JVM基础学习——双亲委派机制

15 篇文章 0 订阅
2 篇文章 0 订阅

一、类加载器介绍

在这里插入图片描述

1)引导类加载器(BootstrapClassLoader)

也叫启动类或根类加载器,它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

2)扩展类加载器(ExtensionsClassLoader)

它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

3)系统类加载器(SystemClassLoader)

被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

二、机制说明

在这里插入图片描述

1、加载的机制

通过上面的图我们来理解一下这个加载的机制,图中绿色代表加载请求,红色代码返回结果;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

2、机制的优势

双亲机制给JVM的加载带来了层级关系,这种层级关系避免了类的重复加载,同时java有许多核心库,这就避免了外围代码对核心API的渗透和篡改,保障了java本身架构的安全。

3、案例说明

这里自定义加载类,重新改写java.lang.String,然后看它们的加载器是否一样

自定义加载器的方法可以查看这篇博客,这里就不去重复定义了。https://blog.csdn.net/X_ABU/article/details/107412051

自定义加载器

package com.main.jvmtexst.classloadertest.test2;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * 自定义加载器
 */
public class CustomizeClassLoader extends ClassLoader {
    //目录地址
    private String path;
    //类全限定名称地址
    private String classPath;

    //默认使用系统加载器加载
    public CustomizeClassLoader(String path) {
        this.path = path;
    }

    //选择自己的父类加载器,如果使用引导类加载器则使用null代替
    public CustomizeClassLoader(ClassLoader parent, String path) {
        super(parent);
        this.path = path;
    }

    private String getClassPath() {
        //处理全限定名,转换成地址形式
        List<String> nameList = Arrays.asList(classPath.split("\\."));
        StringBuilder nameBuilder = new StringBuilder();
        nameList.forEach(item -> nameBuilder.append(item).append(File.separator));
        classPath = nameBuilder.toString();
        classPath = classPath.substring(0, classPath.length() - 1);

        //返回全地址
        return path + File.separator + classPath + ".class";
    }

    /**
     * 重写findClass方法,通过landClass访问
     *
     * @param name 类的全限定名称
     * @return 返回Class对象
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        this.classPath = name;
        byte[] classData = null;

        //读取字节码文件流
        try (FileInputStream fileInputStream = new FileInputStream(this.getClassPath());
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();) {

            int ch = 0;
            while ((ch = fileInputStream.read()) != -1) {
                byteArrayOutputStream.write(ch);
            }
            classData = byteArrayOutputStream.toByteArray();

        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }

        /**
         * 参数一是类的全限定名称
         * 参数二是字节码
         * 参数三是读取字节码的开始偏移量
         * 参数四是读取字节码的结束偏移量
         * 这里表示读取全部字节码
         */
        return defineClass(name, classData, 0, classData.length);
    }
}

重写的java.lang.String

package java.lang;

public class String {
    public String toString() {
        return "新定义的String";
    }
}

编译指令:

javac -d .\class -encoding UTF-8  .\java\lang\String.java

测试方法:

package com.main.jvmtexst.classloadertest.test2;

public class ParentalMechanismTest {
    public static void main(String[] args) throws Exception {
        //设置读取目录
        String path = "D:\\selfProjects\\Test\\JAVABASIC\\class\\";
        //创建自定义加载器
        //使用默认加载器
        ClassLoader classLoader1 = new CustomizeClassLoader(path);
        //自定义加载器
        ClassLoader classLoader2 = new CustomizeClassLoader(classLoader1, path);

        System.out.println("classLoader1的加载器 : " + classLoader1.getParent());
        System.out.println("classLoader2的加载器 : " + classLoader2.getParent());

        //测试加载指定目录下的类文件
        //加载并获取指定类的Class对象
        Class loader = classLoader2.loadClass("java.lang.String");

        System.out.println("核心库String的加载器 : " + String.class.getClassLoader());
        //通过反射创建实例对象并获取他的Class加载器
        System.out.println("自定义String的加载器 : " + loader.newInstance().getClass().getClassLoader());

    }
}

运行结果:

classLoader1的加载器 : sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader2的加载器 : com.main.jvmtexst.classloadertest.test2.CustomizeClassLoader@1622f1b
核心库String的加载器 : null
核心库String的Class : 1490180672
自定义String的加载器 : null
自定义String的Class : 1490180672

通过运行结果可以知道两种方式获取的String的加载器都是引导类加载器,同时他们的Class对象是同一个,也就说自定义的String没有加载,原因就是双亲机制,核心库的String先被加载,然后加载自定义的,但是双亲机制一直到引导类加载器,发现已经被加载过了,于是不加载了,直接返回加载好的,核心库的String的Class对象,这样我的篡改就失败了,这就保障了核心库API的安全性了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值