JVM 实战篇(一万字)

此笔记来至于 黑马程序员

image-20241011150518298

内存调优

内存溢出和内存泄漏

  • 内存泄漏(memory leak):在Java中如果不再使用一个对象,但是该对象依然在 GC ROOT 的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称之为内存泄漏。
  • 内存泄漏绝大多数情况都是由堆内存泄漏引起的,所以后续没有特别说明则讨论的都是堆内存泄漏。
  • 少量的内存泄漏可以容忍,但是如果发生持续的内存泄漏,就像滚雪球雪球越滚越大,不管有多大的内存迟早会被消耗完,最终导致的结果就是内存溢出。但是产生内存溢出并不是只有内存泄漏这一种原因

image-20241011151333653

image-20241011152029780

  • 内存泄漏导致溢出的常见场景是大型的 Java后端应用 中,在处理用户的请求之后,没有及时将用户的数据删除。随着用户请求数量越来越多,内存泄漏的对象占满了堆内存最终导致内存溢出。
  • 这种产生的内存溢出会直接导致用户请求无法处理,影响用户的正常使用。重启可以恢复应用使用,但是在运行一段时间之后依然会出现内存溢出
  • 第二种常见场景是 分布式任务调度系统如 Elastic-job、Quartz 等进行任务调度时,被调度的 Java应用 在调度任务结束中出现了内存泄漏,最终导致多次调度之后内存溢出。
  • 这种产生的内存溢出会导致应用执行下次的调度任务执行**。同样重启可以恢复应用使用,但是在调度执行一段时间之后依然会出现内存溢出。**

解决内存溢出的方法

image-20241011161821552

发现问题 - Top 命令
  • top命令 是 linux 下用来查看系统信息的一个命令,它提供给我们去实时地去查看系统的资源,比如执行时的进程、线程和系统参数等信息。
  • 进程使用的内存为 RES(常驻内存)- SHR(共享内存)

image-20241011162105848

发现问题 - VisualVM
  • VisualVM是多功能合一的 Java故障排除工具 并且他是一款 可视化工具,整合了 命令行JDK工具 和 轻量级分析功能,功能非常强大。
  • 这款软件在 Oracle JDK 6~8 中发布,但是在 Oracle JDK 9 之后不在JDk安装目录下需要单独下载。
  • 下载地址:https://visualvm.github.io/
云服务器配置

image-20241011164325281

image-20241011164429775

只能用于测试环境,因为会有 STW 影响用户体验

发现问题- Arthas
  • Arthas 是一款线上监控诊断产品,通过全局视角实时查看 应用load、内存、gc、线程 的状态信息,**并能在不修改应用代码的情况下,对业务问题进行诊断,**包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

image-20241011164706550

image-20241011164912911

发现问题 - Prometheus + Grafana
  • Prometheus + Grafana 是企业中运维常用的监控方案,其中 Prometheus 用来采集系统或者应用的相关数据,同时具备告警功能。Grafana 可以将 Prometheus 采集到的数据以可视化的方式进行展示。
  • Java程序员 要学会如何读懂 Grafana 展示的 Java虚拟机 相关的参数。

image-20241011165939158

image-20241011171029053

发现问题 - 堆内存状况的对比

image-20241011171444574

产生内存溢出原因一:代码中的内存泄漏(压力测试)

image-20241011171534308

equals() 和 hashcode() 导致的内存泄漏

1、以 JDK8 为例,首先调用 hash方法 计算 key 的哈希值,hash方法 中会使用到 key的 hashcode方法。根据 hash方法 的结果决定存放的数组中位置。
2、如果没有元素,直接放入。如果有元素,先 判断key 是否相等,会用到 equals方法,如果 key相等,直接替换value;key不相等,走链表或者红黑树查找逻辑,其中也会使用equals比对是否相同。

1、hashCode 方法实现不正确,会导致相同 id的学生对象计算出来的 hash值 不同,可能会被分到不同的槽中。
2、equals 方法实现不正确,会 导致key在 比对时,即便学生对象的 id是相同的,也被认为是 不同的key。
3、长时间运行之后 HashMap中会 保存大量相同id的学生 数据。

image-20241011174855774

image-20241011172728196

解决方案:

1、在定义新实体时,始终重写 equals() 和 hashCode() 方法。
2、重写时一定要确定使用了唯一标识去区分不同的对象,比如用户的id等。
3、hashmap 使用时尽量使用编号 id 等数据作为 key,不要将整个实体类对象作为 key存放。

案例2:内部类引用外部类
  • 1、非静态的内部类默认会持有外部类,尽管代码上不再使用外部类,所以如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类。
  • 2、匿名内部类对象如果在非静态方法中被创建,会持有调用者对象,垃圾回收时无法回收调用者。
