Android | 关于 OOM 的那些事

news2024/11/23 19:52:04

前言

Android 系统对每个app都会有一个最大的内存限制,如果超出这个限制,就会抛出 OOM,也就是Out Of Memory 。本质上是抛出的一个异常,一般是在内存超出限制之后抛出的。最为常见的 OOM 就是内存泄露(大量的对象无法被释放)导致的 OOM,或者说是需要的内存大小大于可分配的内存大小,例如加载一张非常大的图片,就可能出现 OOM。

常见的 OOM

堆溢出

堆内存溢出是最为常见的 OOM ,通常是由于堆内存已经满了,并且不能够被垃圾回收器回收,从而导致 OOM。

线程溢出

不同的手机允许的最大线程数量是不一样的,在有些手机上这个值被修改的非常低,就会比较容易出现线程溢出的问题

FD数量溢出

文件描述符溢出,当程序打开或者新建一个文件的时候,系统会返回一个索引值,指向该进程打开文件的记录表,例如当我们用输出流文件打开文件的时候,系统就会返回我们一个FD,FD是可能出现泄露的,例如输入输出流没有关闭的时候,详细可参考 Android FD泄露问题

虚拟内存不足

在新建线程的时候,底层需要创建 JNIEnv 对象,并且分配虚拟内存,如果虚拟内存耗尽,会导致创建线程失败,并抛出 OOM。

Jvm,Dvm,Art的内存区别

Android 中使用的是基于 Java 语言的虚拟机 Dalvik / ART ,而 Dalvik 和 ART 都是基于 JVM 的,但是需要注意的是 Android 中的 虚拟器和标准的 JVM 有所不同,因为它们需要运行在 Android 设备上,因此他们具有不同的优化和限制。

在回收方面,Dalvik 仅固定一种回收算法,而 ART 回收算法可在运行期按需选择,并且ART 具备内存整理能力,减少内存空洞。

JVM

JVM 是一个虚构出来的计算机,是通过在实际计算机上仿真各种计算机功能来实现的,它有完善的(虚拟)硬件架构,还有相应的指令系统,其指令集基于堆栈结构。使用 Java虚拟机就是为了支持系统操作无关,在任何系统中都可以运行的程序。

JVM 将所管理的内存分为以下几个部分:
请添加图片描述

  • 方法区

    各个线程锁共享的,用于存储已经被虚拟机加载的类信息,常量,静态变量等,当方法区无法满足内存分配需求时,将会抛出 OutOfMemoryError 异常。

    • 常量池

      常量池也是方法区的一部分,用于存放编译器生成的各种自变量和符号引用,用的最多的就是 String,当 new String 并调用intern 时,就会在常量池查看是否有该字符串,有则返回,没有则创建一个并返回。

  • Java 堆

    虚拟机内存中最大的一块内存,所有通过 new 创建的对象都会在堆内存进行分配,是虚拟机中最大的一块内存,也是gc需要回收的部分,同时OOM也容易发生在这里

    从内存回收角度来看,由于现在收集器大都采用分代收集法,所以还可以细分为新生代,老年代等。

    根据 Java 虚拟机规定,Java 堆可以处于物理上不连续的空间,只要逻辑上是连续的就行,如果对中没有可分配内存时,就会出现 OutOfMemoryError 异常

  • Java 栈

    线程私有,用来存放 java 方法执行时的所有数据,由栈贞组成,一个栈贞就代表一个方法的执行,每个方法的执行就相当于是一个栈贞在虚拟机中从入栈到出栈的过程。栈贞中主要包括,局部变量,栈操作数,动态链接等。

    Java 栈划分为操作数栈,栈帧数据和局部变量数据,方法中分配的局部变量在栈中,同时每一次方法的调用都会在栈中奉陪栈帧,栈的大小是把双刃剑,分配太小可能导致栈溢出,特别是在有递归,大量的循环操作的时候。如果太大就会影响到可创建栈的数量,如果是多线程应用,就会导致内存溢出。

  • 本地方法栈

    与 java 栈的效果基本类似,区别只不过是用来服务于 native 方法。

  • 程序计数器

    是一块较小的空间,它的作用可以看做是当前线程锁执行字节码的行号指示器,用于记录线程执行的字节码指令地址,使得线程切换时能够恢复到正确的执行位置。

