大师级GC调优:剖析高并发系统的垃圾回收优化实战

news2024/11/28 6:36:52

前期准备

大家开始前一定要对VisualVM、Jmeter这两款工具有所了解!

1. 下载安装VisualVM,以便后续调优分析。JDK1.8及之前所在目录的bin目录下有自带的VisualVM,JDK1.8以后需要自行手动安装下载。

下载地址:https://visualvm.github.io/download.html
IDEA插件配置:在Plugins里搜索visualVM Launcher即可。(也可以不用配置,直接下载客户端软件)后续只要在配置下载安装好的VisualVM程序地址即可,这样就能直接在IDEA中根据指定的类启动VisualVM了,不需要在独立的VisualVM里找指定路径装配。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 代码使用JDK1.8,VM参数在示例代码中自带。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 需要大家对Jmeter压测工具有所了解

下载链接:https://jmeter.apache.org/download_jmeter.cgi

在这里插入图片描述


调优步骤

一般的调优步骤大体上分为以下几个部分:

  1. 熟悉业务场景
  2. (发现问题)性能监控:
    GC 频繁、CPU load过高、OOM、内存泄漏、死锁、程序响应时间较长等。
  3. (排查问题)性能分析:
    打印GC日志,通过GCviewer或者http://gceasy.io来分析日志信息;运用命令行工具,jstack,jmap,jinfo等;dump出堆文件,使用内存分析工具分析文件使用阿里Arthas,或jconsole,VisuaIVM来实时查看JVM状态;jstack查看堆栈信息等等。
  4. (解决问题)性能调优:
    适当增加内存,根据业务背景选择垃圾回收器;优化代码,控制内存使用;增加机器,分散节点压力合理设置线程池线程数量;使用中间件提高程序效率,比如缓存,消息队列等等。

优化案例一:逃逸分析之栈上分配、标量替换、锁清除

堆,是分配对象的唯一选择吗?

在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

  • 只有对频繁执行的代码(热点代码),JIT才能保证有正面的收益。

  • 没有循环,对只执行一次的代码做JIT编译再执行,可以说是得不偿失。

  • 对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。

在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

代码优化:栈上分配

使用逃逸分析,编译器可以对代码做如下优化:

  • 栈上分配。将堆分配转化为栈分配。如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。可以减少垃圾回收时间和次数。
  • JIT编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无须进行垃圾回收了。

示例代码:

刚开始我们先默认关闭逃逸分析(-XX:-DoEscapeAnalysis),执行代码查看结果:

/**
 * 栈上分配测试
 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
 * 只要开启了逃逸分析,就会判断方法中的变量是否发生了逃逸。如果没有发生了逃逸,则会使用栈上分配
 *
 * @author shkstart  shkstart@126.com
 */
public class StackAllocation {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        // 查看执行时间
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
        // 为了方便查看堆内存中对象个数,线程sleep
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }

    private static void alloc() {
        User user = new User();//是否发生逃逸? 没有!
    }

    static class User {

    }
}

代码分析:

执行并启动VisualVM查看分析:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这里可以看到我们确实已经创建了1000万个线程对象,内存占用也不小。我们顺势打开逃逸分析(-XX:+DoEscapeAnalysis或者直接删掉这个命令也是可以的,JDK6以后系统默认是打开的)

在这里插入图片描述

查看栈上分配:

在这里插入图片描述

可以看到速度已经明显得到提升了,个位级别的耗时,很夸张。

在这里插入图片描述

代码优化:同步省略(消除)

同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

  • 线程同步的代价是相当高的,同步的后果是降低并发性和性能。
  • 在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除。

示例代码:

代码中对hollis这个对象进行加锁,但是hollis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉。

/**
 * 同步省略说明
 * @author shkstart  shkstart@126.com
 */
public class SynchronizedTest {
    public void f() {
        /*
        * 问题:字节码文件中会去掉hollis吗?
        * */
        Object hollis = new Object();
        synchronized(hollis) {
            System.out.println(hollis);
        }

        /*
        * 优化后:
        * Object hollis = new Object();
        * System.out.println(hollis);
        * */
    }
}

