性能优化|记一次线上OOM问题处理

news2024/11/17 1:26:55

概述

最近线上监控发现 OOM 涨幅较大,因此去尝试定位和修复这个问题,在修复了一些内存泄漏和大对象占用问题后, OOM 依旧未达到正常标准,在这些新上报的 hprof 文件中,发现几乎所有 case 中都有个叫 FinalizerReference 的对象,数量巨多,内存占用高居首位,因此判断它就是引起本次 OOM 上涨的罪魁祸首。

ReferenceQueue

首先前置了解下 ReferenceQueue 引用队列是个啥,简言之就是用来存放 Reference 对象的队列,当 Reference 对象所引用的对象被 GC 回收时,该 Reference 对象就会被加入到引用队列 ReferenceQueue 中。即:

valqueue= ReferenceQueue<Bean>()
valbean= Bean()
valreference= SoftReference(bean, queue)
复制代码

上述代码中创建了一个 bean 强引用,以及一个 reference 软引用,当 bean 被回收时,该软引用 reference 对象会被放到 queue 队列中,后续该 reference 对象需要开发者自行处理(如从队列中 poll 等)。

Leakcanary 检测内存泄漏的原理就是应用的 ReferenceQueue 引用队列,以 Activity 为例:

  1. 监听 Activity 的生命周期。

  1. 在 onDestory 的时候,通过 ReferenceQueue 来创建对应 Actitity 的 Refrence 引用对象。

  1. 一段时间后,从 ReferenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收;如果 ReferenceQueue 没有这个 Actitity 的 Refrence, 那就说明出现了内存泄漏。

关于 Reference 引用,网上一搜很多文章,大家估计比较清楚了,就不再赘述。以前做过一些相关的笔记,有兴趣可以看看 JVM垃圾回收-引用。

FinalizerReference

首先介绍一下 Finalizer 对象,它指的是在其 Java 类中复写了 finalize() 方法,且该方法非空的对象,有的地方称呼这种类叫 f 类,我们也这样叫它。在类加载过程中会把加载的 Java 类标记为是否 f 类。

FinalizerReference概述

现在开始看看 FinalizerReference 是个啥玩意,知己知彼,才能知道怎么去修它。这个 FinalizerReference 是用来协助 FinalizerDaemon 线程处理对象的 finalize() 工作的,一次性出现了三个名词,我们先从 FinalizerReference 看起。

看看它的部分代码:

// java/lang/ref/FinalizerReference.javapublicfinalclassFinalizerReference<T> extendsReference<T> {
    // This queue contains those objects eligible for finalization.publicstaticfinal ReferenceQueue<Object> queue = newReferenceQueue<Object>();

    // This list contains a FinalizerReference for every finalizable object in the heap.// Objects in this list may or may not be eligible for finalization yet.privatestatic FinalizerReference<?> head = null;

    // The links used to construct the list.private FinalizerReference<?> prev;
    private FinalizerReference<?> next;

    publicstaticvoidadd(Object referent) {
        FinalizerReference<?> reference = newFinalizerReference<Object>(referent, queue);
        synchronized (LIST_LOCK) {
            reference.prev = null;
            reference.next = head;
            if (head != null) {
                head.prev = reference;
            }
            head = reference;
        }
    }

    publicstaticvoidremove(FinalizerReference<?> reference) {
        synchronized (LIST_LOCK) {
            FinalizerReference<?> next = reference.next;
            FinalizerReference<?> prev = reference.prev;
            reference.next = null;
            reference.prev = null;
            if (prev != null) {
                prev.next = next;
            } else {
                head = next;
            }
            if (next != null) {
                next.prev = prev;
            }
        }
    }
}
复制代码

FinalizerReference 中有两个静态方法: add 和 remove 方法。它创建了一个 FinalizerReference 类型的链表(表头用静态变量 FinalizerReference.head 表示),其中每个 FinalizerReference 对象都使用 ReferenceQueue 类型的静态变量 queue 来创建,这样当对应的对象 Object referent 被回收后,该 FinalizerReference 会被放入 ReferenceQueue 中。