DVM

原名 Dalvik 是 Google 公司自己设计用于 Android 平台的虚拟机,本质上也是一个 JAVA 虚拟机,是 Android 中 Java 程序运行的基础,其指令基于寄存器架构,执行其特有的文件格式-dex。

DVM 运行时堆

DVM 的堆结构和 JVM 的堆结构有所区别,主要体现在将堆分成了 Active 堆 和 Zygote 堆。Zygote 是一个虚拟机进程,同时也是一个虚拟机实例孵化器,zygote 堆是 Zygote 进程在启动时预加载的类,资源和对象,除此之外我们在代码中创建的实例,数组等都是存储在 Active 堆中的。

为什么要将 Dalvik 堆分为两块,主要是因为 Android 通过 fork 方法创建一个新的 zygote 进程,为了尽量避免父进程和子进程之间的数据拷贝。

Dalvik 的 Zygote 对存放的预加载类都是 Android 核心类和 Java 运行时库,这部分很少被修改,大多数情况下子进程和父进程共享这块区域,因此这部分类没有必要进行垃圾回收,而 Active 作为程序代码中创建的实例对象的堆,是垃圾回收的重点区域,因此需要将两个堆分开。

DVM 回收机制

DVM 的垃圾回收策略默认是标记清除算法(mark-and-sweep),基本流程如下

  1. 标记阶段:从根对象开始遍历,标记所有可达对象,将它们标记为非垃圾对象
  2. 清楚阶段:遍历整个堆,将所有未被标记的对象清除
  3. 压缩阶段(可选):将所有存货的对象压缩到一起,以便减少内存碎片

需要注意的是 DVM 垃圾回收器是基于标记清除算法的,这种算法会产生内存算法,可能会导致内存分配效率降低,因此 DVM 还支持分代回收算法,可以更好的处理内存碎片问题。

在分代垃圾回收中,内存被分为不同的年代,每个年代使用不同的垃圾回收算法进行处理,年轻代使用标记复制算法,老年代使用标记清除法,这样可以更好的平衡内存分配效率和垃圾回收效率

ART

ART 是在 Android 5.0 中引入的虚拟机,与 DVM 相比,ART 使用的是 AOT(Ahead of Time) 编译技术,这意味着他将应用程序的字节码转换为本机机器码,而不是在运行时逐条解释字节码,这种编译技术可以提高应用程序的执行效率,减少应用程序启动时间和内存占用量

JIT 和 AOT 区别
  • Just In Time

    DVM 使用 JIT 编译器,每次应用运行时,它实时的将一部分 dex 字节码翻译成机器码。在程序的执行过程中,更多的代码被编译缓存,由于 JIT 只翻译一部分代码,它消耗更少的内存,占用更少的物理内存空间

  • Ahead Of Time

    ART 内置了一个 AOT 编译器,在应用安装期间,她将 dex 字节码编译成机器码存储在设备的存储器上,这个过程旨在应用安装到设备的时候发生,由于不在需要 JIT 编译,代码的执行速度回快很多

ART运行时堆

与 DVM 不同的是,ART 采用了多种垃圾收集方案,每个方案会运行不同的垃圾收集器,默认是采用了 CMS (Concurrent Mark-Sweep) 方案,也就是并发标记清除,该方案主要使用了 sticky-CMS 和 partial-CMS。根据不同的方案,ART 运行时堆的空间也会有不同的划分,默认是由四个区域组成的。

分别是 Zygote、Active、Image 和 Large Object 组成的,其中 Zygote 和 Active 的作用越 DVM 中的作用是一样的,Image 区域用来存放一些预加载的类,Large Object 用来分配一下大对象(默认大小为12kb),其中 Zygote 和 Image 是进程间共享的,