代码优化:标量替换

标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。

相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。

在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。

示例代码:

参数-XX:+EliminateAllocations 开启了标量替换(默认打开),允许将对象打散分配在栈上

package com.atguigu.escape;

/**
 * 标量替换测试
 *  -Xmx50m -Xms50m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:-EliminateAllocations
 * @author shkstart  shkstart@126.com
 */
public class ScalarReplace {
    public static class User {
        public int id;
        public String name;
    }

    public static void alloc() {
        User u = new User();//未发生逃逸
        u.id = 5;
        u.name = "www.atguigu.com";
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");

    }
}

代码分析:

未打开标量替换之后:(-XX:-EliminateAllocations)
在这里插入图片描述

打开标量替换之后:(-XX:+EliminateAllocations同理去掉这个参数也是可以的,系统默认会打开的)

在这里插入图片描述

通过对比,我们可以很轻易地看到:

  • 在没有开启标量替换的时候,系统运行程序花费时间长,而且新生代(PSYoungGen)总的内存空间使用很大,主要体现在eden区对象占有率达到了恐怖的77%。
  • 当我们开启标量替换的时候,系统运行程序花费时间短,这时我们所创建的对象都遵循栈上分配,导致新生代的eden区对象使用率减小,只有34%,新生代中对象内存使用也变小了。(新生代老年代都是堆空间的概念)

结论:Java中的逃逸分析,其实优化的点就在于对栈上分配的对象进行标量替换

逃逸分析结论

逃逸分析小结:逃逸分析并不成熟

  • 关于逃逸分析的论文在1999年就已经发表了,但直到JDK 1.6才有实现,而且这项技术到如今也并不是十分成熟的。

  • 其根本原因就是无法保证非逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析,这其实也是一个相对耗时的过程。

  • 一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的。那这个逃逸分析的过程就白白浪费掉了。

  • 虽然这项技术并不十分成熟,但是它也是即时编译器优化技术中一个十分重要的手段。


优化案例二:合理配置堆内存

推荐配置

我们说增加内存可以提高系统的性能而且效果显著,那么随之带来的一个问题就是,我们增加多少内存比较合适?

  • 如果内存过大,那么如果产生Full GC的时候,GC时间会相对比较长。
  • 如果内存较小,那么就会频繁的触发GC。

在这种情况下,我们该如何合理的适配堆内存大小呢?

依据的原则是根据Java Performance里面的推荐公式来进行设置。

  • Java整个堆大小设置,Xmx和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。

  • 方法区(永久代 PermSize和MaxPermSize 或 元空间 MetaspaceSize 和MaxMetaspaceSize)设置为老年代存活对象的1.2-1.5倍。

  • 年轻代Xmn的设置为老年代存活对象的1-1.5倍。

  • 老年代的内存大小设置为老年代存活对象的2-3倍。

但是,上面的说法也不是绝对的,也就是说这给的是一个参考值,根据多种调优之后得出的一个结论大家可以根据这个值来设置一下我们的初始化内存,在保证程序正常运行的情况下,我们还要去查看GC的回收率,GC停顿耗时,内存里的实际数据来判断,Full GC是基本上不能有的,如果有就要做内存Dump分析,然后再去做一个合理的内存分配。


但这里问题就来了,如何计算老年代存活对象大小呢?上述经验参考都是以老年代存活对象为基准的。

这里介绍两种方式:

  1. 第一种最稳妥的方式就是:JVM参数中添加GC日志,GC日志中会记录每次FuIGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天)的FuIIGC之后的内存情况,根据多次的FuGC之后的老年代的空间大小数据来预估FuIGC之后老年代的存活对象大小(可根据多次FuGC之后的内存大小取平均值)
  2. 方式1的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FuIIGC,所以日志中并没有记录FuIGC的日志。在分析的时候就比较难处理。所以有时候需要强制触发一次FuIIGC,来观察Full GC之后的老年代存活对象大小。会影响线上服务,慎用!
  • jmap -histo:live <pid> 打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量.此时会触发FuIGC
  • 在性能测试环境,可以通过]ava监控工具来触发FulIGC,比如使用VisualVMJConsoleVisualVM集成了JConsole,VisualVM或者JConsole上面有一个触发GC的按钮。