public class Outer {
    private byte[] bytes = new byte[1024 * 1024]; // 外部类持有数据
    private String name = "测试";
    class Inner { // 改为静态内部类就好了 static class Inner
        private String name;
        public Inner() {
            this.name = Outer.this.name;
        }
    }
    
    public static void main(String[] args) throws IOException, InterruptedException {
        System.in.read();
        int count = 0;
        ArrayList<Inner> inners = new ArrayList<>();
        while (true) {
            if (count++ % 100 == 0) {
                Thread.sleep(10);
            }
            inners.add(new Outer().new Inner());
        }
    }
}

1、这个案例中,使用内部类的原因是可以直接获取到外部类中的成员变量值,简化开发。如果不想持有外部类对象,应该使用静态内部类。
2、使用静态方法,可以避免匿名内部类持有调用者对象。

案例3:ThreadLocal 的使用

image-20241011185345312

import java.util.concurrent.*;

public class Demo5 {
    public static ThreadLocal<Object> threadLocal = new ThreadLocal<>():
    
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Integer.MAX_VALUE, Integer.MAX_VALUE,
                   keepAliveTime:0,TimeUnit.DAYS, new SynchronousQueue<>());
        
        int count = 0;
        while (true) {
            System.out.println(++count);
            
            threadPlloExecutor.execute(() -> {
                threadPoolExecutor.set(new byte[1024 * 1024]);
                // threadLocal.remove();  线程池一定要释放 threadPoolExecutor 
            });
            
        }
    }
}
案例4:String 的 intern 方法

image-20241011190129392

import java.util.ArrayList;
import java.util.List;

public class Demo6_2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            // String.valueOf(i++).intern(); // JDK1.6 perm gen 不会溢出
            list.add(String.valueOf(i++).intern()); // 溢出
        }
    }
}
案例5:通过静态字段保存对象

image-20241011191401993

import java.time.Duration;

public class CaffineDemo {
    public static void main(String[] args) throws InterruptedException {
        
        Cache<Object, Object> build = Caffeine.newBuilder()
            				.expireAfterWrite(Duration.ofMillis(100))
            				.build();
        int count = 0;
        while (true) {
            build.put(count++, new byte[1024 * 1024 * 10]);
            Thread.sleep(100L);
        }
    }
}

image-20241011192007708

产生内存溢出原因二:并发请求问题
  • 并发请求问题指的是用户通过发送请求 向 Java应用 获取数据,正常情况下 Java应用将数据返回之后,这部分数据就可以在内存中被释放掉。

  • 并发请求问题指的是用户通过发送请求向 Java应用获取数据,正常情况下 Java应用 将数据返回之后,这部分数据就可以在内存中被释放掉。**但是由于用户的并发请求量有可能很大,同时处理数据的时间很长,导致大量的数据存在于内存中,最终超过了内存的上限,导致内存溢出。**这类问题的处理思路和内存泄漏类似,首先要定位到对象产生的根源。

image-20241011192945175

使用 Jmeter 进行并发测试,发现内存溢出问题

image-20241011193030224

诊断-内存快照

  • 当堆内存溢出时,需要在堆内存溢出时将整个堆内存保存下来,生成内存快照(HeapProfile)文件

  • 使用 MAT打开 hprof文件,并选择内存泄漏检测功能,MAT 会自行根据内存快照中保存的数据分析内存泄源的根源。

  • 生成内存快照的 Java虚拟机 参数:
    -XX:+HeapDumpOnOutOfMemoryError:发生 OutOfMemoryError 错误时,自动生成 hprof 内存快照文件。
    -XX:HeapDumpPath = :指定 hprof文件 的输出路径。

MAT 内存泄漏检测的原理-支配树
  • MAT 提供了称为 **支配树(DominatorTree)**的对象图。支配树展示的是对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为 对象A 支配 对象B。

image-20241011202742562

MAT 内存泄漏检测的原理-深堆和浅堆

image-20241011211241696

image-20241011211333872

// -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=D:/jvm/heapdemo.hprof
public class HeapDemo {
    public static void main(String[] args) {
        TestClass a1 = new TestClass();
        TestClass a2 = new TestClass();
        TestClass a3 = new TestClass();
        
        
        String s1 = "itheima1";
        String s2 = "itheima2";
        String s3 = "itheima3";
        
        
        a1.list.add(s1);
        
        a2.list.add(s1);
        a2.list.add(s2);
        
        a3.list.add(s3);
        
        // System.out.print(ClassLayout.parseClass(TestClass.class).toPrintable());
        
        s1 = null;
        s2 = null;
        s3 = null;
        System.gc();
    }
}