为什么会出现 OOM?

出现 OOM 是应为 Android 系统对虚拟机的 heap 做了限制,当申请的空间超过这个限制时,就会抛出 OOM,这样做的目的是为了让系统能同时让比较多的进程常驻于内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应

Android 获取可分配的内存大小

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.memoryClass

返回当前设备的近似每个应用程序内存类。这让你知道你应该对应用程序施加多大的内存限制,让整个系统工作得最好。返回值以兆字节为单位; 基线Android内存类为16 (恰好是这些设备的Java堆限制); 一些内存更多的设备可能会返回24甚至更高的数字。

我使用的手机内存是 16 g,调用返回的是 256Mb,

manager.memoryClass 对应 build.prop 中 dalvik.vm.heapgrowthlimit

申请更大的堆内存

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.largeMemoryClass

可分配的最大对内存上限,需要在 manifest 文件中设置 android:largeHeap=“true” 方可启用

manager.largeMemoryClass 对应 build.prop 中 dalvik.vm.heapsize

Runtime.maxMemory

获取进程最大可获取的内存上限,等于上面这两个值之一

/system/build.prop

该目录是Android内存配置相关的文件,里面保存了系统的内存的限制等数据,执行 adb 命令可看到 Android 配置的内存相关信息:

adb shell
cat /system/build.prop

默认是打不开的,没有权限,需要 root

打开后找到 dalvik.vm 相关的配置

dalvik.vm.heapstartsize=5m	#单个应用程序分配的初始内存
dalvik.vm.heapgrowthlimit=48m	#单个应用程序最大内存限制,超过将被Kill,
dalvik.vm.heapsize=256m  #所有情况下(包括设置android:largeHeap="true"的情形)的最大堆内存值,超过直接oom。

未设置android:largeHeap="true"的时候,只要申请的内存超过了heapgrowthlimit就会触发oom,而当设置android:largeHeap="true"的时候,只有内存超过了heapsize才会触发oom。heapsize已经是该应用能申请的最大内存(这里不包括native申请的内存)。

OOM 演示

堆内存分配失败

堆内存分配失败对应的是 /art/runtime/gc/heap.cc ,如下代码

oid Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
  // If we're in a stack overflow, do not create a new exception. It would require running the
  // constructor, which will of course still be in a stack overflow.
  if (self->IsHandlingStackOverflow()) {
    self->SetException(
        Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
    return;
  }
  //....
  std::ostringstream oss;
  size_t total_bytes_free = GetFreeMemory();
  oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
      << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM,"
      << " target footprint " << target_footprint_.load(std::memory_order_relaxed)
      << ", growth limit "
      << growth_limit_;
  
  self->ThrowOutOfMemoryError(oss.str().c_str());
}

通过上面的分析,我们也知道系统对每个应用都做了最大内存的约束,超过这个值就会 OOM ,下面通过一段代码来演示一下这种类型的 OOM

fun testOOM() {
    val manager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    Timber.e("app maxMemory ${manager.memoryClass} Mb")
    Timber.e("large app maxMemory ${manager.largeMemoryClass} Mb")
    Timber.e("current app maxMemory ${Runtime.getRuntime().maxMemory() / 1024 / 1024} Mb")
    var count = 0
    val list = mutableListOf<ByteArray>()
    while (true) {
        Timber.e("count $count    total ${count * 20}")
        list.add(ByteArray(1024 * 1024 * 20))
        count++
    }
}