FinalizerReference.add

上述 FinalizerReference.add 方法是虚拟机调用的,当创建对象的时候,如果发现该类是 f 类,就会调用 FinalizerReference.add 方法创建 FinalizerRefence 对象,并将其加入到 head 链表中。

// art/runtime/mirror/class-alloc-inl.htemplate<bool kIsInstrumented, Class::AddFinalizer kAddFinalizer, bool kCheckAddFinalizer>
inline ObjPtr<Object> Class::Alloc(Thread* self, gc::AllocatorType allocator_type){
  CheckObjectAlloc();
  gc::Heap* heap = Runtime::Current()->GetHeap();
  bool add_finalizer;
  switch (kAddFinalizer) {
    case Class::AddFinalizer::kUseClassTag:
      // 这里判断是不是 f 类
      add_finalizer = IsFinalizable();
      break;
    case Class::AddFinalizer::kNoAddFinalizer:
      add_finalizer = false;
      DCHECK_IMPLIES(kCheckAddFinalizer, !IsFinalizable());
      break;
  }
  // Note that the `this` pointer may be invalidated after the allocation.
  ObjPtr<Object> obj = heap->AllocObjectWithAllocator<kIsInstrumented, /*kCheckLargeObject=*/false>(self, this, this->object_size_, allocator_type, VoidFunctor());
  if (add_finalizer && LIKELY(obj != nullptr)) {
    // 如果是 f 类,则调用 AddFinalizerReference 方法
    heap->AddFinalizerReference(self, &obj);
    // ...
  }
  return obj;
}

// art/runtime/gc/heap.ccvoidHeap::AddFinalizerReference(Thread* self, ObjPtr<mirror::Object>* object){
  ScopedObjectAccess soa(self);
  ScopedLocalRef<jobject> arg(self->GetJniEnv(), soa.AddLocalReference<jobject>(*object));
  jvalue args[1];
  args[0].l = arg.get();
  InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_FinalizerReference_add, args);
  // Restore object in case it gets moved.
  *object = soa.Decode<mirror::Object>(arg.get());
}
复制代码

上面的 AddFinalizerReference 方法会调用到 Java 层的 FinalizerReference.add() 静态方法,就完成了 add 的操作。

FinalizerReference.remove

我们前面提到,当 Reference 对象所引用的对象被 GC 回收时,该 Reference 对象就会被加入到引用队列 ReferenceQueue 中,所以当 f 类的对象发生 gc 时,会将其对应的 FinalizerReference 对象加入到 FinalizerReference.queue 队列里。而 remove 的时机与 FinalizerDaemon 守护线程有关,我们看看这个线程的源码:

// libcore/libart/src/main/java/java/lang/Daemons.java// private static abstract class Daemon implements RunnableprivatestaticclassFinalizerDaemonextendsDaemon {
    privatestaticfinalFinalizerDaemonINSTANCE=newFinalizerDaemon();
    privatefinal ReferenceQueue<Object> queue = FinalizerReference.queue;

    @OverridepublicvoidrunInternal() {
        // ...while (isRunning()) {
            try {
                // Use non-blocking poll to avoid FinalizerWatchdogDaemon communication when busy.
                FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
                // ...
                doFinalize(finalizingReference);
            } catch (InterruptedException ignored) {
            } catch (OutOfMemoryError ignored) {
            }
        }
    }
}
复制代码

在 FinalizerDaemon.runInternal 方法中会通过 FinalizerReference.queue(ReferenceQueue引用队列) 的 poll/remove 方法拿到 queue 中的 Reference 引用,接着执行 doFinalize() 方法,该方法会调用 Finalizer 对象的 finalize() 方法:

