JVM学习笔记

JVM

学习概览:

  1. 基础篇
    • 初识JVM
    • 字节码文件详解
    • Java 内存结构
    • 垃圾回收
    • 类的生命周期和类加载器
  2. 实战篇
    • 内存泄漏
      • 概念学习
      • 监控工具
      • 产生原因
      • 线上实战
    • GC 调优
    • 性能调优
      • JMH 性能测试
      • 线上实战
  3. 高级篇
    • 即时编译器 JIT
    • ASM 字节码增强
    • Java Agent 技术
    • ZGC
    • GraalVM
      • 功能介绍
      • 入门案例
      • Spring Native
      • Web 应用
  4. 原理篇
    • Java 对象布局
    • 异常、反射原理
    • G1 垃圾回收器工作原理
    • 编译器优化
      • 方法内联
      • 逃逸分析
  5. 面试题
    • 常见的 JVM 参数
    • Tomcat 的类加载器
    • ThreadLocal 会内存泄露吗?
    • 如何优化减少 Full GC
    • 三色标记法是什么?

初识 JVM

JVM(Java Virtual Machine): Java虚拟机

JVM 的功能

解释和运行: 对字节码文件中的指令,实时的解释成机器码,让计算机执行

内存管理:

  • 自动为对象、方法等分配内存
  • 自动的垃圾回收机制,回收不再使用的对象

即时编译(JIT)

  • 对热点代码进行优化,提升执行效率
  • Java 语言如果不做任何优化,性能不如C、C++等语言
  • 目的是为了跨平台

常见的 JVM

image-20240529094548345

字节码文件详解

JVM组成

image-20240529095508295

字节码文件的组成

以正确姿势打开文件

  • 字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。

通过NotePad++ 使用十六进制插件查看class文件:

左边为地址,中间为十六进制数据,右边为编码

image-20240529095935829

推荐使用jclasslib工具查看字节码文件。

字节码文件组成

基础信息:魔数、字节码文件对应的 Java 版本号访问标识(public final等等)、父类和接口

常量池:保存了字符串常量、类或接口名、字段名主要在字节码指令中使用

字段:当前类或接口声明的字段信息

方法:当前类或接口声明的方法信息转换成字节码指令

属性:类的属性,比如源码的文件名、内部类的列表等

image-20240529102734415

魔数:

每个字节码文件开头的4位都是指定的,称为魔数

image-20240529102946836

魔数的作用:

  • 文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容
  • 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。

常见的文件类型:

image-20240529103207777

主副版本号

  • 主副版本号指的是编译字节码文件的 JDK 版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2 是 46 之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号
  • 1.2之后大版本号计算方法就是:主版本号 - 44
  • 版本号的作用主要是判断当前字节码的版本和运行时的 JDK 是否兼容

image-20240529103715970

常量池

  • 字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。

解释:

image-20240529104949709

在常量池中,一份字面量只存储一份,可是现在有字段名和字面量相同,在常量池中如何存储呢。

  • 将abc字面量存入常量池中,并在常量池中存储一份String类型的字面量,引用到abc的字面量,a1和a2再引用这份String类型的字面量。
  • 而变量abc的字段名是直接引用abc字面量,这样就节省了空间。
image-20240529105319526
  • 常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速找到对应的数据。
  • 字节码指令中通过编号引用到常量池的过程称之为==符号引用==

image-20240529105507030

方法

局部变量表

  • 数组为0的位置存储的函数参数args,根据声明顺序生成局部变量表下标

image-20240529110059147

  • iconst_0 将常量 0 放入操作数栈

  • istore_1 将操作数栈的数据取出放在局部变量表数组中下标为1的位置(弹出,操作数栈的数据销毁)

  • iload_1 将局部变量表数组中下标为1的位置的数据放入操作数栈(复制,局部变量表的数据保留)

  • iadd 将操作数栈中最上面的两个值相加,并将结果存入操作数栈中

image-20240529110956718

从字节码文件的层面解释 i = i++:

将i++翻译为字节码文件之后,语句为:

  • iinc 1 by 1:表示把局部变量表中下标为1的位置的数据加 1,并且此操作不进入操作数栈,是直接在局部变量表中完成。

所以最初将0放入了操作数栈,然后存入局部变量表,然后局部变量表下标为 1 的数据 0 加 1,变成了 1,然后 istore_1 将操作数栈中的 0 存入 局部变量表中下标为 1 的位置,所以结果为 0

image-20240529111535071

++i 同理:

image-20240529111632503

Arthas

用法见官网 ->>> Arthas官方文档

类的生命周期

image-20240530134140680

加载阶段

  • 第一步是==类加载器==根据类的全限定名通过不同的渠道以二进制的方式获取字节码信息。==程序员可以使用Java代码拓展的不同的渠道==。

image-20240530134516063

控制开发者访问数据的范围

image-20240530134739135