河北王校长JVM

河北王校长JVM

Class文件

  • 文件结构

    • 魔数(Magic Number):class文件的前4个字节是固定的魔数,用于标识文件类型为class文件。魔数的值为0xCAFEBABE。

    • 版本信息:紧随魔数后的4个字节表示class文件的次版本号和主版本号。Java的版本号从45开始,每个新版本增加1,例如Java 8的版本号为52。

    • 常量池(Constant Pool):常量池是class文件中的一个重要组成部分。它包含了类、接口、字段、方法的符号引用以及文本字符串等。常量池使用变长的数据结构表示,保存在class文件的常量池表(Constant Pool Table)中。

    • 访问标志(Access Flags):访问标志描述了class文件的访问控制属性,包括是否是public、final等。访问标志的值使用特定的标志位表示。

    • 类索引(This Class):类索引指向常量池中类定义的符号引用,用于表示当前类。

    • 父类索引(Super Class):父类索引指向常量池中父类的符号引用,用于表示当前类的父类。

    • 接口索引集合(Interfaces):接口索引集合包含了所有实现的接口的符号引用。每个接口索引指向常量池中的接口定义。

    • 字段表集合(Fields):字段表集合描述了类中声明的字段信息,包括字段名称、访问修饰符、类型等。

    • 方法表集合(Methods):方法表集合描述了类中声明的方法信息,包括方法名称、访问修饰符、参数列表、返回类型等。

    • 属性表集合(Attributes):属性表集合用于保存与类、字段、方法等相关的额外信息,如注解、行号表、字节码等。

  • 字段方法名长度

    • 65535
  • 常量池计数器起始为1

    • 特殊处理:匿名内部类;object
  • 类中常量(final static String)在字节码中的存储方式

    • 直接存储到class文件字段表中属性表constant属性
  • 类索引查找全限定类名过程

    • 指向常量池中的constant_class_info类型,constant_class_info又指向了constanct_utf8_info 从而最终找到全限定名
  • 接口索引查找全限定接口名过程

    • 指向常量池中的constant_interface_info类型,constant_class_info又指向了constanct_utf8_info 从而最终找到全限定名
  • 方法表code属性

    • 将代码转化成JVM字节码指令存储到方法表Code属性

    • 名称 数量 备注
      attribute_name_index 1 此常量值固定为"Code"
      attribute_length 1
      max_stack 1 操作数栈深度的最大值
      max_locals 1 局部变量所需最大空间,按Slot计算
      code code_length 方法源代码
      code_length 1 方法代码长度

类加载子系统

  • JVM类加载流程
    • 加载
      • 加载字节流
      • 文件验证
      • 转化为运行时数据区
      • 生成数据结构
    • 链接
      • 验证
        • 元数据验证
        • 字节码验证
      • 准备 准备0值
      • 解析
        • 符号引用验证
    • 初始化
  • 双亲委派模型
    • 启动类加载器 <JAVA_HOME>\lib
    • 扩展类加载器 <JAVA_HOME>\lib\ext
    • 应用程序加载器
    • 自定义类加载器
  • 双亲委派模型的优点
    • 避免重复加载
    • 安全
  • 数组加载
    • 数组类本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的
    • 如果数组的组件类型是引用类型,那就遵循JVM类加载过程去加载这个组件类型,数组C将被标识在加载该组件类型的类加载器的类名称空间上
    • 如果数组的组件类型不是引用类型,Java虚拟机将会把数组C标记为与引导类加载器关联。
  • 初始化阶段
    • 调用clinit()方法
  • JVM执行类初始化的场景(有且只有6类)
    • 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
      • 使用new关键字实例化对象的时候。
      • 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
      • 调用一个类型的静态方法的时候。
    • 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
    • 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    • 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
    • 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

