51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

JVM和JVM调优 学习

JVM调优学习 {#jvm调优学习}

JVM的主要组成部分 {#jvm的主要组成部分}

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

image-20231005193033202

类加载子系统(JVM类加载子系统) {#类加载子系统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类加载器有哪些 {#jvm类加载器有哪些}

image-20231005195209618

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

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

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

    这两种是有Java语言实现的

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

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

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

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

最后都由启动类加载器加载 java1.9双亲委派机制出现了变化

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

image-20231005193052291

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

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

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

公有部分 {#公有部分}

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

    空间分配 8:1:1

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

私有部分 {#私有部分}

程序计数器(PC寄存器) {#程序计数器pc寄存器}

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

虚拟机栈 {#虚拟机栈}
本地方法栈 {#本地方法栈}

Java堆很重要

执行引擎 {#执行引擎}

image-20231005193327769

  • 解释器
  • JIT(编译器)

JVM垃圾回收机制 {#jvm垃圾回收机制}

JVM垃圾回收算法 {#jvm垃圾回收算法}

image-20231006195017828

引用计数器 {#引用计数器}

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

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

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

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

GCRoot {#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调优}

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

image-20231006204550879

调优步骤 {#调优步骤}

性能监控 {#性能监控}

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

性能分析 {#性能分析}

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

性能调优 {#性能调优}

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

JDK自带的调优工具 {#jdk自带的调优工具}

jps {#jps}

jps

image-20231006210625437

jps -v

image-20231006210657452

jps -l

image-20231006210729068

jstat {#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}

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的问题 {#生产环境中出现oom的问题}

之后再补

生产环境中出现CPU飙高问题 {#生产环境中出现cpu飙高问题}

之后再补

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

赞(0)
未经允许不得转载:工具盒子 » JVM和JVM调优 学习