image-20241011214825207

MAT 内存泄漏检测的原理

image-20241011215329321

导出运行中系统的内存快照并进行分析

image-20241011215756900

分析超大堆的内存快照
  • 在程序员开发用的机器内存范围之内的快照文件,直接使用 MAT 打开分析即可。但是经常会遇到服务器上的程序占用的内存达到 10G以上,开发机无法正常打开此类内存快照,此时需要下载服务器操作系统对应的 MAT。 下载地址: https://eclipse.dev/mat/downloads.php

image-20241011221408384

案例实战

image-20241011221724444

案例1-分页查询文章接口的内存溢出

image-20241011221918900

image-20241011221852061

image-20241011223246327

案例2- Mybatis 导致的内存溢出

image-20241011224532800

案例3 - 导出大文件内存溢出

image-20241011224719174

image-20241011230057521

案例4- ThreadLocal 使用时占用大量内存

image-20241011230854575

image-20241011230823686

案例5-文章内容审核接口的内存问题

image-20241011230935004

image-20241011231121950

存在问题:

1、线程池参数设置不当,会导致大量线程的创建或者队列中保存大量的数据。
2、任务没有持久化,一旦走线程池的拒绝策略或者服务宕机、服务器掉电等情况很有可能会丢失任务。

image-20241011231709485

image-20241011232008891

image-20241011232134014

诊断和解决问题 两种方案

image-20241011232252246

在线定位问题 -步骤

image-20241011232618809

1 在线定位问题 btrace

image-20241011233113483

官网:https://github.com/btraceio/btrace/releases/latest

2、内存溢出有哪几种产生的原因?

1、持续的内存泄漏:内存泄漏持续发生,不可被回收同时不再使用的内存越来越多就像滚雪球雪球越滚越大,最终内存被消耗完无法分配更多的内存取使用,导致内存溢出。
2、并发请求问题:用户通过发送请求向Java应用获取数据,正常情况下Java应用将数据返回之后,这部分数据就可以在内存中被释放掉。但是由于用户的并发请求量有可能很大,同时处理数据的时间很长,导致大量的数据存在于内存中,最终超过了内存的上限,导致内存溢出。

3、解决内存泄漏问题的方法是什么?

1、发现问题,,通过监控工具尽可能尽早地发现内存慢慢变大的现象。
2、诊断原因,通过分析内存快照或者在线分析方法调用过程,诊断问题产生的根源,定位到出现问题的源代码。
3、修复源代码中的问题,如代码bug、技术方案不合理、业务设计不合理等等。
4、在测试环境验证问题是否已经解决,最后发布上线。

GC 调优

GC调优 指的是对**垃圾回收(Garbage Collection)**进行调优。GC调优的主要目标是避免由垃圾回收引起程序性能下降。

GC调优的核心分成三部分:
1、通用 Jvm参数 的设置。
2、特定垃圾回收器的 Jvm参数的设置。
3、解决由频繁的 FULL GC 引起的程序性能问题。

GC调优没有没有唯一的标准答案,如何调优与硬件、程序本身、使用情况均有关系,重点学习调优的工具和方法。

GC调优的核心指标

所以判断 GC 是否需要调优,需要从三方面来考虑,与 GC算法 的评判标准类似:
1.吞吐量(Throughput)吞吐量分为业务吞吐量和垃圾回收吞吐量
业务吞吐量 指的在一段时间内,程序需要完成的业务数量。比如企业中对于吞吐量的要求可能会是这样的:

  • 支持用户每天生成10000笔订单
  • 在晚上8点到10点,支持用户查询50000条商品信息

GC调优 的方法

保证高吞吐量的常规手段有两条:

1、优化业务执行性能,减少单次业务的执行时间
2、优化垃圾回收吞吐量

image-20241011235212818

image-20241011235255462

image-20241011235403878

3.内存使用量

内存使用量指的是 Java应用 占用系统内存的最大值,一般通过 Jvm参数调整,在满足上述两个指标的前提下,这个值越小越好。

image-20241011235513666

发现问题页- jstat 工具
  • Jstat工具 是 JDK 自带的一款监控工具,可以提供各种垃圾回收、类加载、编译信息等不同的数据。
  • 使用方法为:jstat-gc 进程ID 每次统计的间隔 (毫秒) 统计次数

image-20241011235900009

发现问题- visualvm插 件
  • VisualVm 中提供了一款 visualTool 插件,实时监控 Java进程 的堆内存结构,堆内存变化趋势以及垃圾回收时间的变化趋势。同时还可以监控对象晋升的直方图。

