JVM基础:类的加载


在这里插入图片描述

在这里插入图片描述
Car.class加载到JVM中,称为DNA元数据模板,放在方法区。

类加载的完整过程

在这里插入图片描述
虚拟机对Class文件采用的是按需加载的方式:当需要使用该类时才会将它的Class文件加载到内存生成Class字节码对象【方法区:Hotspot的元空间】
类的加载过程主要分为三个过程:加载、链接、初始化,其中链接又分为:验证、准备、解析

  • 加载:通过类的完全限定名获取此类的二进制字节流,将字节码文件中的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
  • 验证: 验证字节码文件的数据是否符合虚拟机的要求
  • 准备:

(1)为静态成员变量(不包括被final修饰的)分配内存,并设置该静态成员变量的默认初始值。如果静态成员变量
在这里插入图片描述
(2)被final修饰的静态成员变量在准备阶段会显示的初始化值
(3)准备阶段不会为普通成员变量赋值,因为静态成员变量分配在方法区中,而普通成员变量是会随着对象一起分配到堆内存中

public static int num=123;
注意:变量num在准备阶段后的初始值为0而不是123
因为这个时候尚未开始执行任何方法,把123赋值给num是在初始化阶段,在构造方法中进行的

public static final int  num=123;final修饰的静态成员变量,在准备阶段初始化为123而非0,初始化时会把此语句和静态代码块会被合并到初始化构造方法中

public int num=1;
public final num=1;
普通成员变量不会再初始化阶段赋默认值或赋指定值,因为普通成员变量会随着对象存入堆内存中
  • 解析: 将静态常量池中的符号引用转换为直接引用的过程,解析动作主要针对接口、类、字段、类方法、接口方法、方法类型等。
    其中符号引用就是静态常量池中的#开头的符号地址,直接引用就是指向目标的指针或偏移量等。
    在这里插入图片描述
  • 初始化: 实际是执行类构造器方法clinit的过程,此方法不需要定义,javac编译器会自动将类中的所有静态变量的赋值语句、静态代码块收集到Clinit方法中,执行顺序按语句在文件中的出现顺序执行。下图是通过jclasslib反编译后的结果:
    在这里插入图片描述
    在Java编译之后会在字节码文件中生成clinit方法(class init)、init方法,但当源码文件中没有静态代码块、静态成员变量赋值语句时,是没有clinit方法的。

clinit方法(类构造器)、init方法(实例对象构造器)

  • clinit方法是类构造器方法,收集了静态变量初始化语句和静态块语句,执行顺序为:父类静态变量初始化- -》父类静态语句块 --》子类静态变量初始化 --》子类静态语句块
  • init方法是实例构造器,该实例构造器会收集:普通成员变量的初始化语句,构造代码块、调用父类构造器(super())等操作,执行顺序为:父类变量初始化–》父类构造代码块–》子类变量初始化–》子类构造代码块
    在这里插入图片描述
    clinit方法在类加载的初始化阶段执行,而init方法在创建对象的过程中执行
    静态代码块是在类加载的初始化阶段执行的,当初始化一个类的时候发现其父类还没有初始化,需要先对父类进行加载

类的加载器分类

JVM规范中将类的加载器分为两类:启动类加载器(Bootstrap ClassLoader)、自定义类加载器(User-Defined ClassLoader),其中所有派生于抽象类ClassLoader的类加载器都是自定义类加载器。

hotspot中类加载器分类:BootstrapClassLoader、ExtClassLoader、SystemClassLoader、用户自定义类加载器,从前往后是一种包含关系,但不是继承关系。

(1) 启动类加载器Bootstrap ClassLoader

  • 启动类加载器Bootstrap ClassLoader主要用来加载Java核心内库,用于加载JVM自身需要的类
  • 启动类加载器Bootstrap ClassLoader是用C/C++语言编写的,并不继承java.lang.ClassLoader,没有父加载器
  • 启动类加载器会加载扩展类加载器、应用程序加载器,并指定他们各自的父类加载器