privatevoiddoFinalize(FinalizerReference<?> reference) {
    FinalizerReference.remove(reference);
    Objectobject= reference.get();
    reference.clear();
    try {
        object.finalize();
    } catch (Throwable ex) {
        // The RI silently swallows these, but Android has always logged.
        System.logE("Uncaught exception thrown by finalizer", ex);
    } finally {
        // Done finalizing, stop holding the object as live.
        finalizingObject = null;
    }
}
复制代码

首先将获取到的 FinalizerReference 对象从 FinalizerReference.head 链表中移除,接着通过 reference.get() 方法得到 Java 对象,并执行其 finalize() 方法。

小结

FinalizerReference 的主要作用是协助 FinalizerDaemon 守护线程来执行 Finalizer 对象的 finalize() 方法

  • 在 f 类创建对象时,调用 FinalizerReference.add() 方法创建一个 FinalizerReference 对象(使用 ReferenceQueue 队列)并加入到 head 链表里。

  • 在 f 类的对象被回收后,其对应的 FinalizerReference 对象会被加入到 ReferenceQueue 队列(FinalizerReference.queue)里。

  • FinalizerDaemon 守护线程从 FinalizerReference.queue 队列中取出 FinalizerReference 对象,并执行其对应 f 类对象的 finalize() 方法。

ReferenceQueueDaemon

上面看完了 FinalizerDaemon 守护线程,这里再看看 ReferenceQueueDaemon 守护线程,上面说到在创建引用对象 Reference 的时候可以关联一个 ReferenceQueue 队列,当被引用对象被 gc 回收时,该 reference 对象就会被加入到其创建时关联的队列去,这个加入队列的操作就是由 ReferenceQueueDaemon 守护线程完成的

privatestaticclassReferenceQueueDaemonextendsDaemon {
    @OverridepublicvoidrunInternal() {
        while (isRunning()) {
            Reference<?> list;
            try {
                synchronized (ReferenceQueue.class) {
                    if (ReferenceQueue.unenqueued == null) {
                        progressCounter.incrementAndGet();
                        do {
                            ReferenceQueue.class.wait();
                        } while (ReferenceQueue.unenqueued == null);
                        progressCounter.incrementAndGet();
                    }
                    list = ReferenceQueue.unenqueued;
                    ReferenceQueue.unenqueued = null;
                }
            } catch (InterruptedException e) {
                continue;
            } catch (OutOfMemoryError e) {
                continue;
            }
            ReferenceQueue.enqueuePending(list, progressCounter);
        }
    }
}
复制代码

具体源码感兴趣的话可以自己去看看,这里就不贴太多源码了。

FinalizerWatchdogDaemon

这里再补充一下 FinalizerWatchdogDaemon 守护线程,它跟 FinalizerDaemon 以及 ReferenceQueueDaemon 线程是一起 start 的。我们再看一下上面 FinalizerDaemon 和 ReferenceQueueDaemon 线程的 runInternal() 方法:

// ReferenceQueueDaemon@OverridepublicvoidrunInternal() {
    FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
    while (isRunning()) {
        Reference<?> list;
        try {
            synchronized (ReferenceQueue.class) {
                if (ReferenceQueue.unenqueued == null) {
                    progressCounter.incrementAndGet();
                    FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
                    do {
                        ReferenceQueue.class.wait();
                    } while (ReferenceQueue.unenqueued == null);
                    progressCounter.incrementAndGet();
                    FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
                }
                list = ReferenceQueue.unenqueued;
                ReferenceQueue.unenqueued = null;
            }
        } catch (InterruptedException e) {
            continue;
        } catch (OutOfMemoryError e) {
            continue;
        }
        ReferenceQueue.enqueuePending(list, progressCounter);
        FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();
    }
}