案例演示

现在我们通过idea启动springboot工程,我们将内存初始化为1024M。我们这里就从1024M的内存开始分析我们的GC日志,根据我们上面的一些知识来进行一个合理的内存设置。

案例链接:https://pan.baidu.com/s/1C8IMG4ZXrqjQdYb4B-Z5gg
提取码: syhh

JVM设置如下:

-XX:+PrintGCDetails 
-XX:MetaspaceSize=64m 
-Xss512K
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/heapdump3.hprof 
-XX:SurvivorRatio=8
-XX:+PrintGCDatestamps
-Xms1024M -Xmx1024M
-Xloggc:log/gc-oom3.log

启动程序:

在这里插入图片描述
要测试的代码其实就是连接数据库并查询记录:
在这里插入图片描述

在这里插入图片描述

观察Full GC的次数:

在这里插入图片描述

接着做压测,看看有没有出现Full GC值得优化:

在这里插入图片描述
在这里插入图片描述

回到idea继续查看Full GC的次数发现依旧为0,但是如果我们想知道使用多少空间必须经过Full GC后才能分析,所以我们可以使用强制命令来强制系统进行Full GC:

jmap -histo:live 53519(PID)

可以重复执行3次Full GC,然后我们就可以查看内存使用情况了:

jmap -heap 53519(PID)

在这里插入图片描述

发现使用的空间只占了20M差不多,如果我们需要减小设置的堆内存初始上限和最大上限,最佳配置应该在x3—x4左右,也就是60M-80M左右,咱们差不多设置成80M即可。

在这里插入图片描述

重新启动程序,发现Full GC次数依旧为0,使用Jmeter进行压测50000条样例,发现Full GC依旧为0。这样我们就可以保证在系统不发生Full GC的情况下,最大化的利用当前堆内存,没有造成浪费,还提升了效率。


优化案例三:CPU占用很高的排查方案

示例代码:

/**
 * <pre>
 *    @author  : shkstart
 *    desc    : jstack 死锁案例
 *    version : v1.0
 * </pre>
 */
public class JstackDeadLockDemo {

    private final Object obj1 = new Object();
    private final Object obj2 = new Object();

    public static void main(String[] args) {
        new JstackDeadLockDemo().testDeadlock();
    }

    private void testDeadlock() {
        Thread t1 = new Thread(() -> calLock_Obj1_First());
        Thread t2 = new Thread(() -> calLock_Obj2_First());
        t1.start();
        t2.start();
    }

    /**
     * 先synchronized  obj1,再synchronized  obj2
     */
    private void calLock_Obj1_First() {
        synchronized (obj1) {
            sleep();
            System.out.println("已经拿到obj1的对象锁,接下来等待obj2的对象锁");
            synchronized (obj2) {
                sleep();
            }
        }
    }

    /**
     * 先synchronized  obj2,再synchronized  obj1
     */
    private void calLock_Obj2_First() {
        synchronized (obj2) {
            sleep();
            System.out.println("已经拿到obj2的对象锁,接下来等待obj1的对象锁");
            synchronized (obj1) {
                sleep();
            }
        }
    }

