Java类加载器及加载机制

Java类加载过程及加载过程

1. Java类加载器

1.1 什么是Java类加载器

  • Java类加载器将编译后的class文件加载到内存中,并将这些静态数据转换为方法区的运行时数据,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

1.2 类缓存

  • 在加载器中也有缓存机制,标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM的垃圾回收机制(GC)可以回收这些Class对象

1.3 JVM规范定义了如下类型的类加载器

  • BootstrapClassLoader : 引导类加载器(根类加载器,启动类加载器)
    • 该加载器是用C++编写的,是JVM自带的类加载器,用来装载Java核心类库。该类加载器无法直接获取。
     //获取拓展类加载器的父类----> 根加载器 (启动类加载器)
        ClassLoader bootClassLoader= extClassLoader.getParent();
        System.out.println("根加载器---"+parent1);
        //根加载器---null 因为该加载器是C++编写所以无法在Java表示
    
  • ExtensionClassLoader:扩展类加载器
    • 负责加载jre/lib/ext下的jar包,或者被java.ext.dirs指定目录下的jar装入工作库
     //获取系统类加载器的父类---> 拓展类加载器
        ClassLoader extClassLoader= systemClassLoader.getParent();
        System.out.println("拓展类加载器---" + parent);
        //拓展类加载器---sun.misc.Launcher$ExtClassLoader@1540e19d
    
  • SystemClassLoader:系统类加载器(应用程序类加载器,ApplicationClassLoader)
    • 负责加载ClassPath中的类库,是最常用的加载器
     //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器---" + systemClassLoader);
        //系统类加载器---sun.misc.Launcher$AppClassLoader@18b4aac2
    
    • 我们也可以查看当前类是哪个加载器加载的
    	//测试当前类是哪个加载器加载的
        ClassLoader getClassLoader = Class.forName("GetClassLoader").getClassLoader();
    	//sun.misc.Launcher$AppClassLoader@18b4aac2
    
    • 也可以查看JDK内置的类是哪个加载器加载的
    	//测试JDK内置类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("java.lang.Object").getClassLoader();
        //null
        //这就证明是Bootstrap ClassLoader加载器加载的
    
  • 自定义类加载器
    • 通过继承ClassLoader实现,一般是加载我们的自定义类

1.4 双亲委派机制

  • 类加载器的Java类也如同其他Java类一样,也是要由类加载器来加载的,除了启动类加载器,每个类都有其父类加载器(父子关系由组合(不是继承)来实现)
  • 所谓双亲委派机制是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有的加载请求最终会委派到模型顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类才会尝试加载
  • 双亲委派机制的好处
    • 避免同一个类被多次加载
    • 每个加载器只能加载自己范围内的类
      在这里插入图片描述
    • 我们来看一个错误的示范
    package java.lang;
    public class String {
    	public static void main(String[] args) {
    	}
    }	
    //当我们的程序去加载当前我们自定义的 java.lang.String类时,我们的加载请求会传递到模型顶层,我们的启动器加载类会在自己的范围内搜索,肯定能搜索到嘛,因为java.lang.String是我们的字符串的类,所以JVM会加载系统内的类而不会加载我们的类,所以我们的类肯定是跑不起来的
    

2. Java类加载过程

类加载过程分为三个步骤:加载,连接,验初始化;
在这里插入图片描述

2.1 加载

  • 根据一个类的全限定名(如cn.edu.hdu.test.HelloWorld.class)来读取此类的二进制字节流到JVM内部;
  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构(hotspot选择将Class对象存储在方法区中,Java虚拟机规范并没有明确要求一定要存储在方法区或堆区中)
  • 转换为一个与目标类型对应的java.lang.Class对象;

2.2 连接

  1. 验证

    验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;

  2. 准备

    类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量将不再此操作范围内);

  3. 解析

    将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针,以便直接调用该方法),这个阶段可以在初始化之后再执行
    ps:对符号引用不懂或者感兴趣的可以去看我的另一篇博客《Java符号引用和直接引用》

2.3 初始化

  • 初始化会做什么
    • 所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里,存放到一个特殊的方法中,这个方法就是clinit方法,即类/接口初始化方法,该方法只能在类加载过程中由JVM调用
    • 编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态代码块中只能访问到定义在静态语句块之前的变量
    • 若当前加载类的父类还没被初始化,那么优先对父类初始化,但在方法内部不会显示调用父类的方法,由JVM负责保证一个类的方法执行之前,其父类方法已经执行
    • JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。(所以可以利用静态内部类实现线程安全的单例模式)
    • 如果一个类没有声明任何的类变量,也没有静态代码块,那么可以没有类的方法
  • 何时会发生类的初始化
    • 类的主动引用(一定会发生类的初始化)
      • 当虚拟启动,先初始化main方法所在的类
      • new一个类的对象,反射或序列化也会
      • 调用类的静态成员(除final常亮)和静态方法
      • 当初始化一个类,如果父类没有被初始化,则会先初始化它的父类
    • 类的被动引用(不会发生类的初始化)
      • 当访问一个静态域时,只有真正声明这个与域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化1
      • 通过数组定义类引用,不会触发此类的初始化
      • 引用常亮不会触发此类的初始化(常亮在连接阶段就存入了常量池中了)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值