// FinalizerDaemon@OverridepublicvoidrunInternal() {
    FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
    while (isRunning()) {
        try {
            FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
            if (finalizingReference != null) {
                finalizingObject = finalizingReference.get();
            } else {
                finalizingObject = null;
                FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
                finalizingReference = (FinalizerReference<?>)queue.remove();
                finalizingObject = finalizingReference.get();
                FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
            }
            doFinalize(finalizingReference);
        } catch (InterruptedException ignored) {
        } catch (OutOfMemoryError ignored) {
        }
    }
}
复制代码

上面的 monitoringNotNeeded 方法会休眠线程,停止 timeout 计时,而 monitoringNotNeeded 会唤醒 FinalizerWatchdogDaemon 守护线程。看看 FinalizerWatchdogDaemon 的源码,我加了一些注释:

privatestaticclassFinalizerWatchdogDaemonextendsDaemon {
    // Single bit values to identify daemon to be watched.// 监控的两种类型staticfinalintFINALIZER_DAEMON=1;
    staticfinalintRQ_DAEMON=2;

    @OverridepublicvoidrunInternal() {
        while (isRunning()) {
            // sleepUntilNeeded 内部会调用 wait() 方法,不贴了if (!sleepUntilNeeded()) {
                continue;
            }
            finalTimeoutExceptionexception= waitForProgress();
            if (exception != null && !VMDebug.isDebuggerConnected()) {
                // 抛出异常 Thread.currentThread().dispatchUncaughtException(exception)
                timedOut(exception);
                break;
            }
        }
    }

    // 去掉 whichDaemon 的监控,进入 waitprivatesynchronizedvoidmonitoringNotNeeded(int whichDaemon) {
        activeWatchees &= ~whichDaemon;
    }

    // 添加 whichDaemon 的监控, notify 线程privatesynchronizedvoidmonitoringNeeded(int whichDaemon) {
        intoldWatchees= activeWatchees;
        activeWatchees |= whichDaemon;
        if (oldWatchees == 0) {
            // 调用 notify 唤醒
            notify();
        }
    }

    // 判断是否添加了指定 whichDaemon 类型的监控privatesynchronizedbooleanisActive(int whichDaemon) {
        return (activeWatchees & whichDaemon) != 0;
    }

    // 调用 isActive 判断开启监控的类型,通过 Thread.sleep() 方法休眠指定时间// 休眠结束后返回 TimeoutException 异常,若途中监控的逻辑执行完成则表示未超时,返回 null// 等待时间: VMRuntime.getFinalizerTimeoutMs// If the FinalizerDaemon took essentially the whole time processing a single reference,// or the ReferenceQueueDaemon failed to make visible progress during that time, return an exception.private TimeoutException waitForProgress() {
        finalizerTimeoutNs = NANOS_PER_MILLI * VMRuntime.getRuntime().getFinalizerTimeoutMs();
        // ...
    }
}
复制代码

上面一些细节代码就不贴出了, FinalizerWatchdogDaemon 主要用来监控两种类型的执行时长: FINALIZER_DAEMON 和 RQ_DAEMON。当执行超时时就会抛出 TimeOutException 异常,因此不要在 finalize() 方法中做耗时操作。

这个超时时间在 AOSP 中定义为 10s, 国内厂商可能会修改这个值, 据说有的改为了 60 s, 有兴趣的同学可以自己解包看看:

RUNTIME_OPTIONS_KEY (unsigned int,        FinalizerTimeoutMs,             10000u)
复制代码

OOM排查

通过线上 hprof 文件排除了大对象和内存泄漏的可能,接着在 hprof 中发现存在大量的 X(用 X 表示业务上的某个对象) 对象堆积,这些对象对应的 Java 类是一个与 Native 层有关的类,其重写了 finalize() 方法,线下无法复现堆积大量 X 对象的路径。

可能有某个业务场景的代码逻辑使用不当造成了 X 对象的疯狂创建,导致 FinalizerDaemon 线程来不及回收