image-20241012000022885

Prometheus + Grafana
  • Prometheus + Grafana 是企业中运维常用的监控方案,其中 Prometheus用来采集系统或者应用的相关数据,同时具备告警功能。Grafana 可以将 Prometheus 采集到的数据以可视化的方式进行展示。
  • Java程序员要学会如何读懂 Grafana展示 的 Java虚拟机 相关的参数。

image-20241012000336691

发现问题 -GC 日志
  • 通过 GC日志,可以更好的看到垃圾回收细节上的数据,同时也可以根据每款垃圾回收器的不同特点更好地发现存在的问题。
  • 使用方法(JDK 8及以下):-XX:+PrintGCDetails -Xloggc:文件名
  • 使用方法(JDK 9+):-Xlog:gc*:file=文件名

image-20241012001414831

发现问题 - GC Viewer
  • GC Viewer 是一个将 GC日志 转换成可视化图表的小工具

    github 地址: https://github.com/chewiebug/GcViewer

  • 使用方法:java-jar gcviewer_1.3.4.jar 日志文件.log

image-20241012001703938

发现问题 - GCeasy
  • GCeaSy是业界首款使用 AI机器学习技术 在线进行 GC分析 和 诊断的工具。定位内存泄漏、GC延迟高 的问题,提供 JVM参数优化建议,支持在线的可视化工具图表展示。

    官方网站:https://gceasy.io/

image-20241012002543353

发现问题 - 常见的 GC模式
一、正常情况

特点:呈现锯齿状,对象创建之后内存上升,一旦发生垃圾回收之后下降到底部,并且每次下降之后的内存大小接近,存留的对象较少

image-20241012094955440

二、缓存对象过多

特点:呈现锯齿状,对象创建之后内存上升,一旦发生垃圾回收之后下降到底部,并且每次下降之后的内存大小接近,处于比较高的位置。
问题产生原因:程序中保存了大量的缓存对象,导致 GC 之后无法释放,可以使用 MAT 或者 HeapHero等工具 进行分析内存占用的原因。

image-20241012095108175

三、内存泄漏

特点:呈现锯齿状,每次垃圾回收之后下降到的内存位置越来越高,最后由于垃圾回收无法释放空间导致对象无法分配产生 outOfMemory 的错误。
问题产生原因:程序中保存了大量的内存泄漏对象,导致 GC 之后无法释放,可以使用 MAT 或者 HeapHero等工具 进行分析是哪些对象产生了内存泄漏。

image-20241012095138087

四、持续的 Fu GC

特点:在某个时间点产生多次 FULL GC,CPU使用率 同时飙高,用户请求基本无法处理。一段时间之后恢复正常。问题产生原因:在该时间范围请求量激增,程序开始生成更多对象,同时 垃圾收集 无法跟上对象创建速率,导致持续地在进行 FULL GC。GC分析报告

image-20241012095207463

五、元空间不足导致的 FULL GC

特点:堆内存的大小并不是特别大,但是持续发生 FULL GC。问题产生原因:元空间大小不足,导致持续 FULL GC 回收元空间的数据。GC分析报告

image-20241012095921960

解决 GC 问题的手段

解决GC问题的手段中,前三种是比较推荐的手段,第四种仅在前三种无法解决时选用:

image-20241012100032580

解决问题 -优化基础 JVM参数
参数1:-Xmx 和-Xms

-Xmx 参数设置的是**最大堆内存,**但是由于程序是运行在服务器或者容器上,计算可用内存时,要将元空间、操作系统、其它软件占用的内存排除掉。

  • 案例:服务器内存 4G,操作系统+元空间最大值+其它软件占用 1.5G,-Xmx可以设置为 2g。最合理的设置方式应该是根据最大并发量估算服务器的配置,然后再根据服务器配置计算最大堆内存的值。

image-20241012100423088

参数1:-Xmx 和-Xms
  • -Xms用来设置初始堆大小,建议将-Xms设置的和-Xmx一样大,有以下几点好处:

    1. 运行时性能更好,堆的扩容是需要向操作系统申请内存的,这样会导致程序性能短期下降。
    2. 可用性问题,如果在扩容时其他程序正在使用大量内存,很容易因为操作系统内存不足分配失败。
    3. 启动速度更快,Oracle官方文档的原话:如果初始堆太小,Java应用程序启动会变得很慢,因为 JVM 被迫频繁执行垃圾收集,直到堆增长到更合理的大小。为了获得最佳启动性能,请将初始堆大小设置为与最大堆大小相同。
  • 参数2 :-XX:MaxMetaspaceSize 和 -XX:MetaspaceSize

    • -XX:MaxMetaspaceSize=值 参数指的是最大元空间大小,默认值比较大,如果出现元空间内存泄漏会让操作系统可用内存不可控,建议根据测试情况设置最大值,一般设置为 256m。
    • -XX:MetaspaceSize=值 参数指的是到达这个值之后会触发FULL GC(网上很多文章的初始元空间大小是错误的)后续什么时候再触发 JVM 会自行计算。如果设置为和 MaxMetaspaceSize 一样大,就不会 FULL GC,但是对象也无法回收