上面代码中每次申请 20mb,测试分为两种情况,

  1. 未开启 largeHeap:

     E  app maxMemory 256 Mb
     E  large app maxMemory 512 Mb
     E  current app maxMemory 256 Mb
     E  count 0    total 0
     E  count 1    total 20
     E  count 2    total 40
     E  count 3    total 60
     E  count 4    total 80
     E  count 5    total 100
     E  count 6    total 120
     E  count 7    total 140
     E  count 8    total 160
     E  count 9    total 180
     E  count 10    total 200
     E  count 11    total 220
     E  count 12    total 240
    java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 12386992 free bytes and 11MB until OOM, target footprint 268435456, growth limit 268435456
    ......
    

    可以看到一共分配了 12次,在第十二次的时候抛出了异常,显示 分配 20 mb 失败,空闲只有 11 mb,

  2. 开启 largeHeap

    app maxMemory 256 Mb                      
    large app maxMemory 512 Mb
    current app maxMemory 512 Mb
    E  count 0    total 0
    E  count 1    total 20
    E  count 2    total 40
    E  count 3    total 60
    E  count 4    total 80
    E  count 5    total 100
    E  count 6    total 120
    E  count 7    total 140
    E  count 8    total 160
    E  count 9    total 180
    E  count 10    total 200
    E  count 11    total 220
    E  count 12    total 240
    E  count 13    total 260
    E  count 14    total 280
    E  count 15    total 300
    E  count 16    total 320
    E  count 17    total 340
    E  count 18    total 360
    E  count 19    total 380
    E  count 20    total 400
    E  count 21    total 420
    E  count 22    total 440
    E  count 23    total 460
    E  count 24    total 480
    E  count 25    total 500
    FATAL EXCEPTION: main
    Process: com.dzl.duanzil, PID: 31874
    java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 8127816 free bytes and 7937KB until OOM, target footprint 536870912, growth limit 536870912
    

    可以看到分配了25 次,可使用的内存也增加到了 512 mb

创建线程失败

线程创建会消耗大量的内存资源,创建的过程涉及 java 层 和 native 层,本质上是在 native 层完成的,对应的是 /art/runtime/thread.cc ,如下代码

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  //........
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

这里借用网上的一张照片来看一下创建线程的流程

请添加图片描述

根据上图可以看到主要有两部分,分别是创建 JNI Env 和 创建线程

创建 JNI Env 失败
  1. FD 溢出导致 JNIEnv 创建失败

    E/art: ashmem_create_region failed for 'indirect ref table': Too many open files java.lang.OutOfMemoryError:Could not allocate JNI Env at java.lang.Thread.nativeCreate(Native Method) at java.lang.Thread.start(Thread.java:730)
    
  2. 虚拟内存不足导致 JNIEnv 创建失败

    E OOM_TEST: create thread : 1104
    W com.demo: Throwing OutOfMemoryError "Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log." (VmSize 2865432 kB)
    E InputEventSender: Exception dispatching finished signal.
    E MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
    MessageQueue-JNI: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
    E MessageQueue-JNI:      at java.lang.Thread.nativeCreate(Native Method)
    E MessageQueue-JNI:      at java.lang.Thread.start(Thread.java:887)
    
    E AndroidRuntime: FATAL EXCEPTION: main
    E AndroidRuntime: Process: com.demo, PID: 3533
    E AndroidRuntime: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
    E AndroidRuntime:        at java.lang.Thread.nativeCreate(Native Method)
    E AndroidRuntime:        at java.lang.Thread.start(Thread.java:887)
    