由于这个 X 对象的创建场景比较多,因此无法通过代码 review 来定位到有问题的创建代码,采取了一些监控手段后依旧无果。

用治标不治本的方式,显式调用系统 gc 和 runFinalization 方法

在定位不到具体的问题场景后,因为猜测是创建了大量的 X 对象导致 FinalizerDaemon 线程回收不及时,因此分别在主线程和子线程中显式调用系统 gc 和 runFinalization 方法来手动回收 X 对象。

结果:子线程调用无效果, OOM 未下降;主线程调用发生了 ANR。

查看 ANR 堆栈后,发现 ANR 的位置都在另外一个 f 对象的 finalize() 方法中调用到的一行 Native 代码,因此问题就明了了,是因为在某个 finalize() 方法中调用到的这行代码卡死了(逻辑问题导致死锁),因此阻塞了 FinalizerDaemon 线程的执行,进而导致对象堆积。另外本应发生的 TimeoutException 异常被我们的异常处理框架捕获了,因此未曾暴露出来。

总结

Java 中并没有析构函数, finalize() 实现了类似析构函数的概念,可以在对象被回收前做一些回收性的操作,在 JNI 开发中可能被用来进行 Native 内存的释放。关于 f 类使用不当会造成的影响:

  • FinalizerDaemon 守护线程的优先级不高,在 CPU 紧张的情况下调度可能会被影响,所以可能无法及时回收 f 类对象。

  • finalize 对象由于 finalize() 的引用,它变成了一个强引用,即使没有其他显式的强引用了,它也还是无法立即被回收。

  • 因此 f 对象至少需要 2 次 gc 才可能被回收,第一次 gc 时会将 f 对象加入到 ReferenceQueue 中,而等到 finalize() 方法执行完毕后的下一次 gc 里才有可能回收它。由于守护线程的优先级低,这期间可能已经发生过多次 gc 了,长时间未回收的话又可能会导致 f 类在资源紧张时进入到老年代,从而引起老年代的 gc 甚至是 full gc。

因此尽量不要重载 finalize() 方法,而是通过一些逻辑上的接口去释放内存,如果一定要重载它的话,也不要让一些需要频繁创建的对象或者大对象去通过 finalize() 释放,不然总有一天会出现问题的。

与此相关的守护线程有四个,感兴趣的同学可以深入看看相关源码:

// libcore/libart/src/main/java/java/lang/Daemons.javaprivatestaticfinal Daemon[] DAEMONS = newDaemon[] {
        HeapTaskDaemon.INSTANCE,
        ReferenceQueueDaemon.INSTANCE,
        FinalizerDaemon.INSTANCE,
        FinalizerWatchdogDaemon.INSTANCE,
};

前段时间整理收集的关于Android性能优化的知识脑图总结和学习手册文档!既能够夯实底层原理、性能调优等核心技术点,又能够掌握普通开发者,难以触及的架构设计方法论。那你在工作中、团队里、面试时,也就拥有了同行难以复制的核心竞争力。需要完整版的朋友,可点击文末卡片或者点击这里!!

Android性能分析与优化实战进阶手册

Android性能分析与优化实战进阶手册目录及内容展示

第一章:Android性能优化概述

第二章:卡顿优化

第三章:启动速度优化

第四章:内存优化

第五章:布局优化

第六章:线程优化

第七章:电量优化

第八章:稳定性优化

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

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

相关文章

集合体系概述以及Collection集合常用API

一. 集合 集合与数组类似&#xff0c;都是一种容器。集合是Java中存储对象数据的一种容器。集合也被称为对象容器。 数组的特点 集合的特点 集合的大小不固定&#xff0c;启动后可以动态变化&#xff0c;类型也可以选择不固定。集合更像气球&#xff0c;可大可小。集合非常适合…

python与pycharm从零安装