    /**
     * 为了便于让两个线程分别锁住其中一个对象,
     * 一个线程锁住obj1,然后一直等待obj2,
     * 另一个线程锁住obj2,然后一直等待obj1,
     * 然后就是一直等待,死锁产生
     */
    private void sleep() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

问题分析

那么如果是生产环境的话,是怎么样才能发现目前程序有问题呢?我们可以推导一下,如果线程死锁,那么线程一直在占用CPU,这样就会导致CPU一直处于一个比较高的占用率。所示我们解决问题的思路应该是:

1、首先使用jps命令查看Java进程ID
2、根据进程 ID 检查当前使用异常线程的pidtop -Hp <PID>
3、把线程pid变为16进制如 31695 -> 7bcf然后得到0x7bcf
4、jstack < 线程的pid > | grep -A20 0x7bcf 得到相关进程的代码(鉴于我们当前代码量比较小,线程也比较少,所以我们就把所有的信息全部导出来)

接下来是我们的实现上面逻辑的步骤,如下所示:

# 查看所有Java进程ID
jsp -l

在这里插入图片描述

# 根据进程ID检查当前使用异常线程的PID
top -Hp <PID>

在这里插入图片描述

然后我们就可以通过十六进制转换,把< PID >转成十六进制,继续执行:

jstack <线程的PID> | grep -A20 0x7f03

就可以找到出现问题的具体信息:
在这里插入图片描述

当然了,如果命令行对你来说有点繁琐的话,我们也可以直接使用VisualVM直接去分析CPU使用情况:

在这里插入图片描述
可以看到线程报告那边直接爆红了,Thread0 和 Thread1停止工作了(Running直接为0ms了),提示我们发生了死锁,我们看下线程dump文件分析::

在这里插入图片描述

在这里插入图片描述

解决方案

  1. 调整锁的顺序,保持一致
  2. 采用定时锁,一段时间后,如果还不能获取到锁就释放锁

优化案例四:G1并发GC线程数对性能的影响

示例代码:

/**
 * @author Juechen
 * @version : GCTest.java
 */
public class GCTestApp {
    private static final int COUNT = 100000;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Starting object creation...");
        long start = System.currentTimeMillis();
        for (int i = 0; i < COUNT; i++) {
            new DummyObject();
        }
        long end = System.currentTimeMillis();
        System.out.println("Object creation took " + (end - start) + " ms");

        System.out.println("Starting workload...");
        start = System.currentTimeMillis();
        Thread.sleep(1000); // 模拟一些处理时间
        end = System.currentTimeMillis();
        System.out.println("Workload took " + (end - start) + " ms");
    }

    static class DummyObject {
        private byte[] placeholder = new byte[1024 * 1024]; // 占位符数据,模拟较大的对象
    }
}

参数配置

后面调整JVM参数的ConcGCThreads即可,默认为2个线程,我们分别用1个、4个、8个线程(G1并发线程最多不超过8个)设置做比对,比较创建对象的时间。

-Xms20m -Xmx20m 
-XX:+UseG1GC 
-XX:ConcGCThreads=1

案例分析:

  • 当-XX:ConcGCThreads=1时,耗时如下(等待消耗时间不计):
    在这里插入图片描述

  • 当-XX:ConcGCThreads=4时,耗时如下(等待消耗时间不计):
    在这里插入图片描述

  • 当-XX:ConcGCThreads=8时,耗时如下(等待消耗时间不计):
    在这里插入图片描述

通过比较,我们可以得出结论,增加并发线程数有助于减少 GC 的次数和总耗时,从而提高应用的整体性能。由于现在模拟的是简单的创建对象,在高并发场景中对比耗时会更加夸张!


优化案例五:调整垃圾回收器对吞吐量的影响

1)使用 Serial GC

java -XX:+UseSerialGC 

2)使用 Parallel GC

java -XX:+UseParallelGC

3)使用 CMS GC

java -XX:+UseConcMarkSweepGC 

4)使用 G1 GC

java -XX:+UseG1GC

5)使用 ZGC

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC

优化案例六:日均百万订单如何设置JVM参数

对于百万级的日订单量,可能需要较大的新生代来减少Full GC的频率。

  • 可以设置 -Xms 和 -Xmx 参数来确保启动时的最小堆内存与最大堆内存一致,以避免频繁的垃圾回收。
  • 对于高并发应用,推荐使用如ZGC(Z Garbage Collector)等低延迟垃圾回收器或者G1(Garbage First)等高吞吐量垃圾回收器。
  • 通过调整参数来控制垃圾回收的频率。比如使用 -XX:MaxGCPauseMillis 指定一个期望的最大停顿时间。
  • 此外,可以使用 -XX:NewRatio 来设置新生代与老年代的比例,从而间接控制新生代的大小。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模型的深度优化