运行时数据区

  • 运行时数据区包括什么
    • 方法区
      • 类信息:方法区存储了每个类的完整结构信息,包括类的类名、访问修饰符、父类信息、接口信息、字段信息、方法信息等。这些信息被加载到方法区后,供虚拟机在运行时使用和处理。
      • 运行时常量池:方法区中还包含了每个类的运行时常量池,它是类文件中的常量池表在内存中的表示形式。运行时常量池中存储了类中使用的常量、字符串字面量、符号引用等信息。
      • 静态变量:方法区中存储了类的静态变量,也即类级别的变量。静态变量在类加载时被初始化,并在整个程序的生命周期内存在。
      • 类方法:方法区中存储了类的方法信息,包括方法的字节码、访问修饰符、参数列表、返回值类型等。类方法在类的加载过程中被加载到方法区,并供虚拟机执行。
      • 常量池:方法区中还包含了一部分常量池数据,用于存储字符串常量、类和接口的符号引用等。这些常量数据可以被类的方法和字段引用。
      • 对象
        • 对象头
          • Mark Word:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
          • 类数据
        • 实例数据
        • 对齐填充
    • 虚拟机栈
      • 操作数栈 支持java进行复杂操作
      • 局部变量表 存储栈帧中参数及局部变量
      • 动态链接 运行时常量池的方法引用动态转化为直接引用,方法动态调用
      • 返回地址 方法正常退出或异常退出,恢复其调用者状态
    • 本地方法栈
    • 程序计数器
      • 确定代码执行位置
  • 64位JVM在new Object()操作时,对象实例占多少个字节
    • java 默认使用了 calssPointer 压缩,markword 8 字节 ,classpointer 4 字节,对象实例0字节,padding 4 字节 因此是 16 字节
    • 如果没开启 classpointer 默认压缩,markword 8 字节,classpointer8字节,对象实例0字节,padding 0 字节 也是 16 字节。
  • 对象如何被线程访问定位
    • 句柄访问和直接指针访问

堆结构和垃圾回收

  • 堆结构

    • 年轻代
      • eden区:s0区:s1区=8:1:1
      • 基于标记复制算法
      • 初次gc,存活对象移动到s0,age+1;
      • 第n次gc,所有存活对象移动到s1,age+1,s0、s1互换身份;
    • 老年代
      • 超过年龄阈值(默认15) -XX:MaxTenuringThreshold
      • 大对象超过阈值 -XX:PretenureSizeThreshold
      • 幸存者空间有相同年龄所有对象大小总和大于幸存者空间的一半,将大于等于次年龄的对象移动
      • eden区存活对象超过s区大小
  • 空间分配担保

    • 年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间
      • 这个可用空间小于年轻代里现有的所有对象大小之和
        • 检查-XX:HandlePromotionFailure参数允许担保失败
          • 判断老年代最大可用连续空间大于历次晋升到老年代对象的平均大小,minor gc
          • 判断老年代最大可用连续空间小于历次晋升到老年代对象的平均大小,full gc
        • 检查-XX:HandlePromotionFailure参数允许不担保失败,full gc
      • 这个可用空间大于年轻代里现有的所有对象大小之和
        • 直接minor gc
  • 可达性分析

    • 判断对象是否存活的算法

      • 通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称 为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从 GC Roots到这个对象不可达时, 三色标记,引发垃圾回收
    • GC Roots

      • 常量对象
      • 静态变量对象
      • 虚拟机栈
      • 本地方法栈
      • 虚拟机内部引用
      • 锁持有对象
    • 引用链

      • 强引用
        • 只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
      • 软引用
        • 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收
      • 弱引用
        • 被弱引用关联的对象只能生存到下一次垃圾收集发生为止
      • 虚引用
    • 对象不可达意味什么

      • 如果对象在进行可达性分析后发现没有与GCRoots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。
    • 三色标记

      • 白色 不可达
      • 灰色 中间
      • 黑色 安全
    • 跨代引用

      • 引入记忆集。记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。
      • 最常用的一种记忆集实现形式种称为“卡表”,卡表中的每个记录精确到一块内存区域(每块内存区域称之为卡页),该区域内有对象含有跨代指针
  • 垃圾回收算法

    • 标记清除
    • 标记复制
    • 标记整理