创建线程失败
  1. 虚拟机内存不足导致失败

    native 通过 FixStackSize 设置线程大小

    static size_t FixStackSize(size_t stack_size) {
      if (stack_size == 0) {
        stack_size = Runtime::Current()->GetDefaultStackSize();
      }
      stack_size += 1 * MB;
      if (kMemoryToolIsAvailable) {
        stack_size = std::max(2 * MB, stack_size);
      }  if (stack_size < PTHREAD_STACK_MIN) {
        stack_size = PTHREAD_STACK_MIN;
      }
      if (Runtime::Current()->ExplicitStackOverflowChecks()) {
        stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
      } else {
        stack_size += Thread::kStackOverflowImplicitCheckSize +
            GetStackOverflowReservedBytes(kRuntimeISA);
      }
      stack_size = RoundUp(stack_size, kPageSize);
      return stack_size;
    }
    
    W/libc: pthread_create failed: couldn't allocate 1073152-bytes mapped space: Out of memory
    W/tch.crowdsourc: Throwing OutOfMemoryError with VmSize  4191668 kB "pthread_create (1040KB stack) failed: Try again"
    java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
            at java.lang.Thread.nativeCreate(Native Method)
            at java.lang.Thread.start(Thread.java:753)
    
  2. 线程数量超过限制

    用一段简单的代码来测试一下

    fun testOOM() {
        var count = 0
        while (true) {
            val thread = Thread(Runnable {
                Thread.sleep(1000000000)
            })
            thread.start()
            count++
            Timber.e("current thread count $count")
        }
    }
    

    通过打印日志发现,一共创建了 2473 个线程,当然这些线程都是没有任务的线程,报错信息如下所示

    pthread_create failed: couldn't allocate 1085440-bytes mapped space: Out of memory
    Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again" (VmSize 4192344 kB)
    
    FATAL EXCEPTION: main
    Process: com.dzl.duanzil, PID: 18085
    java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
    	at java.lang.Thread.nativeCreate(Native Method)
    

    通过测试可以看出来,具体的原因也是内存不足引起的,而不是线程数量超过限制,可能是。

OOM 监控

我们都知道,OOM 的出现就是大部分原因都是由于内存泄露,导致内存无法释放,才出现了 OOM,所以监控主要监控的是内存泄露,现在市面上对于内存泄露检查这方面已经非常成熟了,我们来看几个常用的监控方式

LeakCanary

使用非常简单,只需要添加依赖后就可以直接使用,无需手动初始化,就能实现内存泄露检测,当内存发生泄露后,会自动发出一个通知,点击就可以查看具体的泄露堆栈信息

LeakCannary 只能在 debug 环境使用,因为他是在当前进程 dump 内存快照,会冻结当前进程一段时间,所以不适于在正式环境使用。

Android Profile

可以以图像的方式直观的查看内存使用情况,并且可以直接 capture heap dump,或者抓取原生内存(C/C++) 以及 Java/Kotlin 内存分配。只能在线下使用,功能非常强大,可是吧内存泄露,抖动,强制 GC 等。

ResourceCanary

ResourceCanary 属于 Matrix 的一个子模块,它将原本难以发现的 Acivity 泄露和 Activity 泄露和重复创建的沉余的 Bitmap 暴露出来,并提供引用链等信息帮助排查这些问题

ResourceCanary 将检测和分析分离,客户端只负责检测和dump内存镜像文件,并且对检查部分生成的 Hprof 文件进行了裁剪,移除了大部分无用数据。也增加了 Bitmap 对象检测,方便通过减少沉余 Bitmap 数量,降低内存消耗。

使用可查看 Matrix

KOOM

上面的两者都只能在线下使用,而 KOOM 可以再线上使用,KOOM 是快手出的一套完整的解决方案,可以实现 Java,native 和 thread 的泄露监控

使用可查看 KOOM

OOM 优化

OOM 的优化其实相当于是内存优化,对于这部分内容,网上有一篇讲的非常详细的文章 深入探索 Android 内存优化,该文章内容非常全面且难度也比较高,建议仔细阅读。

参考链接

【性能优化】大厂OOM优化和监控方案

深入探索 Android 内存优化

DVM和ART原理初探

Android OOM 问题探究

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

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

相关文章

精准数据分析,TeeChart为企业量身定制可视化退休预估方案

TeeChart for .NET是优秀的工业4.0 WinForm图表控件&#xff0c;官方独家授权汉化&#xff0c;集功能全面、性能稳定、价格实惠等优势于一体。TeeChart for .NET 中文版还可让您在使用和学习上没有任何语言障碍&#xff0c;至少可以节省30%的开发时间。 点击立即下载最新版Tee…

docker下不同容器的网络互相访问问题

