JVM学习日志(一) 类加载机制

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 简述JVM的类加载机制

JVM的类加载机制

  • 类从被加载到虚拟机内存中开始,到卸载出内存为止,他的整个生命周期包括:加载(loading), 验证(Verification),准备(Preparation),解析(Resloution),初始化(initialization),使用(Using)和卸载(OnLoading) 七个阶段,其中,验证,准备,解析,三个部分统称为链接(linking)
  • image-20230329103813207.png

每个类加载的流程

image-20230329113722053.png

  • 以上的加载,验证,准备,解析的流程是基本固定化的,也就是对于大部分类使用之前都必须按照这个流程进行,但是解析的流程不确定,现目前jvm存在动态解析,所以解析这个流程比较重要`
  • 注意,验证这个流程是在管道搭建完成后,通过IO读取数据的时候,就会执行验证流程,如果验证不通过,就会报 java.lang.CLassFormatError 异常

JVM类加载各个流程拆解

加载(Loading)

  • 加载Loading阶段就是JVM第一次去加载和读取对应位置上的文件,上述的整个类加载过程其实都是类加载器在完成。相当于正式建立了IO通道,在这通道上,我们需要作上述的一系列事情,比如验证,准备,解析等
JVM再什么情况下会加载一个类?JVM如何加载一个类
  • Java程序运行的时候,首先会将包含Main方法的启动类加载到内存中,如果这个启动类中又创建了一个对象,那么,这个时候JVM就会在内存中扫描是否含有这个类,如果没有就会去将被创建类的字节码文件加载到内存中

  • image-20230329110813535.png

  • 运行并查看JVM类加载相关的指令

    java-XX:TranceClassLoading-cp .cn.itsource.load.LoadTest

  • 执行效果:

image-20230329111314981.png

验证(Verification)

  • 验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《JAVA虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全

  • 验证主要包括四种验证:文件格式验证(魔数CAFEBABE),元数据验证,字节码验证,符号引用验证,简单来说就是我们的.class文件是否符合JVM规范,是否有被篡改,否则JVM是没法执行该字节码文件的

  • 每个符合规范的JAVA文件二进制开头应该是对应的魔数CA FE BA BE

image-20230329112408065.png

  • 如果字节码文件的二进制文件的开头CA FE BA BE被修改之后,当运行的时候,会报异常:

    image-20230329113318737.png

准备(Preparation)

  • 准备阶段是正式为类中定义的变量,及静态变量(被statistic修饰的变量),分配内存并设置类变量的初始值的阶段

  • JVM在准备阶段的操作流程图

image-20230329141934995.png

  • 准备阶段工作流程梳理

  • 当JVM通过加载阶段创建通道之后,加载的.class文件通过验证流程之后,会先将.class文件解析成为一个.class对象模板,存放在方法区的元空间中,但是此时并不会创建对象,当对象模板解析完成之后,会在堆内存中创建一个.class字节码文件对象,然后给对象中的静态变量赋初始化值,后续在程序中不论是new对象还是通过反射创建对象,都是以堆内存中class对象作为模板来复制
    image-20230329143004917.png

  • 注意

    • statistic变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于 java_mirror末尾,换句话说,1.7之前是存在于方法区,1.7之后是存在于堆内存中
    • java_mirror:一个指向堆内存对象的引用属性
  • 特殊情况

    • static变量分配空间何赋值是两个步骤,分配空间(内存分配)在准备阶段完成,赋值在初始化阶段完成

    • 关于准备阶段,还有两个容易产生混淆的概念需要着重强调,首先是这时候进行内存分配的变量仅包括静态变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆内存当中,其次是这里所说的初始值“通常情况下”是数据类型的零值,假设一个类变量的定义为:

      public static int value = 123;
      

      那变量value在准备阶段过后的初始值为0而不是123,因为这个时候尚未开始执行任何java方法,而把value赋值为123的put static指令是程序被编译后,存放于类构造器()方法中,所以把value赋值为123的动作要到类的初始化阶段才会被执行

    • 如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成

      • 上面提到在'通常情况下'初始值是零值,那言外之意就是会有些'特殊情况';如果类字段的字段属性表中存在ConstanValue属性,那再准备阶段变量值就会被初始化为ConstanVlaue属性所指定的初始化值,假设上面的类变量定义为:

        public stattic final int value = 123;
        

        编译时javaC将会为value生成ConstanValue属性,在准备阶段虚拟机就会根据ConstanValue属性来给value变量赋值为123

      • 如果static变量是final的,但是属于引用类型,那么赋值也会在初始化阶段完成

解析阶段

  • 主要将常量池中的符号引用替换为直接引用的过程
    • java代码在进行javaC编译的时候,在虚拟机加载Class文件的时候进行动态链接,在Class文件中不会保存各个方法,各个属性的布局信息,这些字段,方法的符号引用不经过虚拟机在运行期间转换的话是无法得到真正的入口地址,也就无法直接被虚拟机使用的,当虚拟机唑类加载时,将会从常量池中获取对应的符号,引用,再在类创建时或运行时解析,翻译到具体的内存地址中
    • 符号引用(Symbolic References)
      • 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到到虚拟机内存当中的内容,各种虚拟机实行的内存布局可以各不相同,但是他们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义再《JAVA虚拟机规范》的class文件格式中
    • 直接引用(DIrect References)
      • 直接引用是可以直接指向目标的指针,相对偏移量或者是一个能直接间接定位达到目标的句柄,直接引用是和虚拟机实现的内存布局直接相关,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在JVM的内存中存在

初始化阶段

  • 通过准备阶段类变量已经赋过一次系统要求的初始化零值,而初始化阶段就是在给类变量进行赋值操作

  • 初始化阶段会执行类构造器方法(),该方法不同于类构造器(是虚拟机视角下的()方法);该方法不需要定义,是javaC编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而成

  • 同理,静态代码块的操作也是在Clinit方法中执行

  • 注意

    • 编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句中只能访问到定义在静态语句块之前的变量,定义在他之后的变量,在前面的静态语句块中是可以赋值,但是不能访问(如下代码,编译是可以通过的,但是在执行的时候会报错)

image-20230329183933399.png

  • 父类初始化先执行

    • ()方法与类的构造函数(及在虚拟机视角中的实例构造器方法)不同,他不需要显式的调用父类构造器,java虚拟机会保证在子类的方法执行之前,父类的方法已经执行完毕,因此在java虚拟机中第一个被执行的方法的类型一定是java.lang.Object
  • 线程同步

    • java虚拟机必须保证一个类的方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的方法,其他线程都需要阻塞等待,直到活动线程执行完毕方法,如果在一个类的方法中有耗时很长的操作,那就可能造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。
    • 其他线程虽然会被阻塞,但如果执行方法的那条线程退出方法之后,其他线程唤醒后则不会在次进入方法,同一个类加载器下,一个类型只会被初始化一次
  • 总结

    • 一个类如果已经被一个线程加载到内存中,也就代表加载过程中的这些阶段(加载-连接)都已经执行完毕,那么后续的线程想要访问对应的字节码对象直接访问即可,不需要再次去加载这个类的字节码文件了
    • 对应了我们javaSE中的语法问题,一个类中的静态修饰的内容会随着类的加载而加载,并且只会被加载一次 《这个说法仅仅限于同一个类加载器》

总结

  • 类加载阶段:建立通道:本地磁盘到内存的通道
  • 验证阶段:校验字节码文件是否符合JVM的规范
  • 准备阶段:分配内存空间,给类变量设置初始化值
  • 解析阶段:将符号引用(占位符)转化为直接引用(真实内存地址)
  • 初始化阶段:给类变量进行赋值的操作

image-20230331101210582.png

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
9天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
107 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
45 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
2月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
3月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
45 4
|
3月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
87 3
|
3月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
47 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
356 1
|
3月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
48 4
|
29天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。