JVM(二)Class类的加载过程

Class Loading Linking Initalizing

有一个class文件,它默默躺在硬盘上,需要经过一个什么样的过程才能到内存里准备好呢?
在这里插入图片描述

class怎么进入内存分为三大步:

  1. 第一步Loading
    把一个class文件通过ClassLoader读取到内存,他本来是一个class文件上的一个一个的二进制,一个一个字节,装完之后就是接下来的Linking
  2. 第二步Linking,分为三小步:
    • Verification
      是校验装进来的class文件是不是符合class文件的标准,假如你装进来的不是这个CA FA BA BE,在这个步骤就被拒接掉了
    • Preparation
      是把class文件静态变量赋默认值,不是初始值,比如你static int i =8,注意在这个步骤8并不是在把i值赋值成8,而是先赋值为0
    • Resolution
      把class文件常量池的符号引用转换成直接引用(内存地址)
  3. 第三步Initlalizing
    调用类初始化代码,给静态成员变量赋初始值
  • 假如有一个成员变量private int m = 8;要想用起来的话是需要new 对象的,这点比较简单,这个new对象的过程分为2步,第一步是给这个对象申请内存的过程,申请完内存之后它里面的变量先赋默认值.new T再来分解的话他实际上中间也是两步,第一步是new出来T这个内存,new出来之后里面这个成员变量还没赋值呢,他默认值为0,下一步他才会调用构造方法,调用构造方法之后才会赋初始值变成8.
    load-默认值-初始值
    new-申请内存-默认值-初始值

类的加载器

在这里插入图片描述

  • 我们来看这个类的加载器的内容,首先第一点JVM它本身有一个类加载器的层次,这个类的加载器本身就是一个普通的class,jvm有一个类加载器的层次分别来加载不同的class,jvm索引的class都是被类加载器加载到内存的,那么这个类加载器可以叫做ClassLoader.
  • 每一个class在java虚拟机里面到内存里面任何一个class都是被ClassLoader load内存的,那么这个ClassLoader其实就是顶级有一个父类,这个父类就叫ClassLoader,他是一个abstract抽象类,相当于这个类是被谁领到内存里去了,他一定是ClassLoader这个类的子类,如果你想知道你的class是被谁弄到内存里的,其实很简单就是下面代码
    System.out.println(类名.class.getClassLoader)
  • 一个class文件平时躺在硬盘上,这个内存被load到内存之后,创建了两块内容,第一块内容把class二进制扔到了内存中,第二块内容是于此生成一个class类对象,这一块对象指向第一块内容
  • 以前用过反射的同学应该知道,我们通过class这个对象去拿他的有哪些方法甚至方法可以调用,分析一下执行反射的过程中,他一定是那些方法的信息存在这个对象里,然后真让这个方法执行的时候,他一定会去找那些class文件里面的二进制码,翻译成java指令一步一步来执行
  • 类加载器层次
    类加载器加载过程,加载过程分为不同的层次来加载,不同类的加载器来加载不同的class文件
    在这里插入图片描述
  1. 第一个类加载器层次
    最顶层的是BootStrap(c语言实现),他是用来加载lib里jdk最核心的内容,比如说rt.jar,charset.jar等核心类,当我们调用getClassLoader获取到null值的时候,代表已经达到了最顶层的加载器
  2. 第二个类加载器层次
    这个是Extension加载器扩展类,加载扩展包里各种各样的文件,这些扩展包在jdk安装目录jre/lib/ext下的jar
  3. 第三个类加载器层次
    这个就是平时用的加载器application,它用于加载classpath指定的内容
  4. 第四类的加载器层次
    这个就是自定义加载器ClassLoader,加载自定义的加载器
    CustomClassLoader父类加载器>application父类加载器>Extension父类加载器>Bootstrap
    在这里插入图片描述
    注意:这个图讲的是这个classLoader语法上是从谁继承的,不是类加载器的继承关系
  • 双亲委派
    在这里插入图片描述