目录 背景 ​编辑 docker网络模式 解决方法 mysql下 docker-compose下网络设置 nacos 效果 背景 我这边有两个容器&#xff0c;宿主机ip为 192.168.1.115&#xff0c;一个mysql&#xff0c;一个nacos&#xff0c;部署在主机上&#xff0c;使用的默认网络bridge&#xff…

学习SpringBoot入门知识,附带教程源码分享,快速掌握开发技巧-【imooc-java2021】体系课-Java工程师 2022版

学习SpringBoot入门知识&#xff0c;附带教程源码分享&#xff0c;快速掌握开发技巧 目录福利&#xff1a;文末有分享SpringBoot教程及源码哦 一、Spring Boot 是什么二、为什么要使用 Spring Boot三、快速入门3.1 创建 Spring Boot 项目3.1.1 通过 Spring Initializr 来创建1、…

微信小程序入门开发懂你找图小程序

文章目录 搭建 tabbar页面路径 首页模块tabs组件需求推荐组件精选大图月份热门分类模块需求 分类详情业务 首页模块专辑模块需求 专辑详情精美视频需求 视频详情需求 图片详情需求 搭建 tabbar 页面路径 页面名称路径首页index横屏horizontal精美视频video搜索search我的mine …

MySQL——存储引擎于索引应用

文章目录 一、 存储引擎1.1 MySQL结构1.2 存储引擎简介1.3 存储引擎特点1.3.1 InnoDB1.3.1.1 InnoDB 基本介绍1.3.1.2 InnoDB 逻辑存储结构 1.3.2 MyISAM1.3.3 Memory 1.4 三种引擎特点及区别1.5 存储引擎选择 二、 索引 - 重点2.1 介绍2.2 索引结构2.2.1 B-Tree 多路平衡二叉树…

【Linux:动态库与静态库】

1 动态库与静态库的概念 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。 动态库&#xff08;.so&#xff09;&#xff1a;程序在运行的时候才去链接动态库的代码&#xff0c;多个程序共享使…

企业微信4.1.6 版本新功能介绍

一、效率工具与基础体验优化 文档 文档增加了丰富的模板&#xff0c;包含项目管理、日报周报、信息收集等多种场景&#xff0c;帮助了解更多文档功能&#xff0c;助力日常工作。 权限管理新增了「成员加入确认」开关&#xff0c;开启后需要管理员确认才能添加成员&#xff0…

相爱相杀的在线帮助文档语雀、Baklib、石墨文档,到底有何区别?

在线帮助文档是现代企业不可或缺的一部分&#xff0c;它提供了针对特定产品或服务的详细说明和指南&#xff0c;以帮助用户更好地理解产品或服务并解决问题。目前市面上有许多在线帮助文档工具&#xff0c;其中语雀、Baklib和石墨文档是比较受欢迎的三种&#xff0c;本文将对它…

卸载旧版本Keil,安装新版本的注意事项以及安装完成以后的一些问题

1. 资料 这里使用的是MDK536安装包和Keil.STM32F4xx_DFP.2.16.0安装包&#xff08;因为板子是正点原子stm32F407的&#xff09;&#xff1b; 安装包放到下面&#xff08;安装包也是在网上找的&#xff0c;里面还是比较全的&#xff0c;有C51&#xff0c;以及注册机等&#xf…

只做笔记有必要买apple pencil吗?好写的电容笔排行榜

随着科技的发展&#xff0c;出现了许多新的电子器件和数码器件。比如智能手机&#xff0c;比如ipad&#xff0c;比如电容笔等等。但实际上&#xff0c;想要让ipad发挥出最大的作用&#xff0c;就必须要有一支好的电笔。就像是我们在ipad上写字&#xff0c;总是要手写&#xff0…

硬件工程师-电路设计1-概念

学习电路设计及分析需要掌握的三大定律详解 1、遵循 源 回路 阻抗 分析回路上的节点&#xff0c;分析节点的内阻&#xff0c;电压&#xff0c;功率电流 2、分析电路上的波形&#xff1a;电路设计的过程就是波形整形的过程 波形整形&#xff1a;幅值的整形 波的…

