性能优化|记一次线上OOM问题处理
概述
最近线上监控发现 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 为例:
监听 Activity 的生命周期。
在 onDestory 的时候,通过 ReferenceQueue 来创建对应 Actitity 的 Refrence 引用对象。
一段时间后,从 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性能优化概述
第二章:卡顿优化
第三章:启动速度优化
第四章:内存优化
第五章:布局优化
第六章:线程优化
第七章:电量优化
第八章:稳定性优化
相关文章:
性能优化|记一次线上OOM问题处理
概述最近线上监控发现 OOM 涨幅较大,因此去尝试定位和修复这个问题,在修复了一些内存泄漏和大对象占用问题后, OOM 依旧未达到正常标准,在这些新上报的 hprof 文件中,发现几乎所有 case 中都有个叫 FinalizerReference 的对象&…...
Vue动态粒子特效插件(背景线条吸附动画)
目录 效果图: 一、安装: 二、引入 main.js 文件: 三、使用: 四、属性说明: 效果图: 一、安装: npm install vue-particles --save 二、引入 main.js 文件: import VueParticles…...
【Java 类】002-类、属性、方法、代码块
【Java 类】002-类、属性、方法、代码块 文章目录【Java 类】002-类、属性、方法、代码块一、类1、类与对象2、类的作用3、创建与使用类类结构创建类调用类运行结果4、Java 类的执行过程5、封装、继承、多态、抽象类、内部类、接口、枚举、记录、注解等二、属性1、概述2、类型3…...
Ubuntu Linux 编译安装的基本步骤
文章目录1 基本步骤若报错: No such file or directory2 解压 tar.bz2文件参考:1 基本步骤 解压: tar -zxvf file.tar.gz 进入解压后的文件夹: cd file 源码编译安装 ./configure # ./configmakesudo make install 若报错&…...
day59反刍笔记
1.本地环境安装vue后,没有vue.js文件,只有vue.cjs.js文件, 引用后也无法正常使用,看npm install vue后的文件夹中没有vue.js文件_找不到vue.js_一枝风的博客-CSDN博客 老哥的博客后得到启发,将原本的命令由࿱…...
【阅读笔记】你不知道的Javascript--强制类型转换4
目录强制类型转换基本概念JSONboolean强转归纳其他知识点被诟病的安全使用隐式强转法则抽象关系比较语法表达式变动强制类型转换 基本概念 类型转换发生在静态类型语言的编译阶段; 强制类型转换则发生在动态类型语言的运行时(runtime) JSON…...
华为OD机试真题Python实现【有效子字符串】真题+解题思路+代码(20222023)
有效子字符串 题目 输入两个字符串S和L,都只包含小写字母, S长度 <= 100,L长度 <= 500000, 判断S是否是L的有效子字符串, 判定规则:S中的每个字符在L中都能找到(可以不连续) 且S在L中字符的前后顺序与S中顺序要保持一致 例如: S="ace"是L="abcd…...
上门按摩预约APP源码-东郊到家源码(开发,PHP,平台搭建)
一、什么是上门按摩预约APP源码? 上门按摩预约APP源码是一款家政服务类型的APP,可以帮忙用户在家就能享受按摩的服务。APP源码分两端,一端是用户端,另外一端是技师端。采用的技术,前端是安卓IOS,后端是PHP&…...
STL讲解——模拟实现vector
STL讲解——模拟实现vector vector深度剖析 在STL源码中,发现vector定义的并不是 start、size、capacity,而是start、finish、end_of_storage. 这样就可以得到size()和capacity()。 sizefinish-start capacityend_of_storage-start 扩容可能是本地扩容也…...
各种经典排序算法介绍及实现源码
一,冒泡排序(Bubble Sort) 排序算法是程序员必须了解和熟悉的一类算法,排序算法有很多种,基础的如:冒泡、插入、选择、快速、归并、计数、基数和桶排序等。 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不…...
历史大讲堂:这是真·图形化 苹果系统历史回顾(上)
众所周知,米国有个非常牛掰的公司叫苹果,想必大家对这个logo不陌生吧。 目前已发布的苹果产品有iPhone、iPad、iPod等等,简直花样繁多,而且各种功能很好用,我的手机就是一部苹果iPhone X。 等一下,似乎扯远…...
今天女神节,用python画个贺卡送给母亲吧
今天女神节,你给女神妈妈准备了什么祝福呢?如果还没有,那么画个贺卡送给她吧,在你眼里,她是一个什么样的人呢? 是"可爱",“温柔”,“美丽”,“漂亮”…...
【编程基础之Python】11、Python中的表达式
【编程基础之Python】11、Python中的表达式Python中的表达式表达式与运算符算术表达式赋值表达式比较表达式逻辑表达式位运算表达式总结Python中的表达式 在Python中,表达式是由操作数、运算符和函数调用等组成的语法结构,可以进行各种数学运算、逻辑判…...
华为OD机试真题Python实现【乱序整数序列两数之和绝对值最小】真题+解题思路+代码(20222023)
乱序整数序列两数之和绝对值最小 题目 给定一个随机的整数数组(可能存在正整数和负整数)nums, 请你在该数组中找出两个数,其和的绝对值(|nums[x]+nums[y]|)为最小值 并返回这两个数(按从小到大返回)以及绝对值。 每种输入只会对应一个答案。但是,数组中同一个元素不能使用两…...
字符串转换整数 (atoi)(python)
链接: https://leetcode.cn/problems/string-to-integer-atoi 题目描述: 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C 中的 atoi 函数)。 函数 myAtoi(string s) 的算法…...
洛谷 P1115 最大子段和
题目链接:P1115 最大子段和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数,表示序列的长度 n。 第二行有 n 个整数ÿ…...
【Linux】-- 权限和Shell运行原理
目录 Shell的运行原理 用户切换 su - / su sudo 权限 chmod chown chgrp 八进制方法修改文件属性 目录权限 粘滞位 umask 自定义默认权限 Shell的运行原理 广义上,Linux发行版 Linux内核 外壳程序 Linux 从广义上来理解它是一个操作系统 而从狭义上…...
C++各类设计模式及实现详解
软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累。最近看设计模式的书࿰…...
【Linux】进程理解与学习(Ⅰ)
环境:centos7.6,腾讯云服务器Linux文章都放在了专栏:【Linux】欢迎支持订阅🌹相关文章推荐:【Linux】冯.诺依曼体系结构与操作系统进程概念什么是进程?进程是什么?我们打开任务管理器可以看到有…...
认识代码之前,请先认识你自己 |《编程人生》
这是我的湛庐课程《给技术人的职场突围课》 (链接) 的一部分。 这篇文章也是 IT 女神征文活动 的一部分。 《编程人生》是一本优秀程序员的采访集,里面记录了15位世界级编程大师的故事。 我在 发刊词 里面说过,在这个书单课里&am…...
react学习笔记-5:react路由
react旧版本路由 旧版本的路由是按照组件的方式来写的 编写router/index.tsx文件 import App from "../App" import Home from "../views/Home" import About from "../views/About" import { BrowserRouter,Routes,Route } from "react…...
[Python图像处理] 使用高通滤波器实现同态滤波
使用高通滤波器实现同态滤波同态滤波基础实现同态滤波相关链接同态滤波基础 同态滤波是一种去除图像中乘性噪声的技术,常用于校正图像中的不均匀照明。根据图像形成的光照反射模型,图像 f(x,y)f(x,y)f(x,y) 可以由以下两个分量表征: 入射到…...
PyTorch深度学习:60分钟入门
PyTorch深度学习:60分钟入门 本教程的目的: 更高层级地理解PyTorch的Tensor库以及神经网络。训练一个小的神经网络来对图像进行分类。 本教程以您拥有一定的numpy基础的前提下展开 Note: 务必确认您已经安装了 torch 和 torchvision 两个包。 这是一个基于Pytho…...
C语言指针常见问题汇总
我们在学C语言时,指针是我们最头疼的问题之一,针对C语言指针,博主根据自己的实际学到的知识以及开发经验,总结了以下使用C语言指针时常见问题。 1、指针做函数参数 学习函数的时候,讲了函数的参数都是值拷贝…...
Coremail邮件系统全新上线存档邮箱功能
邮箱积累邮件太多,搜索起来又慢又麻烦! 我的重要邮件忘记下载丢失了!14天自动删除太难了! 有没有可能重要邮件自动存档,解救一下“遗忘星”人? 在我们日常工作中,邮件是最经常使用的办公工具之一…...
Python绘图
1.二维绘图 a. 一维数据集 用 Numpy ndarray 作为数据传入 ply 1. import numpy as np import matplotlib as mpl import matplotlib.pyplot as pltnp.random.seed(1000) y np.random.standard_normal(10) print "y %s"% y x range(len(y)) print "x%s&q…...
【独家】华为OD机试 - 第K个最小码值的字母(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试…...
整数反转(python)
题目链接: https://leetcode.cn/problems/reverse-integer/ 题目描述: 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231,231−1][−2^{31}, 2^{31} − 1][−231,231…...
【数据结构】二叉树与堆
文章目录1.树概念及结构1.1树的相关概念1.2树的结构2.二叉树概念及结构2.1相关概念2.2特殊的二叉树2.3二叉树的性质2.4二叉树的存储结构3.二叉树的顺序结构及实现3.1二叉树的顺序结构3.2堆的概念3.3堆的实现Heap.hHeap.c3.4堆的应用3.4.1 堆排序3.4.2 TOP-KOJ题最小K个数4.二叉…...
Git图解-常用命令操作-可视化
目录 一、前言 二、初始化仓库 2.1 设置用户名与邮箱 2.2 初始化仓库 三、添加文件 四、查看文件状态 五、查看提交日志 六、查看差异 七、版本回退 八、删除文件 九、分支管理 9.1 创建分支 9.2 切换分支 9.3 查看分支 9.4 合并分支 十、文件冲突 十一、转视…...
支付网站建设推广的会计分录/企业网站seo公司
TFTP(Trivial File Transfer Protocol,简单文件传输协议),是一个基于 UDP 协议实现的用于在客户机和服务器之间进行简单文件传输的协议,适合于开销不大、不复杂的应用场合。TFTP 协议专门为小文件传输 而设计ÿ…...
网站建设佰首选金手指二七/长春百度seo公司
原始地址:http://www.myhack58.com/Article/sort099/sort0100/2015/62781.htm 一、生成密钥 现在我们通过xshell生成密钥,注意:本章节,我只进行截图,不做进一步的文章说明。 如下: 我们现在有了公钥…...
个人网页设计师/长沙百度快速优化排名
Ⅰ、概述 打开上一篇文章新建的工程,是提取的ST标准库里面源代码文件和UCOS工程包源代码文件。下载过的朋友可能会知道,直接编译那个工程会有大片的错误和警告,原因在于那个工程是没有经过修改源代码的工程,接下来就是讲述一步一步…...
门户网站建设方法/短视频seo询盘获客系统
芯片支持库(CSL)提供了一个用于配置和控制片上外设的C语言接口。它有各个分立的模块组成,并被编译成为库文件。每个模块对应一个单独的外设,除了个别提供通用程序支持的模块。使用CSL可以方便片上外设的使用,缩短开发周…...