一个class文件需要被load内存时候是这样的
任何一个class,加入自定义classloader,这时候先去自定义的classloader里面找,他内部维护着缓存,看缓存中有没有,有就直接返回,没有的话并不会直接加载,而是就接着往上级找,有就返回,没有就继续往上级找,最后如果父类Bootstrap也没有加载过,就委托Extension去加载,如果能加载就加载,不能加载就往下传递,如果能就就直接加载,最后如果都不能加载,就抛出异常ClassNotFound找不到,这就叫双亲委派机制
在这里插入图片描述

为什么要搞双亲委派机制?
主要是为了安全,加入用反正法,如果任何一个class都可以把它load到内存的胡,那我就可以给你java.lang.string,我交给自定义classloader,把这个string load到内存,打包给客户,然后给密码存储成string对象,我可以偷偷摸摸的把密码发给我自己,那就不安全了
双亲委派机制就不会出现这样的情况,自定义classloader加载一个java.lang.string他就产生了警惕,他先去上面查看有没有加载过,上面有加载过,直接返回给你,不给你重新加载

父加载器不是"类加载器的加载器"也不是"类加载器的父类加载器"
Laucher就是ClassLoader的一个包装类启动类,在这个类里面你要去看源码,就会看出很多内容,比如:类的加载路径,其实这些都规定在Launcher源码里面了
你可以通过System.getProperties(“类的加载路径(上面类加载路径图地址)”)来获取对应类加载器能加载的jar包,可以加一个replace()方法把;符号;换成System.lineSeparator()
双亲委派主要的含义就是当你要求一个classloader去load class的时候,它首先去找他的父亲,注意这个父亲不是继承关系上的父亲,是通过他的成员变量parent,是源码指定的被final修饰了,自定义的classLoader默认是通过父类的无参构造方法指定的,你可以调用父类的有参构造来设置自定义classLoader的父加载器

  • 自定义加载器
    在这里插入图片描述
    需要先来看一下源码:
    在这里插入图片描述
    loadclass这个方法过程,就说你要加载一个类你只需要调用classLoader的loadclass方法就能够把这个类加载到内存中,加载到内存他会给你返回一个class类的对象,也就是刚才的过程,在硬盘上找到这个类的源码,这些源码可能在那些目录下面,找到这个class文件源码之后,把他load到内存,于此同时生成一个class类对象,把这个class对象返回给你
    什么时候我们需要自己去加载一个类?Tomcat在load自己的那部分肯定是要load自定义的那些class的JRebel热部署你这个class放硬盘上我怎么去给你热替换,肯定需要一个classloader手动的load到内存里面去,spring动态代理,他是一个新的class,当你要用的时候他把新的load内存里了

在这里插入图片描述
自定义类加载器:
ClassLoader源码执行步骤:
findInCache->parent.loadClass->findClass()
自定义类加载器很简单,需要你自己继承ClassLoader类,去重写findClass方法,在findclass方法里找到需要load进来的二进制的内容,在内存里给它完整的load进来,load完之后再把这部分内容转换成class类对象,用defindClass方法,这种模式叫钩子函数模板方法
lazyLoading
懒加载
在这里插入图片描述
java是懒加载,什么时候需要才加载
执行模式
一个class文件load到内存之后通过java的解释器intepreter来执行,JIT的编译器指的是有某些代码我需要就把它编译成本地代码,相当于是exe,所以当有人问你java是编译语言还是解释语言,你完全可以告诉他我想解释就解释想编译就编译看你怎么定了,默认是混合模式,就是混合使用解释器+热点代码编译(hotspot)
什么叫做热点代码编译?
多次被调用的方法,多次被调用的循环,进行编译,怎么检测呢?就是用一个计数器,每个方法上都有一个方法计数器,循环有循环计数器,结果在发现某个方法执行了超过某个设定的值(XX: CompileThreshold=值),就要对它就行编译,直接编译成本地代码,在用的话直接用本地的,不要解释器执行了
为什么不直接都编译成本地代码呢,执行效率更高?

  1. 因为java解释器现在效率也非常高了,在一些简单的代码执行上它不输于编译器
  2. 如果你有一段代码执行文件特别特别多各种各样的类库时候,好几十个class这是很正常的,你上来二话不说先给编译器编译,过程会非常的长,所以现在默认的模式是混合模式,但是完全可以用参数来指定是什么模式
    在这里插入图片描述
    扩展知识:
    如何打破双亲委派机制?
    1. 重写loadClass()
    2. 在JDK1.2之前,自定义ClassLoader都必须重写loadClass()
    3. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
    4. 模块化的时候使用热部署
      比如:tomcat,都有自己的模块指定classloader(可以加在同一类库的不同版本),两个classloader可以load进来同名的类,这是完全没有问题的,这里已经打破了双亲委派机制