python&#xff08;解释器&#xff09;下载地址&#xff1a;Welcome to Python.orgpycharm&#xff08;编译器&#xff09;下载地址&#xff1a;PyCharm: the Python IDE for Professional Developers by JetBrains一、python的下载与安装到官网后根据步骤下载安装包后&#xf…

xgboost:分割查找:Weighted Quantile Sketch

Weighted Quantile Sketch 专门处理流式和分布式加权数据集的一种分桶的方法 近似算法的一个重要步骤是提出候选分裂点。通常使用特征的百分位数来使候选数据均匀分布。形式上&#xff0c;设Dk(x1k,h1)&#xff0c;(x2k,h2)⋅⋅⋅(xnk,hn)D_k {(x_{1k}, h_1)&#xff0c;(x_…

Redis持久化:RDB、AOF

Redis持久化一. RDB(1) save(2) bgsave(3) 总结二. AOF(1) 重写优化(2) RDB和AOF的区别引入&#xff1a;Redis用内存存储数据&#xff0c;有数据丢失的问题&#xff1b; 一. RDB RDB&#xff08;Redis Database Bcakup file&#xff09;即Redis数据备份文件&#xff0c;或Red…

如何用 Python采集 <豆某yin片>并作词云图分析 ?

嗨害大家好鸭&#xff01;我是小熊猫~ 总有那么一句银幕台词能打动人心 总有那么一幕名导名作念念不忘 不知道大家有多久没有放松一下了呢&#xff1f; 本次就来给大家采集一下某瓣电影并做词云分析 康康哪一部才是大家心中的经典呢&#xff1f; 最近又有哪一部可能会成为…

拉链表详解

目录 一、拉链表概念 二、拉链表对应的业务需求 三、代码实现 3.1 数据初始化&#xff1a; 3.2 创建ods层增量表&#xff1a; 3.3 创建dwd层拉链表 3.4 数据更新 &#xff0c;将数据日期为2023-3-4的日期添加到拉链表中 3.4.1 先追加数据到ods层表 3.4.2 更新dwd层表数据 …

【SpringCloud】SpringCloud详解之Ribbon实战

目录前言SpringCloud Ribbon 负载均衡一需求二.RestTemplate远程调用配置负载均衡(order服务内修改)三.Ribbon实现负载均衡的原理四.Ribbon负载均衡策略1.负载均衡种类2.配置负载均衡(order服务中配置)五.Ribbon的饥饿加载配置(在order服务配置)前言 微服务中比如用户服务部署…

sklearn使用入门

文章目录1.机器学习1.1 机器学习简介1.2 有监督学习(supervised learning)1.3 无监督学习(unsupervised learning)1.4 半监督学习2. 机器学习工具SKlearn2.1 sklearn2.2 sklearn常用模块2.2.1 分类2.2.2 回归2.2.3 聚类2.2.4 降维2.2.5 模型选择2.2.6 数据预处理2.3 sklearn使用…

Android startActivityForResult()废弃了,代替方案案例

安卓项目compileSdk为32&#xff0c;在使用startActivityForResult()方法时发现Android studio提示此方法已经废弃了。 目前的代替方案案例。 // 确保 app 的 build.gradle中已经引入了androidx.appcompat:appcompat dependencies {implementation androidx.appcompat:appcomp…

操作系统之进程管理---每天一点点(春招加油呀)--知识点回顾(自问自答版本总结)

1.什么是进程&#xff1f;什么是线程&#xff1f;进程和线程的区别&#xff1f; 进程&#xff1a;资源分配和管理的基本单位 线程&#xff1a;程序执行的最小单位。 区别&#xff1a; 地址空间&#xff1a; 同一进程的所有线程共享本进程的地址空间&#xff0c;而不同的进程之间…

仓库拣货标签电子价格标签办公电子标牌

