JVM调优学习

JVM的主要组成部分

  • Class LoaderSub System (类加载子系统)
  • Runtime Data Areas (运行时内存)
  • Execution Engine (执行引擎)

image-20231005193033202

类加载子系统(JVM类加载子系统)

image-20231005193451166

java中,类型加载是在运行时完成的,虽然会增加性能开销,但却时java更加灵活。

java的动态拓展是基于运行时加载和运行时连接实现的

image-20231005193832292

加载、验证、准备、初始化、卸载一定是安顺序进行的

解析不一定,有可能是在初始化之后执行

  • 加载阶段:

    1. 通过全限定类名来获取定义此类的二级制字节流

    2. 将这个字节流所代表的静态存储结构转化为方法区

    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

  • 验证阶段:

    1. 文件格式验证
    2. 元数据验证
    3. 字节码验证
    4. 符号引用验证
  • 准备阶段:

    • 赋初始值

    • 比如:定义一个int a = 10 ,那么在准备阶段就是int a = 0

  • 解析阶段:

    • 将符号引用转换为直接引用
  • 初始化阶段:

    • 赋值
    • 执行类构造器
  • 使用阶段

  • 卸载阶段

    • 内存销毁

JVM类加载器有哪些

image-20231005195209618

一个类两次被同一个类加载器加载,这样生成的两个类才是相等的

  • 启动器加载类(C++)(虚拟机自身的)

    • 加载 jre/lib/*.jar
  • 其他加载器

    这两种是有Java语言实现的

    • Extension ClassLoader 扩展加载器
      • 加载jre/lib/ext/*.jar (拓展包)
    • Application ClassLoader 应用程序加载器/系统加载器(没有自定义,用户使用默认的类加载器)
      • 加载用户的类路径指定的类库

上面这个些就是类加载器的双亲委派机制

扩展类加载器的父类是启动类加载器

系统类加载器的父类是拓展类加载器

最后都由启动类加载器加载

java1.9双亲委派机制出现了变化

运行时数据区(虚拟机的内存结构)(Java内存结构)

image-20231005193052291

  • JVM内存
  • 本地内存
  • 虚拟机栈
  • 程序计数器
  • 本地方法栈

主要分为公有部分私有部分

    • 所有线程共享的部分
    • java堆、方法区、常量池
    • 线程私有的部分
    • 程序计数器(PC寄存器)、虚拟机栈、本地方法栈

公有部分

  • Java堆
    • 存放Java实例对象(几乎)
  • 方法区(1.7叫做永久代、1.8叫做元空间
  • 常量池
Java堆
  • 年轻代

    空间分配 8:1:1

    • 一等区(对象优先分配)
    • 幸存者1区(to区)
    • 幸存者0区(from区)
  • 老年代

私有部分

程序计数器(PC寄存器)

当前正在执行的方法的地址保存在这里

虚拟机栈
本地方法栈

Java堆很重要

执行引擎

image-20231005193327769

  • 解释器
  • JIT(编译器)

JVM垃圾回收机制

JVM垃圾回收算法

image-20231006195017828

引用计数器

引用计数器存在的问题(循环引用问题):

假如 a引用b,b引用c,c引用a

但是这三个对象都没有被其他对象所引用,然而他们被引用的计数都为1,不为0,

所以从思想上来看,他们都不会被回收

GCRoot

GCRoot集合中存的是活跃引用

这个集合是筛选出来的(包括Java类运行时常量池中的引用类型,Java类中引用的静态变量)

回收垃圾

  • 标记清除法
  • 复制算法
  • 标记压缩算法

JVM采用的分代算法:

不同的区域采用不同的垃圾回收算法

新生代采用复制算法,老年代采用标记清楚法和标记压缩算法

垃圾回收思想

  • 分代思想
  • 分区思想

垃圾回收器

image-20231006201424970
  • 串行回收器

    单线程回收垃圾

    • Serial
      • 复制算法
    • Serial Old
      • 标记压缩算法

    会触发SWT(垃圾回收的时候会挂起其他所有线程)

  • 并行回收器

    多线程回收垃圾

    • ParNew
      • 多线程的Serial(也会触发SWT)
      • 复制算法
    • ParallelScavenge(并行GC回收器)(注重吞吐量)
      • 复制算法
      • 也会SWT
      • 根据堆大小,吞吐量,停顿时间找到平衡点
    • Parallel Old(注重吞吐量)(java1.6才能使用)
      • 标记压缩算法
  • CMS(注重停顿时间)

    • 标记清楚算法

    • 多线程

      1. 初始标记
      2. 并发标记
      3. 预清理
      4. 重新标记

      初始标记和重新标记是独占资源的,其他阶段是可以和用户线程一起执行的

      SWT时间更短

  • G1回收器(1.7之后出现的回收器)

    image-20231006203148546

    • 分代算法
    • 工作流程
      1. 新生代GC
      2. 并发标记周期
      3. 混合回收
      4. FullGC(可能)

JVM调优

原则:大多数应用程序时不要JVM调优,大部分导致GC的原因是我们的代码导致的

image-20231006204550879

调优步骤

性能监控

  • GC频繁
  • CPU负载过高
  • OOM(Out Of Memory)
  • 内存泄漏
  • 死锁
  • 程序响应时间长

性能分析

  • GC日志
  • 工具
  • JPS。。。

性能调优

  • 硬件优化
  • 回收器选择
  • 优化代码
  • 合理设置线程参数
  • 中间件(缓存,消息队列等)

JDK自带的调优工具

jps

jps

image-20231006210625437

jps -v

image-20231006210657452

jps -l

image-20231006210729068

jstat

jstat -class 15348

image-20231006210907948

jstat -gc 15348

image-20231006210953807

统计说明:

  • S0C:年轻代中To Survivor 的容量(KB)
  • S1C:年轻代中from Survivor的容量(KB)
  • S0U:年轻代中的to Survivor目前已使用的空间(KB)
  • S1U:年轻代中的from Survivor目前已使用的空间(KB)
  • EC:年轻代中Eden的容量(KB)
  • EU:年轻代中Eden目前已使用的空间(KB)
  • OC:老年代的容量(KB)
  • OU:老年代目前已使用的空间(KB)
  • MC:Metaspace的容量(KB)
  • MU:Metaspace目前已使用的空间(KB)
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:从应用程序启动到采样时年轻代中gc次数
  • YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
  • FGC:从应用程序启动到采样时old代(全gc)gc次数
  • FGCT:从应用程序启动到采样时old代(全gc)gc所用时间
  • GCT:从应用程序启动到采样时gc用的总时间(s)

jstack

jstack 3148

可以看到死锁

image-20231006213844300

代码:

public class Test {

    Object o1 = new Object();
    Object o2 = new Object();

    public void thread1() throws InterruptedException {
        synchronized (o1) {
            Thread.sleep(500);
            synchronized (o2) {
                System.out.println("线程1拿到两把锁");
            }
        }
    }

    public void thread2() throws InterruptedException {
        synchronized (o2) {
            Thread.sleep(500);
            synchronized (o1) {
                System.out.println("线程2拿到两把锁");
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();

        new Thread(
                () -> {
                    try {
                        test.thread1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        ).start();

        new Thread(
                () -> {
                    try {
                        test.thread2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        ).start();
    }

}

生产环境中出现OOM的问题

之后再补

生产环境中出现CPU飙高问题

之后再补

本文部分图片和笔记来自:https://www.bilibili.com/video/BV1QG4y1P7kL