文章目录 一、测试模型是否正确二、图形打印直观观察三、保存训练模型四、正确率&#xff08;仅使用于分类问题&#xff09; 一、测试模型是否正确 本文承接我的上一篇文章完整网络模型训练&#xff08;一&#xff09; 运用测试数据集&#xff08;test_dataloader&#xff09;…

第二十一章 (动态内存管理)

1. 为什么要有动态内存分配 2. malloc和free 3. calloc和realloc 4. 常⻅的动态内存的错误 5. 动态内存经典笔试题分析 6. 总结C/C中程序内存区域划分 1.为什么要有动态内存管理 我们目前已经掌握的内存开辟方式有 int main() {int num 0; //开辟4个字节int arr[10] …

全局安装cnpm并设置其使用淘宝镜像的仓库地址(地址最新版)

npm、cnpm和pnpm基本概念 首先介绍一下npm和cnpm是什么&#xff0c;顺便说一下pnpm。 npm npm&#xff08;Node Package Manager&#xff09;是Node.js的默认包管理器&#xff0c;用于安装、管理和分享JavaScript代码包。它是全球最大的开源库生态系统之一&#xff0c;提供了数…

centos环境安装JDK详细教程

centos环境安装JDK详细教程 一、前期准备二、JDK安装2.1 rpm方式安装JDK2.2 zip方式安装JDK2.3 yum方式安装JDK 本文主要说明CentOS下JDK的安装过程。JDK的安装有三种方式&#xff0c;用户可根据实际情况选择&#xff1a; 一、前期准备 查看服务器操作系统型号&#xff0c;执…

YOLOv7改进之主干DAMOYOLO结构,结合 CReToNeXt 结构,打造高性能检测器

一、DAMOYOLO理论部分 论文地址:2211.15444 (arxiv.org) 在本报告中,我们提出了一种快速准确的对象检测方法,称为 DAMO-YOLO,它实现了比最先进的 YOLO 系列更高的性能。DAMO-YOLO 是从 YOLO 扩展而来的,具有一些新技术,包括神经架构搜索 (NAS)、高效的重新参数化广义 …

【Linux】用虚拟机配置Ubuntu 24.04.1 LTS环境

目录 1.虚拟机安装Ubuntu系统 2.Ubuntu系统的网络配置 3.特别声明 首先我们先要下载VMware软件&#xff0c;大家自己去下啊&#xff01; 1.虚拟机安装Ubuntu系统 我们进去之后点击创建新的虚拟机&#xff0c;然后选择自定义 接着点下一步 再点下一步 进入这个界面之后&…

[C++]使用纯opencv部署yolov11目标检测onnx模型

yolov11官方框架&#xff1a;https://github.com/ultralytics/ultralytics 【算法介绍】 在C中使用纯OpenCV部署YOLOv11进行目标检测是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTor…

Python+Matplotlib展示单射、双射、满射

import matplotlib.pyplot as pltplt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号def create_function_plot(ax, title, x, y, connections):ax.set_xlim(0, 5)ax.set_ylim(0, 5)ax.set_aspect…

UE4 材质学习笔记01(什么是着色器/PBR基础)

1.什么是shader 着色器是控制屏幕上每个像素颜色的代码&#xff0c;这些代码通常在图形处理器上运行。 现如今游戏引擎使用先进的基于物理的渲染和照明。而且照明模型模型大多数是被锁定的。 因此我们创建着色器可以控制颜色&#xff0c;法线&#xff0c;粗糙度&#xff0c;…

十一假期不停歇-学习ROS第二天

一、自动修改环境变量 source install/setup.bash 这句指令可不重复添加环境变量&#xff0c;直接运行就可以。 从左右图即可即可看出有所不同的。以后写完功能包发现不存在那就source一下就好了。 实际上它运行的是 python3.10那里的功能包与节点 我们在创建完功能包会出…

python-矩阵转置/将列表分割成块/和超过N的最短子数组

一&#xff1a;矩阵转置 题目描述 输入一个 n 行 m 列的矩阵 A&#xff0c;输出它的转置 AT。输入 第一行包含两个整数 n 和 m&#xff0c;表示矩阵 A 的行数和列数。1≤n≤100&#xff0c;1≤m≤100。接下来 n 行&#xff0c;每行 m 个整数&#xff0c;表示矩阵 A 的元素。相邻…

别再为开题报告发愁了,ChatGPT可以助力你高效完成!

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 面对开题报告时&#xff0c;我们常常感到迷茫和压力。如何清晰地表达研究目的、梳理文献综述、明确研究问题与假设&#xff0c;以及构建合理的研究方法&#xff0c;这些环节都对开题报告…

OpenCV-图像拼接

文章目录 一、基本原理二、步骤三、代码实现1.定义函数2.读取图像3.图像配准&#xff08;1&#xff09;.特征点检测&#xff08;2&#xff09;.特征匹配 4.透视变换5.图像拼接 四、图像拼接的注意事项 图像拼接是一种将多张有重叠部分的图像合并成一张无缝的全景图或高分辨率图…

蓝桥杯【物联网】零基础到国奖之路:十七. 扩展模块之单路ADC和NE555

蓝桥杯【物联网】零基础到国奖之路:十七. 扩展模块之单路ADC和NE555 第一节 硬件解读第二节 CubeMx配置第三节 代码1&#xff0c;脉冲部分代码2&#xff0c;ADC部分代码![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57531a4ee76d46daa227ae0a52993191.png) 第一节 …

基于工业物联网的能源监控系统:边缘数据处理的应用

论文标题&#xff1a;《Industrial IoT-Based Energy Monitoring System: Using Data Processing at Edge》 作者信息&#xff1a; Akseer Ali MiraniAnshul AwasthiNiall O’MahonyJoseph Walsh 他们均来自爱尔兰的芒斯特技术大学IMaR研究中心&#xff0c;以及位于利默里克的…

秋招校招北森笔试测评北森笔测评常见图推题目解答

北森笔试测评常见题目解析第二弹&#xff1a; P1&#xff1a;每一行均出现圆&#xff0c;米&#xff0c;口加上内部的菱形和小圆形&#xff0c;行测上称之为遍历。 P2&#xff1a;题干也给提示了&#xff0c;肯定不选9&#xff0c;考虑封闭部分&#xff0c;选11. P3&#xf…

面试速通宝典——7

150. 数据库连接池的作用 数据库连接池的作用包括以下几个方面&#xff1a; 资源重用&#xff1a;连接池允许多个客户端共享有限的数据库连接&#xff0c;减少频繁创建和销毁连接的开销&#xff0c;从而提高资源的利用率。 统一的连接管理&#xff1a;连接池集中管理数据库连…

访问webapps下边的内容不能访问解决办法

1、看是否是带有中文路径&#xff0c;中文路径访问不了 2、是否在访问的时候没有带8080端口 如图&#xff0c;带有8080就可以访问了 原因&#xff1a; 当你尝试通过 http://localhost:8080/hello/hello.html 访问网页时&#xff0c;端口号 “8080” 指定了该请求将发送到本地主…

STM32-MPU6050+DAM库源码(江协笔记)

目录 1、MPU6050简介 2、MPU6050参数 3、MPU6050硬件电路 4、MPU6050结构 5、MPU6000和MPU6050的区别 6、MPU6050应用场景 7、MPU6050电气参数 8、MPU6050时钟源选择 9、MPU6050中断源 10、MPU6050的I2C读写操作 11、DMP库移植 1、MPU6050简介 10轴传感器&#xff1…

使用CSS实现酷炫加载

使用CSS实现酷炫加载 效果展示 整体页面布局 <div class"container"></div>使用JavaScript添加loading加载动画的元素 document.addEventListener("DOMContentLoaded", () > {let container document.querySelector(".container&q…