CNNs:ZFNet之基于AlexNet特征可视化实验分析

CNNs:ZFNet之基于AlexNet特征可视化实验分析 导言基于AlexNet网络的实验分析实验一:不同卷积层特征提取分析实验二&#xff1a;不同卷积层提取特征收敛分析 ZFNet网络介绍基于ZFNet网络的实验分析实验三&#xff1a;针对AlexNet特征提取改善可视化实验四&#xff1a;特征不变性…

HW之轻量级内网资产探测漏洞扫描工具

简介 RGPScan是一款支持弱口令爆破的内网资产探测漏洞扫描工具&#xff0c;集成了Xray与Nuclei的Poc 工具定位 内网资产探测、通用漏洞扫描、弱口令爆破、端口转发、内网穿透、SOCK5 主机[IP&域名]存活检测&#xff0c;支持PING/ICMP模式 端口[IP&域名]服务扫描 网…

腾讯云图形验证码申请流程

目录 一、官方指引二、操作步骤1.步骤1&#xff1a;新建验证&#xff0c;获取验证码密钥2.步骤2&#xff1a;客户端接入验证码&#xff0c;展示验证页面3.步骤3&#xff1a;服务端接入验证码&#xff0c;调用票据校验 API 进行二次校验 一、官方指引 https://cloud.tencent.co…

为什么mac插入了u盘没反应 苹果mac插上usb后怎么找到

U盘使用简单&#xff0c;便于携带&#xff0c;几乎每个mac用户都有一个u盘。使用新的u盘的时候&#xff0c;你有没有遇到过mac插入了u盘没反应的情况呢&#xff1f;如果你是初次接触Mac电脑&#xff0c;使用u盘可能会手足无措&#xff0c;因为Mac系统和Windows存在差异&#xf…

苹果 App Store 出现山寨ChatGPT;Anthropic宣布获得4.5亿美元C轮融资

&#x1f680; 中国互联网协会提醒公众警惕“AI换脸”的新骗局 中国互联网协会提醒公众警惕“AI换脸”的新骗局&#xff0c;不法分子利用AI技术通过声音合成、伪造面部表情等实施诈骗。 公众应加强个人信息安全与防范措施&#xff0c;如加强个人信息保护、防止信息泄露、安装…

BLE连接通信

// BLE连接有关的技术分析 前言 本文的论述流程&#xff1a; 将传统的连接通信与广播进行对比&#xff0c;指出其不足说明BLE是如何制定规范解决这些问题写出完整的连接通信流程&#xff0c;并对能影响BLE连接通信的参数进行分析 1 连接通信的不足 保持连接是一个相当消耗…

代码随想录算法训练营15期 Day 2 | 977.有序数组的平方 、209.长度最小的子数组 、59.螺旋矩阵II 、总结

977.有序数组的平方 题目建议&#xff1a; 本题关键在于理解双指针思想 题目链接&#xff1a;力扣 思路一&#xff1a;暴力解算&#xff0c;直接将所有元素变成一个平方&#xff0c;然后进行排序。 class Solution { public:vector<int> sortedSquares(vector<int&g…

maven常用插件详解

官网讲解&#xff1a;https://maven.apache.org/plugins/ Maven 实际上是一个依赖插件执行的框架&#xff0c;它执行的每个任务实际上都由插件完成的。Maven 的核心发布包&#xff08;jar&#xff09;中并不包含任何 Maven 插件&#xff0c;它们以独立构件的形式存在&#xff…

数据翻译的代码辅助插件,一个注解搞定,减少30%SQL代码量

一、开源项目简介 Easy Trans是一款用于做数据翻译的代码辅助插件&#xff0c;利用MyBatis Plus/JPA/BeetlSQL 等ORM框架的能力自动查表&#xff0c;让开发者可以快速的把ID/字典码 翻译为前端需要展示的数据。 二、开源协议 使用Apache-2.0开源协议 三、界面展示 四、功能概…