类加载-加载、链接、初始化、类加载器、双亲委派机制、沙箱安全机制

类加载子系统:从本地硬盘或者网络将Class文件加载进内存中
加载过程分为三个阶段:加载-链接-初始化
在这里插入图片描述

加载阶段

将字节码文件变成DNA元数据模板(类信息、方法信息、域信息,这些元数据存储了创建对象和调用时的相关信息)
同时加载阶段会通过类加载器将字节码文件加载进内存中

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • c) 将这个字节流所代表的的静态存储结果转化为方法区的运行时数据结构(元数据(metadata)是关于数据的组织、数据域及其关系的信息
  • d) 在内存中生成一个代表这个类的java.lang.Class对象,该class对象提供了各种方法来方法区这个类的各种数据(类元信息、方法信息、域信息、常量池、静态变量…)

链接阶段

链接分为三个子阶段:验证- 准备-解析

验证

  • 确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
  • 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

文件格式验证:判断字节码文件是否是jvm规定的格式,是否已CAFEBABE开头
在这里插入图片描述

准备

  • 类变量(用static修饰的变量)分配内存,并赋默认值(int为0、boolean为false,引用变量为null…)
  • 这里不包含用final修饰的static,因为final在编译的时候直接被编译器给优化显示替换了
  • 注意:这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
    示例代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.atguigu.java;

public class ClinitTest {
    private int a = 1;
    private static int c = 3;
    private static final int b = 3;

    public static void main(String[] args) {
        int b = true;
    }

    public ClinitTest() {
        this.a = 10;
        int d = true;
    }

    static {
        System.out.println(c);
        System.out.println(3);
        c = 10;
        System.out.println(c);
    }
}

clinit函数字节码:
在这里插入图片描述
静态变量会再次在clinit函数中赋初始值,而常量b直接使用3来代替,不会对常量分配内存。

解析

  • 将常量池内的符号引用转换为直接引用的过程

  • 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行,在需要的时候在进行解析

  • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄

  • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等
    在类加载过程中,当一个类被加载到Java虚拟机中后,其中的方法、字段等元素可能会使用到其他类或接口的引用,这些引用一般以符号形式存在于字节码中,需要经过解析过程将其转换为直接引用。

具体来说,解析阶段包括以下几个步骤:(动态分派)

  • 将类或接口的全限定名转换成对应的二进制名称。
  • 在运行时常量池中查找该类或接口的符号引用,获取对应的类或接口的直接引用。
  • 如果该类或接口还未被加载,则需要先触发其加载过程。
  • 根据该类或接口的直接引用,计算出其在内存中的具体地址。
  • 需要注意的是,在解析阶段中,解析过程的具体实现由Java虚拟机决定,它可以根据具体情况选择不同的实现方式。例如,在HotSpot虚拟机中,解析阶段一般会使用哈希表来实现,从而提高解析的效率。
  • 解析过程

初始化

类的初始化时机

  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(比如:Class.forName(“com.atguigu.Test”))
  • 初始化一个类的子类
  • Java虚拟机启动时被标明为启动类的类
  • JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化,即不会执行初始化阶段(不会调用 clinit() 方法和 init() 方法

clinit()函数

只要类中包含static变量或者static代码块,都会自动产生这样一个函数,对static变量初始化,会自动按照顺序收集static变量赋值语句以及static代码块组合生成一个clinit()函数
示例代码

package com.atguigu.java;

/**
 * @author shkstart
 * @create 2020 下午 8:34
 */
public class ClinitTest {
    //任何一个类声明以后,内部至少存在一个类的构造器
    private int a = 1;
    private static int c = 3;
    private static final int b = 3;
    static {
        System.out.println(c);
        System.out.println(b);
        c = 10;
        System.out.println(c);
        e = 6;  // 可以初始化
//        System.out.println(e); // 但是不可以引用,因为引用不可以在声明之前
    }
    private static int e = 5;
    public static void main(String[] args) {
        int b = 2;
    }

    public ClinitTest(){
        a = 10;
        int d = 20;
    }
    static {
        c = 20;
        System.out.println(c);
    }
}

在这里插入图片描述

init()函数

init()函数就是类的构造函数,收集成员变量的赋值语句、构造代码块以及构造方法来对成员变量赋值(构造代码块的内容会按照顺序放在构造方法中的内容之前)。

package com.atguigu.java;

public class ClinitTest {
    //任何一个类声明以后,内部至少存在一个类的构造器
    private int a = 1;

    public static void main(String[] args) {
        int b = 2;
    }

    public ClinitTest(){
        a = 10;
        int d = 20;
    }
    {
        a = 30;
    }

}

字节码解析:
在这里插入图片描述

双亲委派机制

双亲委派机制是Java类加载器的一种实现方式,它采用了一种树形的加载器委派模型,保证了不同的类加载器在加载类时具有一定的顺序和层次结构。

在这种模型中,每个类加载器都有一个父类加载器,当一个类加载器需要加载一个类时,首先会将该请求委派给它的父类加载器处理。如果父类加载器无法加载该类,那么该类加载器才会自己尝试加载该类。

由于每个类加载器只会尝试加载自己能够加载的类,因此可以避免同一个类被不同的类加载器加载多次的情况。同时,由于Java虚拟机中的核心类都是由引导类加载器加载的,因此可以确保这些类不会被其他类加载器替换。

举个例子来说,假设我们想要替换Java虚拟机中的核心类库中的某个类,如java.lang.String。在这种情况下,如果我们不采用双亲委派机制,而是直接使用一个新的类加载器来加载该类,那么就会出现两个不同的String类,这会导致代码出现不可预知的错误。

而采用双亲委派机制,由于引导类加载器会优先加载核心类库中的类,因此我们可以在不改变原有类的情况下,通过将一个新的类放置在引导类加载器无法访问的位置,让自定义类加载器加载该类,从而实现类的替换。

总的来说,双亲委派机制保证了类加载的顺序和层次结构,避免了类的重复加载和替换问题。同时,由于核心类库中的类都是由引导类加载器加载的,因此可以保证核心代码的稳定性和安全性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值