### JVM加载机制及过程详解 JVM加载机制是一个复杂但至关重要的过程,它决定了Java程序如何将`.class`文件中的进制字节流转化为可以在JVM上执行的对象。以下是关于这一机制及其各个阶段的具体说明。 #### 1. 加载器体系结构 JVM采用了一种层次化的加载器模型,主要包括以下三种型的加载器[^2]: - **启动加载(Bootstrap ClassLoader)** 负责加载核心Java库(如`rt.jar`),通常位于`$JAVA_HOME/lib/`目录下。它是JVM的一部分,由本地代码实现,并不继承自`java.lang.ClassLoader`。 - **扩展加载(Extension ClassLoader)** 负责加载标准扩展库,默认路径为`$JAVA_HOME/lib/ext/`或由系统属性`java.ext.dirs`指定的位置。它是`java.lang.ClassLoader`的一个子。 - **应用程序加载(Application ClassLoader)** 又称为系统加载器,用于加载用户的应用程序代码,默认路径为当前路径(即环境变量`CLASSPATH`)所指向的目录或jar包。 可以通过如下代码验证不同加载器的作用范围[^3]: ```java public class TestJDKClassLoader { public static void main(String[] args) { // 输出核心加载器(null表示由启动加载加载) System.out.println("引导加载器:" + String.class.getClassLoader()); // 输出扩展加载器 System.out.println("扩展加载器:" + DESKeyFactory.class.getClassLoader()); // 输出应用加载器 System.out.println("应用程序加载器:" + TestJDKClassLoader.class.getClassLoader()); } } ``` --- #### 2. 加载过程 加载分为五个主要阶段:**加载、连接、初始化**。其中,“连接”又细分为三个子阶段:**验证、准备、解析**[^4]。 ##### (1)加载 在此阶段,JVM完成以下操作: - 根据的全限定名找到对应的`.class`文件; - 将其读取为进制字节流并存储到内存的方法区中; - 创建一个代表此的`Class`对象实例,作为访问方法区数据的入口。 ##### (2)连接 ###### a. 验证 确保加载符合JVM规范,防止恶意代码破坏运行时安全。验证的内容包括但不限于: - 文件格式是否正确; - 字节码指令是否有非法跳转; - 访问权限是否合法等。 ###### b. 准备 为的静态字段分配内存空间,并设置默认初始值(注意此时不会执行任何代码逻辑)。例如对于`static int value = 10;`,在准备阶段只会将其赋值为`0`,而不是`10`。 ###### c. 解析 将中的符号引用替换为直接引用。这一步骤涉及对方法、字段以及父的查找和绑定。 ##### (3)初始化 这是最后一个阶段,在这里才会真正执行中的代码逻辑,主要是为了给的静态变量赋予正确的初值,并执行静态代码块。如果存在父,则需先完成父的初始化再处理子。 --- #### 3. 触发加载的情况 并非所有的都会立即被加载至内存,只有满足特定条件时才触发加载行为: - 当主函数所在的被执行时; - 使用`new`关键字创建对象实例; - 获取或修改某个的静态成员变量; - 调用某的静态方法; - 利用反射API动态加载; - 初始化过程中发现尚未加载的父或接口。 --- #### 4. 双亲委派机制与例外情况 双亲委派机制规定了加载器的工作顺序——当接收到一个加载请求时,优先交由上级加载器尝试加载;仅当下级无法完成加载任务时,本层加载器才会介入工作[^5]。然而某些框架(如Tomcat)出于隔离需求可能会打破这种模式,自行定制专属的加载策略。 --- ### 总结 综上所述,JVM加载机制不仅涵盖了多种型的加载器协作分工,还包含了多个严谨有序的操作环节以保障程序稳定高效地运行于虚拟环境中。理解这些原理有助于开发者更好地优化性能、排查错误甚至设计插件化架构解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值