image-20241012101010384

参数3:-Xss虚拟机栈大小

如果我们不指定栈的大小,JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。
比如 Linu×x86 64位:1MB,如果不需要用到这么大的栈内存,完全可以将此值调小节省内存空间,合理值为256k - 1m之间。
使用:-Xss 256k

参数4:不建议手动设置的参数

由 于JVM底层 设计极为复杂,一个参数的调整也许让某个接口得益,但同样有可能影响其他更多接口。
-Xmn 年轻代的大小,默认值为整个堆的 1/3,可以根据峰值流量计算最大的年轻代大小,尽量让对象只存放在年轻代,不进入老年代。但是实际的场景中,接口的响应时间、创建对象的大小、程序内部还会有一些定时任务等不确定因素都会导致这个值的大小并不能仅凭计算得出,如果设置该值要进行大量的测试。G1垃圾回收器尽量不要设置该值,G1会动态调整年轻代的大小。

image-20241012101400184

-XX:SurvivorRatio 伊甸园区和幸存者区的大小比例,默认值为8。
-XX:MaxTenuringThreshold 最大晋升阈值,年龄大于此值之后,会进入老年代。另外 JVM 有动态年龄判断机制:将年龄从小到大的对象占据的空间加起来,如果大于 survivor 区域的 50%,然后把等于或大于该年龄的对象,放入到老年代。

image-20241012101641612

-XX:+DisableExplicitGC

禁止在代码中使用system·gc(), System·gc() 可能会引起FULL GC,在代码中尽量不要使用。使用 DisableExplicitGC参数 可以禁止使用 system.gc() 方法调用。

  • -XX:+HeapDumpOnOutOfMemoryError:发生 outofMemoryError错误 时,自动生成 hprof内存快照文件。
    -XX:HeapDumpPath= :指定hprof文件的输出路径。
打印 GC 日志

JDK8及之前:-XX:+PrintGCDetails -XX:+PrintGCDateStamps-Xloggc:文件路径
JDK9及之后:-Xlog:gc*:file=文件路径

image-20241012102056910

image-20241012102452337

解决问题优化垃圾回收器的参数

这部分优化效果未必出色,仅当前边的一些手动无效时才考虑

一个优化的案例:

CMS的 并发模式失败(concurrentmodefailure)现象。由于 CMS 的垃圾清理线程和用户线程是 并行进行的,如果在并发清理的过程中老年代的空间不足以容纳放入老年代的对象,会产生并发模式失败。

并发模式失败会导致 Java虚拟机 使用 Serial old单线程 进行 FULL GC 回收老年代,出现长时间的停顿。

image-20241012103938071

@RequestMapping("/fullgc")

public class Demo2Controller {
    
    private Cache chche = Caffeine.newBuilder().weakKeys().softValues().build();
    private List<Object> objs = nwe ArrayList<>();
    
    private static final int _1MB = 1024 * 1024;
    
    // FULLGC 测试
    // -Xms8g -Xmx8g -Xss256k -XX:MaxMetaspaceSize=512m -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test.hprof -verbose:gc -XX+PrintGCDetails -XX:PrintGCTimeStamps
    
