【JVM】2、类加载器子系统

1 区分栈的指令集架构和寄存器的指令集架构

Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构

两种架构的区别:

(1)基于栈的指令集架构的特点

  • 设计和实现更简单,适用于资源受限的系统
  • 避开了寄存器的分配难题:使用零地址指令方式分配
  • 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈,指令集更小,编译器容易实现
  • 不需要硬件支持,可移植性更好,更好实现跨平台

(2)基于寄存器架构的特点

  • 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机
  • 指令集架构完全依赖于硬件,可移植性差
  • 性能优秀和执行更高效
  • 花费更少的指令去完成一项操作
  • 大部分情况下,基于寄存器架构的指令集往往都以地址指令为主,而基于栈的指令集架构则是以零地址指令为主

2 概述类的加载器及类加载过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3 类的加载过程一:Loading

类的加载过程可以分为如下三个环节:加载→链接→初始化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 类的加载过程二:Linking

在这里插入图片描述

在这里插入图片描述
验证环节:由于用户可以自己手敲一个二进制文件或者字节码文件被拦截且进行恶意修改,导致程序运行崩溃,因此需要进行一个验证

准备环节:
在这里插入图片描述

准备环节时a被赋值为0,在初始化阶段a才赋值为1

解析环节:
解析通常是初始化之后才执行

5 类的加载过程三:Initialization

在这里插入图片描述
当类包含 static 变量时,编译后的字节码文件才会有<clinit>方法

6 类的加载器的使用

在这里插入图片描述
这里之所以只分为两类加载器,是因为直接或间接继承于 ClassLoader 类的类加载器都划分为自定义类加载器
在这里插入图片描述

/**
 * 注意:getParent() 只是获取上层的加载器,并不是继承关系
 */
public class ClassLoaderTest {
    public static void main(String[] args) {

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@61bbe9ba

        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader); //null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。--> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1); //null

    }
}

  • 我们尝试获取引导类加载器,获取到的值为 null ,这并不代表引导类加载器不存在,因为引导类加载器是由 C/C++ 语言构成的,所以我们是获取不到
  • 两次获取系统类加载器的值都相同:sun.misc.Launcher$AppClassLoader@18b4aac2 ,这说明系统类加载器是全局唯一的

7 引导类、扩展类、系统类加载器的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Solution {
    public static void main(String[] args) {

        System.out.println("**********启动类加载器**************");
        //获取BootstrapClassLoader能够加载的api的路径
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader); //null

        System.out.println("***********扩展类加载器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")) {
            System.out.println(path);
        }

        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}

System.out.println(classLoader); //null 再次证明我们无法获取到启动类加载器

运行结果

**********启动类加载器**************
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/resources.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/rt.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/jsse.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/jce.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/charsets.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/lib/jfr.jar
file:/D:/JavaEnvironment/Java/jdk1.8.0_92/jre/classes
null
***********扩展类加载器*************
D:\JavaEnvironment\Java\jdk1.8.0_92\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@4b67cf4d

8 为什么需要用户自定义类加载器及具体实现

在这里插入图片描述
在这里插入图片描述

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                return defineClass(name, result, 0, result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name) {
        //从自定义路径中加载指定类:细节略
        //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One", true, customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

9 ClassLoader的常用方法和获取方法

ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自 ClassLoader (不包括启动类加载器)

方法名称描述
getParent( )返回该类加载器的超类加载器
loadClass(String name)加载名称为name的类,返回结果为java.lang.Class类的实例
findClass(String name)查找名称为name的类,返回结果为java.lang.Class类的实例
findLoadedClass(String name)查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例
defineClass(String name,byte[ ] b,int len)把字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例
resolveClass(Class<?> c)连接指定的一个Java类

在这里插入图片描述

10 双亲委派机制

Java虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存中生成 class 对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式

工作原理:

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行

  2. 如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器

  3. 如果父类加载器可以完成类加载任务,就成功返回。如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

在这里插入图片描述

示例如下:

我们自己定义一个java.lang包,在其下面定义一个String类,里面声明了静态代码块

在这里插入图片描述

package java.lang;

public class String {

    static {
        System.out.println("我是自定义的String类的静态代码块");
    }

}

在一个测试类中加载String类,看看加载的String类是JDK自带的,还是我们自己编写的

public class StringTest {
    public static void main(String[] args) {
        String str = new java.lang.String();
        System.out.println("你好,世界");
    }
}

结果:程序并没有输出我们静态代码块中的内容,可见仍然加载的是 JDK 自带的 String 类

在这里插入图片描述

这里在加载String类时,一直向上委托父类加载器,最终在引导类加载器完成了String类的加载,因此调用的是系统提供的String类

示例二:

在我们自己定义的 String 类中整个 main( ) 方法

public class String {

    static {
        System.out.println("我是自定义的String类的静态代码块");
    }

    //错误: 在类 java.lang.String 中找不到 main 方法
    public static void main(String[] args) {
        System.out.println("hello,String");
    }

}

在这里插入图片描述

原因:由于双亲委派机制,我们的String类是由引导类加载器加载的,而引导类加载器并没有main方法,所以会报错

11 双亲委派机制的优势

SPI接口是核心接口,需要引导类加载器进行加载,引导类加载器就去加载rt.jar包,就把核心SPI接口加载了,这里面的接口的实现类又需要第三方jar包,需要加载jdbc.jar包,由于jdbc.jar包属于第三方,不属于核心jar包了,因此需要系统类加载器进行加载,这就是反向委派

在这里插入图片描述

  1. 避免类的重复加载。父类加载器加载成功后,子类加载器就不会再进行加载,避免了类的重复加载

  2. 保护程序安全,防止核心API被随意篡改

    • 自定义类:java.lang.String
    • 自定义类:java.lang.ClassTest,java开头的包名由引导类加载器加载,访问该包是需要权限的,禁止使用这样的包名去命名
      在这里插入图片描述

12 沙箱安全机制

在这里插入图片描述

13 其他

两个class对象是否为同一个

在JVM中表示两个class对象是否为同一个类需要两个必要条件:

  1. 类的完整类名必须一致,包括包名

  2. 加载这个类的ClassLoader(指ClassLoader实例对象)必须一致

也就是说,在JVM中,即使这两个class对象来源于同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不相同,那么这两个class对象也是不相同的

比如自定义的java.lang.String和系统提供的核心类java.lang.String,虽然这两个class对象的完整类名一致,但是自定义的java.lang.String的类加载器为系统类加载器,而系统提供的java.lang.String的类加载器是引导类加载器

对类加载器的引用

JVM必须知道一个类型是由启动类加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的

类的主动使用和被动使用

在这里插入图片描述

参看视频:宋红康JVM教程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值