垃圾回收处理器

  • 分类

    • 新生代(标记清除)

      • Serial 单线程 垃圾收集运行时候 stop the world 只有垃圾收集器线程运行,其他线程处于安全点,也就是在内核态。垃圾收集器处于用户态。
      • ParNew 响应优先 Serial多线程版本
      • Parallel Scavenge 吞吐优先
        • 控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数,降低停顿时间会降低吞吐量
        • 吞吐量大小的-XX:GCTimeRatio参数,提升吞吐量会加长停顿时间
    • 老年代(标记整理)

      • Serial Old 单线程
      • CMS 响应优先 (多线程并发标记清除)
        • 标记和清除过程
          • 初始标记:停顿,看GCroot关联到的对象
          • 并发标记:不停顿,标记
          • 重新标记:停顿,重新标记
          • 并发清除:不停顿
        • 缺点
          • 占用资源,默认启用(CPU核+3)/4个线程并发标记。
          • 无法处理浮动垃圾。在并发清除阶段产生的垃圾无法处理。
          • 标记清除算法:产生空间碎片问题。
      • Parallel Old 吞吐优先
    • 通用

      • G1
        • 堆内存布局原理(不固定大小,不固定分代)
          • 把java堆分成多个相等的区域region,每个区域都可以根据需要扮演eden,s0,s1,老年代区域
          • 区域大小可通过参数设置,在1M~32M之间
          • 超过区域一半的大小,判定为大对象 ,大对象会被放在特殊区域,成为humugers 区域
          • 如果对象64M,区域大小32M,会分配2个连续的区域存放对象
          • 目标:建立一个可预测停顿时间的模型;
            1. 会根据配置的停顿时间,决定垃圾回收区域的数量
        • 如果解决跨代引用的问题?
          1. 使用记忆集,使用双向卡表
        • 标记和清除过程
          1. 初始标记:停顿,看GCroot关联到的对象
          2. 并发标记
          3. 最终标记:停顿
          4. 筛选回收,根据用户设置的最大停顿时间,计算回收的区域
  • stop the world

    • 根节点枚举:垃圾收集标记阶段,需要寻找GC ROOT,都需要stop the world,否则一边标记,一边产生新垃圾,永远处理不完,用户线程停止的点在jvm安全点上。
    • jvm安全点:垃圾收集过程中,用户线程达到特定位置,不再创建新线程。常见比如有:循环中,异常点,方法区清除。
      • 方法调用过程:类寻址(直接,句柄)-》堆对象位置-》常量池constants_class_info→constants_utf8_info→权限名(方法区入口)-》方法区 运行时动态class数据结构-》class方法表,属性表,code(方法入口)

调优经验

  • 上线前压测,监督内存空间的使用,gc的频率和停顿时间
    • 如果minor gc频繁
      • 调大年轻代或者region大小
    • 如果minor gc频繁,且容易引发full gc
      • 如果minor gc存活对象大于s1,直接进入老年代
        • 合理调整eden区和s区
      • 如果S1区的对象,相同年龄的对象所占总空间大小>s1区空间大小的一半,直接进入老年代
        • 合理调整eden区和s区
    • 大对象创建频繁,导致full gc频繁
      • 拆分大对象
      • 调大jvm参数-XX:PretenureSizeThreshold,使大对象在年轻代创建
    • MGC 与 FGC 停顿时间长导致影响用户体验
      • gc真实回收过程时间长,即real time时间长
        • 降低堆内存:单机部署、分布式集群
      • gc真实回收时间 real time 并不长,但是user time和sys time时间长
        • 用户线程到达不了安全点:http请求等待响应,设置的超时时间比较长
  • oom:
    • +HeapDumpOnOutOfMemoryError dump堆内存heap文件,用virtual vm\jhat观察
  • 调优工具
    • jps 查看java执行程序
    • jstat 监视类加载、内存、垃圾收集、即时编译等运行时数据
    • jinfo 是实时查看和调整虚拟机各项参数
    • jmap 生成堆转储快照
    • jhat 分析堆转储快照
    • jstack 生成虚拟机当前时刻的线程快照
posted @ 2024-03-14 20:07  Faetbwac  阅读(27)  评论(0编辑  收藏  举报