    // ps + po 50并发 260ms 100并发 474 200并发930
    // cms -XX:UseParNewGC -XX:UseConcMarkSweepGC 50并发 157ms 200并发 833
    // g1 JDK11 并发200 248
    @GetMappin("/1")
    public void test() throws InterruptedException {
        cache.put(RandomStringUtils.randomAlphabetic(8), new byte[10 * _1MB]);
    }
}
解决方案:
  1. 减少对象的产生以及对象的晋升。
  2. 增加堆内存大小
  3. 优化垃圾回收器的参数,比如 -XX:CMSInitiatingOccupancyFraction=值,当老年代大小到达该阈值时,会自动进行 CMS垃圾回收,通过控制这个参数提前进行老年代的垃圾回收,减少其大小。
  4. JDK8中 默认这个参数值为 -1,根据其他几个参数计算出阈值:
    ((100 - MinHeapFreeRatio) + (double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)
  5. 该参数设置完是不会生效的,必须开启-XX:+UseCMSInitiatingOccupancyOnly参数。

image-20241012104852881

案例实战

image-20241012104951543

image-20241012111403812

image-20241012111725586

image-20241012111752959

image-20241012111922027

image-20241012112340006

image-20241012114431163

性能调优

应用程序在运行过程中经常会出现性能问题,比较常见的性能问题现象是:

  1. 通过 top命令 查看 CPU占用率高,接近100甚至 多核CPU 下超过100 都是有可能的。
  2. 请求单个服务处理时间特别长,多服务使用 skywalking 等监控系统来判断是哪一个环节性能低下。
  3. 程序启动之后运行正常,但是在运行一段时间之后无法处理任何的请求**(内存和GC正常)**

线程转储(Thread Dump)提供了对所有运行中的线程当前状态的快照。线程转储可以通过 jstack、visualvm 等工具获取。其中包含了线程名、优先级、线程ID、线程状态、线程栈信息等等内容,可以用来解决CPU占用率高、死锁等问题。

image-20241012115127955

线程转储者(Thread Dump)中的几个核心内容:

**名称:**线程名称,通过给线程设置合适的名称更容易“见名知意”
优先级(prio): 线程的优先级
JavaID(tid): JVM 中线程的 唯一ID
本地ID(nid): 操作系统 分配给线程的 唯一ID
**状态:**线程的状态,分为:
NEW -新创建的线程,尚未开始执行
RUNNABLE -正在运行或准备执行
BLOCKED - 等待获取监视器锁以进入或重新进入同步块/方法
WAITING -等待其他线程执行特定操作,没有时间限制
TIMED_WAITING -等待其他线程在指定时间内执行特定操作
TERMINATED -已完成执行

  • 栈追踪:显示整个方法的栈帧信息

    线程转储的可视化在线分析平台:
    1.https://jstack.review/
    2.https://fastthread.io/

案例1:CPU 占用率高问题的解决方案

image-20241012121841656

image-20241012121934850

image-20241012122010546

image-20241012122303010

image-20241012122336314

案例2:接口响应时间很长的问题

image-20241012122426168

Arthas 的 trace命令

使用 arthas 的 trace命令,可以展示出整个方法的调用路径以及每一个方法的执行耗时。
命令:trace 类名方法名

  • 添加–skipJDKMethod false 参数可以输出 JDk核心 包中的方法及耗时。
  • 添加‘#cost 〉毫秒值’参数,只会显示耗时超过该毫秒值的调用。
  • 添加 -n 数值参数,最多显示该数值条数的数据。
  • 所有监控都结束之后,输入 stop 结束监控,重置 arthas增强的对象。

image-20241012122629884

总结:

1、通过 arthas的 trace命令,首先找到性能较差的具体方法,如果访问量比较大,建议设置最小的耗时,精确的找到耗时比较高的调用。
2、通过 watch命令,查看此调用的参数和返回值,重点是参数,这样就可以在开发环境或者测试环境模拟类似的现象,通过 debug 找到具体的问题根源。
3、使用 stop命令 将所有增强的对象恢复。

案例3:定位偏底层的性能问题

image-20241012123340608

Arthas 的 profile 命令

使用 arthas 的 profile 命令,生成性能监控的火焰图。
命令1: profiler start 开始监控方法执行性能
命令2: profiler stop – format html 以 HTML 的方式生成火焰图
火焰图中一般找绿色部分Java中栈顶上比较平的部分,很可能就是性能的瓶颈。

image-20241012123955994

image-20241012124326069

性能调优解决的问题

应用程序在运行过程中经常会出现性能问题,比较常见的性能问题现象是:

1、通过 top命令 查看 CPU占用率高,接近 100甚至 多核CPU下 超过 100都是 有可能的。
2、请求单个服务处理时间特别长,多服务使用 skywalking等监控系统 来判断是哪一个环节性能低下。
3、程序启动之后运行正常,但是在运行一段时间之后无法处理任何的请求(内存和GC正常)。

案例4:线程被耗尽问题

image-20241012124557007

image-20241012124957199

image-20241012125114138

解决方案:

3、使用 fastthread 自动检测线程问题。https://fastthread.io/
Fastthread 和 Gceasy类似,是一款在线的Al自动线程问题检测工具,可以提供线程分析报告。通过报告查看是否存在死锁问题。

image-20241012125216702

性能调优的方法

image-20241012141003302

JIT 对程序性能的影响

Java 程序在运行过程中,JIT 即时编译器会实时对代码进行性能优化,所以仅凭少量的测试是无法真实反应运行系统最终给用户提供的性能。如下图,随着执行次数的增加,程序性能会逐渐优化。

image-20241012141142813

OpenJDK 中提供了一款叫 JMH(Java Microbenchmark Harness)的工具,可以准确地对 Java代码 进行基准测试,量化方法的执行性能。

官网地址:https://github.com/openjdk/jmh

JMH 会首先执行预热过程,确保 JIT对代码进行优化之后再进行真正的迭代测试,最后输出测试的结果。

image-20241012141446910

image-20241012141722857

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;

// 预热次数 时间
@Warmup(iterations = 5, time = 1)
// 启动多少个线程
@Fork(value=1, jvmArgsAppend= {"-Xms1g", "-Xmx1g"})
// 指定显示结果
@BenchamarkMode(Mode.AverageTime)
// 指定显示结果单位
@OutputTimeUnit(TimeUnit.NANOSECONDS)
// 变量共享范围
@State(Scope.Benchmark)
public class MyBenchmark {
    
    @Benchmark
    public void testMethod() {
        // place your benchmarked code here
        int i = 0;
        i++;
        return i;
    }
    // 在项目中测试时,尽量打包成 jar 包
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder();
        		.include(MyBenchmark.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(options).run();
    }
}
编写测试方法,几个需要注意的点:
  • 死代码问题

  • 黑洞的用法

  • 通过 maven 的 verify命令,检测代码问题并打包成 jar包。通过 java -jar target/benchmarks.jar 命令执行基准测试。

    测试结果通过 https://imh.morethan.io/ 生成可视化的结果。

案例:日期格式化方法性能测试

image-20241012143940299

总结:日期格式化方法性能测试

image-20241012143835919

案例实战

image-20241012151014091

image-20241012154017957

image-20241012154144670

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/893612.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Rust usize类型(用来表示地址的类型)Rust地址与指针的区别(Rust指针)

文章目录 Rust usize类型Rust地址与指针的区别&#xff08;指针有数据类型&#xff0c;而地址只是一个数字&#xff09;指针地址使用场景示例 Rust usize类型 在Rust中&#xff0c;地址通常表示为usize类型&#xff0c;这是因为usize是专门设计用来存储指针大小的无符号整型&a…

vue综合指南(五)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vuet篇专栏内容:vue综合指南 目录 81 简述每个周期具体适合哪些场景 82、Vue $forceUpdate的原理 83、vue获取数…

MySQL—关于数据库的CRUD—(增删改查)

文章目录 关于数据库的使用&#xff1a;1. 数据库的背景知识&#xff1a;2. MYSQL数据库软件的使用&#xff08;MYSQL安装的问题在另一篇博客中讲解&#xff09;。&#xff08;1&#xff09;启动MYSQL数据库软件&#xff08;2&#xff09;开始使用数据库程序&#xff1a;1&…

leetcode动态规划(一)-理论基础

本节主要参考&#xff1a;代码随想录 题目分类 动态规划释义 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 动态规划中每一个状态一定是由上一个状态推导出来…

车辆管理的SpringBoot技术革新

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了车辆管理系统的开发全过程。通过分析车辆管理系统管理的不足&#xff0c;创建了一个计算机管理车辆管理系统的方案。文章介绍了车辆管理系统的系统分析部分&…

使用 OpenWebUI 一键部署 Mistral-Large-Instruct-2407-AWQ

教程及模型简介 该教程是使用 OpenWebUI 一键部署 Mistral-Large-Instruct-2407-AWQ&#xff0c;相关环境和配置已经搭建完成&#xff0c;只需克隆启动容器即可进行推理体验。 Mistral-Large-Instruct-2407-AWQ 是法国人工智能公司 Mistral AI 发布的新一代旗舰 AI 模型&…

操作系统简介:作业管理

作业管理 一、作业管理1.1 作业控制1.2 作业的状态及其转换1.3 作业控制块和作业后备队列 二、作业调度2.1 调度算法的选择2.2 作业调度算法2.3 作业调度算法性能的衡量指标 三、人机界面 作业&#xff1a;系统为完成一个用户的计算任务&#xff08;或一次事务处理&#xff09;…

RabbitMQ 核心功能详解

引言 在现代分布式系统中&#xff0c;消息队列已经成为一种不可或缺的组件。它不仅能够实现应用之间的解耦&#xff0c;还能提高系统的灵活性和可扩展性。RabbitMQ 是一款基于 AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;协议的消息中间件&#xff0c;以其…

【人工智能】人工智能的10大算法详解(优缺点+实际案例)

人工智能&#xff08;AI&#xff09;是现代科技的重要领域&#xff0c;其中的算法是实现智能的核心。本文将介绍10种常见的人工智能算法&#xff0c;包括它们的原理、训练方法、优缺点及适用场景。 1. 线性回归&#xff08;Linear Regression&#xff09; 模型原理 线性回归…

2021年10月自考《软件开发工具》03173试题

目录 一.选择题 二.填空题 三.简答题 五.综合题 一.选择题 1.下列各项属于集成化开发工具的是 &#xff08;书中&#xff09;P96页 A.WORDSTAR B.FLOW C.Dictionary/3000 D.Visual Studio 2.软件工程的思想主要服务于 &#xff08;书中&#xff09;P84页面 A.用户 B.项目…

虚拟现实辅助工程技术在现代汽车制造中的重要性

虚拟现实辅助工程&#xff08;VR Aided Engineering&#xff09;&#xff0c;简称VAE&#xff0c;作为数字化转型的重要手段&#xff0c;在各行各业被越来越广泛的应用。随着汽车变得越来越复杂&#xff0c;虚拟现实辅助工程技术逐渐成为汽车行业产品开发过程中不可或缺的一部分…

Redis --- 第四讲 --- 常用数据结构 --- string类型

一、认识数据类型和编码方式 有序集合&#xff0c;相当于除了存储member之外&#xff0c;还需要存储一个score&#xff08;权重&#xff0c;分数&#xff09; Redis底层在实现上述数据结构的时候&#xff0c;会在源码层面&#xff0c;针对上述实现进行特定的优化&#xff0c;来…

3 机器学习之假设空间

归纳(induction)与演绎(deduction)是科学推理的两大基本手段。前者是从特殊到一般的“泛化”(generalization)过程&#xff0c;即从具体的事实归结出一般性规律&#xff1b;后者则是从一般到特殊的“特化”(specialization)过程&#xff0c;即从基础原理推演出具体状况。例如&a…

学习JAVA中的Spring MVC常用注解及三层架构,这一篇就够了

Spring Web MVC 一&#xff1a;什么是 Spring Web MVC&#xff1f;什么是Servlet呢&#xff1f;什么是Servlet API1.1 MVC 定义1.2 什么是Spring MVC ?1.3SpringBoot和SpringMVC的区别 二&#xff1a;Spring MVC中常用注解的使用2.1 RequestMapping:地址映射2.2 RequestBody:请…

Golang | Leetcode Golang题解之第476题数字的补数

题目&#xff1a; 题解&#xff1a; func findComplement(num int) int {highBit : 0for i : 1; i < 30; i {if num < 1<<i {break}highBit i}mask : 1<<(highBit1) - 1return num ^ mask }

大模型缺的脑子,终于在智能体上长好了

智能体是一种通用问题解决器&#xff0c;从软件工程的角度看来&#xff0c;智能体是一种基于大语言模型的&#xff0c;具备规划思考能力、记忆能力、使用工具函数的能力&#xff0c;能自主完成给定任务的计算机程序。 大模型拥有接受输入&#xff0c;分析推理&#xff0c;继而…

k8s备份恢复(velero)

velero简介 velero官网&#xff1a; https://velero.io/ velero-github&#xff1a; https://github.com/vmware-tanzu/velero velero的特性 备份可以按集群资源的子集&#xff0c;按命名空间、资源类型标签选择器进行过滤&#xff0c;从而为备份和恢复的内容提供高度的灵活…

【Linux】【Jenkins】后端maven项目打包教程-Linux版

本次安装版本&#xff1a;2.4 jenkins详细安装教程1、安装git环境2、安装mavne环境2.1 下载依赖2.2、解压、赋权2.2、配置环境变量2.3、验证安装 3、jenkins-插件下载3.1、进入jenkins-->系统管理3.2、进入系统管理-->插件管理3.3、下载两个插件&#xff08;如果之前下载…

创建GitHub仓库和Git更换远程仓库

文章为个人笔记&#xff0c;详情请看reference 创建 GitHub 创建好账号点击自己头像&#xff0c;出现下拉菜单&#xff0c;点击Your profile 创建成功如下 下载Git 绑定用户 设置ssh-key ssh-keygen -t rsa -C “xxxxxx163.com 之后一直en回车 C:\Users\Y\ .ssh id_rsa…

数据不裸奔:如何确保AI分析顾客数据时的隐私保护

在这个信息爆炸的时代&#xff0c;数据已成为最宝贵的资源之一。人工智能&#xff08;AI&#xff09;技术的发展&#xff0c;使得我们能够从海量数据中提取有价值的信息&#xff0c;为商业决策提供支持。然而&#xff0c;随着AI在数据分析领域的广泛应用&#xff0c;顾客隐私保…