(2)扩展类加载器Extension ClassLoader

  • 扩展类加载器从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext目录下加载类库。如果用户创建的jar包也存放在该目录下,也会被扩展类加载器加载。
  • 扩展类加载器Extension ClassLoader是用java语言编写的,是sun.misc.Launcher的内部类ExtClassLoader,ExtClassLoader继承了抽象类ClassLoader
  • 扩展类加载器的父类加载器为引导类加载器

(3)应用程序类加载器(系统类加载器 AppCLassLoader)

  • 应用程序类加载器主要负责加载环境变量classpath或java.class.path指定的路径下的类库
  • 应用程序类加载器用java语言编写的,是sun.misc.Launcher的内部类AppClassLoader,ExtClassLoader继承了抽象类ClassLoader
  • 应用程序类加载器的父类加载器为扩展类加载器
  • 用户自定义的类一般情况下都是由应用程序类加载器加载的
  • 通过抽象类ClassLoader的静态方法可以获取当前类的类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader()

(4)自定义类加载器(目前仅作了了解,待后续学习)

为什么要自定义类的加载器?

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止代码泄漏

双亲委派机制

工作原理:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是委托给父类加载器加载,如果父类加载器还存在父加载器,则继续向上委托,最终将类加载请求委托给启动类加载器执行。如果父类加载器可以完成类的加载,则成功返回,如果父类加载器不能完成加载,则子加载器才会去尝试加载
各加载器到各自负责的路径下加载,不能加载该类,则子类到自己负责的路径下加载。
在这里插入图片描述
举例: 加载JDBC驱动时,系统类加载器将请求一层一层向上委托给启动类加载器,但启动类加载器、扩展类加载器都无法加载,最终又反向委派给系统类加载器加载。

验证双亲委派机制

在项目中将创建一个与String包名相同的包java.lang,并自定义String类:

public class String {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("验证双亲委派机制");
    }
}

在这里插入图片描述
分析:
运行main方法时,应用程序类加载器收到加载java.lang包下String类的请求,一路向上委托至启动类加载器,启动类加载到java的核心类库中加载了String类,而核心类库中的String类没有main方法,因此报错:找不到main方法
上面这种保护核心API不被篡改的机制即:沙箱安全机制

扫描二维码关注公众号,回复: 11951533 查看本文章

使用双亲委派机制的原因

(1)避免类的重复加载
(2)保护核心API被篡改(沙箱安全机制)
以上两点均可以通过验证双亲委派机制的例子证明

判断两个Class类型的对象是否是同一个对象的条件

(1)类的完全限定名必须相同
(2)加载Class类型对象对应的Class文件的ClassLoader实例对象必须相同(同一类型的同一个对象)

类加载器常用API

常用的三种获取应用程序类加载器的方式

 System.out.println(Demo.class.getClassLoader());
 System.out.println(Thread.currentThread().getContextClassLoader());
 System.out.println(ClassLoader.getSystemClassLoader());

在这里插入图片描述

获取当前类的类加载器、获取父类加器、获取各类加载器可以加载的jar包路径

public class Demo {
    
    
    /**
     * BootstrapClassLoader、SystemClassLoader、ExtClassLoader、用户自定义类加载器,从前往后是一种包含关系,
     * 但不是继承关系
     */
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //获取系统类加载器
        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@725bef66
        //获取启动类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null:启动类加载器是用C或C++编写的,无法获取对象
        /**
         * 对比发现:我们自定义类的加载器和应用程序类加载器的地址是一样的,因此自定义类使用的是应用类加载器
         */
        System.out.println(Demo.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2
        //String类的加载器则是启动类加载器,间接证明:Java的核心类库是都是使用启动类加载器加载
        System.out.println(String.class.getClassLoader());//null
        
        
        //获取启动类加载器的可以加载jar包的路径
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL urL : urLs) {
    
    
            System.out.println(urL);
        }
        //获取扩展类加载器的可以加载jar包的路径
        String property = System.getProperty("java.ext.dirs");
        System.out.println(property);
    }
}

猜你喜欢

转载自blog.csdn.net/user2025/article/details/109137392