数字货架标签尺寸&#xff1a;2.13英寸、2.9英寸、4.2英寸、7.5英寸、10.2英寸技术&#xff1a;2.4G MHz无线通信技术&#xff0c;电子墨水显示。刷新20&#xff0c;000个标签/1小时/AP。3-5年的寿命&#xff0c;电池可以快速更换。ESL管理系统工作原理&#xff1a;实时显示最新…

【Maven】P3 依赖管理

依赖管理依赖配置依赖传送依赖具有传递性直接依赖与间接依赖依赖冲突可选依赖排除依赖依赖范围依赖配置 依赖指的是当前项目所需要的jar包&#xff0c;在 pom.xml 中可以看到 <!--设置当前项目所依赖的所有jar包--> <dependencies><!--设置一个具体的依赖jar-…

【CVPR2022】Class Re-Activation Maps for Weakly-Supervised Semantic Segmentation

论文标题&#xff1a;Class Re-Activation Maps for Weakly-Supervised Semantic Segmentation收录&#xff1a;CVPR 2022paper: https://arxiv.org/abs/2203.00962code: https://github.com/zhaozhengChen/ReCAM解读&#xff1a;https://zhuanlan.zhihu.com/p/478133151https:…

mx-font

Abstract 短镜头字体生成(FFG)方法必须满足两个目标:生成的图像既要保留目标字符的底层全局结构,又要呈现多样化的局部参考风格。现有的FFG方法旨在通过提取通用表示样式或提取多个组件样式表示来分离内容和样式。然而,以往的方法要么无法捕捉不同的本地风格,要么无法推广到…

初识系统移植

系统移植简单来说就是将准备好的内核&#xff08;OS&#xff09;移植到一台主机或者开发板中&#xff0c;类似于器官移植&#xff0c;将准备好的器官移植到人体中。 为什么需要系统移植&#xff1f; 没有内核的情况下&#xff0c;上层如果要控制硬件只能将C语言代码先编译成二…

【C++】栈和队列(stackqueue)介绍,实现,oj

&#x1f345;一文包教会&#xff0c;不再赘述栈最基本的结构和性质&#xff08;栈的基本介绍在这里&#xff09; &#xff08;队列基本介绍和实现&#xff09;,博主主页还有很多栈和队列oj题哦~ 目录 ☃️1.stack_list &#x1f41d;1.1 介绍 &#x1f41d;1.2 stack和list实…

人工智能|HCIA-AI V3.0(一)——人工智能概览

文章目录一 AI、机器学习、深度学习的关系二 AI的三个方面的应用2.1 计算机视觉2.2 语音处理2.3 自然语言处理三、争议四、未来展望一 AI、机器学习、深度学习的关系 人工智能:是研究、开发用于模拟、延伸和扩展人的智能的理论、方法及应用系统的一- 门新的技术科学。 机器学习…

【蓝桥杯集训15】求最短路存在负权边——spaf算法(3 / 4)

——SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称 单源最短路&#xff0c;且图中没有负环就可以用spfa 目录 spaf求最短路模板 852. spfa判断负环 341. 最优贸易 - spfa 双向建图 3305. 作物杂交 - spaf求最短路模板 只有当一个点的前驱结点更新了&#xff0c;…

多线程共享数据结构的无锁释放

目录背景问题共享结构的无锁释放对比ref-cntrcuepoch-based reclamhazard pointer: 冒险指针结构原理正确性保证范例参考背景 多线程共享一个数据结构。 共享数据结构&#xff0c;可以做到节约内存。 但是多线程共享&#xff0c;可能会有问题&#xff0c;比如同步的问题。 问…

(Android-RTC-9)PeerConnectionFactory

开篇前瞎扯。很久没发技术文章了&#xff0c;此文一直放着草稿箱没有完成&#xff0c;感觉自己在家庭和工作中找到了拖延的借口&#xff0c;开始慢慢变得懒惰了&#xff0c;那是万万不行的。恰逢2023开年ChatGPT的爆火&#xff0c;更让我这些普通程序员危机感瞬间飙升&#xff…