[LMKD] [Android] 进程OomAdj调整分析:Empty被Kill流程(4)

一.简要说明

二.源码分析

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java,入口是assignCachedAdjIfNecessaryupdateAndTrimProcessLSP方法

@GuardedBy({"mService", "mProcLock"})private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {final int numLru = lruList.size();// cacheAdj值默认起点是900int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;// 那么下一个cache进程,则在当前进程的adj基础上 + (10),例如:900 + (5 * 2) = 910// 所以cache进程的标志为:910,920,930,940等int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);int curCachedImpAdj = 0;// emptyADj值起点为:900 + 5 = 905int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;// 那么下一个empty进程,则在当前进程的adj基础上 + (10),例如:905 + (5 * 2) = 915// 所以分辨empty可以通过:905,915,925,935,945来分辨int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);// empty进程存在的上限:cache进程的上限CUR_MAX_CACHED_PROCESSES(32)/2 = 16个final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;// cache进程存在的上限:CUR_MAX_CACHED_PROCESSES(32)个 - emptyProcessLimit(16) = 16个final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES- emptyProcessLimit;// 所以cache进程数量上限是32个 - emptyProcessLimit的数量// emptyProcessLimit又是cache数量的一半// empty进程数量 = empty总数量 - 非cache - cache隐藏int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;...int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)/ mNumSlots;int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;// 遍历所有empty和cache进程for (int i = numLru - 1; i >= 0; i--) {ProcessRecord app = lruList.get(i);final ProcessStateRecord state = app.mState;// 如果进程adj值确实是 >=UNKNOWN_ADJ,说明是empty或cacheif (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()>= ProcessList.UNKNOWN_ADJ) {final ProcessServiceRecord psr = app.mServices;switch (state.getCurProcState()) {// 如果是cache进程,计算curCachedImpAdj的数量case PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:case ActivityManager.PROCESS_STATE_CACHED_RECENT:// Figure out the next cached level, taking into account groups....// 根据cache因子计算下一个cache进程的adj值if (!inGroup && curCachedAdj != nextCachedAdj) {stepCached++;curCachedImpAdj = 0;if (stepCached >= cachedFactor) {stepCached = 0;curCachedAdj = nextCachedAdj;**nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;**// 最大不能超过999if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}// 设置cache adj值state.setCurRawAdj(curCachedAdj + curCachedImpAdj);state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));break;default:// Figure out the next cached level.// 计算empty值if (curEmptyAdj != nextEmptyAdj) {stepEmpty++;if (stepEmpty >= emptyFactor) {stepEmpty = 0;curEmptyAdj = nextEmptyAdj;// 计算下一次empty值nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}state.setCurRawAdj(curEmptyAdj);state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));break;}}}}

所以以上方法的主要作用就是处理empty和cache进程的adj值

  • empty的起点是905,每次进程的间隔是10,例如915,925,935
  • cache的起点是900,每次进程的间隔是10,例如910,920,930

然后约束其不能超过999最大值,然后继续设置下一次Empty/Cache的Adj值间隔+=10(因为这是遍历所有cache/Empty进程),然后设置adj值即可,所以总结一下:设置empty和cache进程adj值起点,防止每个进程间隔太近导致同时被kill,造成卡顿等情况,循环遍历每个进程,每个进程必须间隔10,且用步进为5来区分是cache还是empty,并约束不能超过999的最大adj值,然后调用setCurRawAdj和setCurAdj设置最终值即可

继续看updateAndTrimProcessLSP方法,该方法用于kill这些进程

  	@GuardedBy({"mService", "mProcLock"})private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,final long oldTime, final ActiveUids activeUids) {// 获取所有进程ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();final int numLru = lruList.size();// 获取empty和cache进程数量上限,已经讲过了// emptyProcessLimit:32 / 2 = 16// cachedProcessLimit:32 - emptyProcessLimit = 16final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES- emptyProcessLimit;int lastCachedGroup = 0;int lastCachedGroupUid = 0;int numCached = 0;int numCachedExtraGroup = 0;int numEmpty = 0;int numTrimming = 0;// 遍历所有进程for (int i = numLru - 1; i >= 0; i--) {ProcessRecord app = lruList.get(i);final ProcessStateRecord state = app.mState;// 要求进程存在,即ActivityThread存在if (!app.isKilledByAm() && app.getThread() != null) {// We don't need to apply the update for the process which didn't get computedif (state.getCompletedAdjSeq() == mAdjSeq) {applyOomAdjLSP(app, true, now, nowElapsed);}// 获取该进程的所有服务final ProcessServiceRecord psr = app.mServices;// Count the number of process types.switch (state.getCurProcState()) {// 如果进程状态是cachedcase PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:mNumCachedHiddenProcs++;// cached进程数量++numCached++;final int connectionGroup = psr.getConnectionGroup();if (connectionGroup != 0) {if (lastCachedGroupUid == app.info.uid&& lastCachedGroup == connectionGroup) {// If this process is the next in the same group, we don't// want it to count against our limit of the number of cached// processes, so bump up the group count to account for it.// 如果这个进程是和上个进程属于同一个组内,我们不希望它计入我们缓存进程数量的限制numCachedExtraGroup++;} else {lastCachedGroupUid = app.info.uid;lastCachedGroup = connectionGroup;}} else {lastCachedGroupUid = lastCachedGroup = 0;}// 如果cached进程数量 > 16个则进行主动kill// ,要排除numCachedExtraGroup(因为目前的进程和上一个进程属于同一个组))if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {app.killLocked("cached #" + numCached,ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,true);}break;case PROCESS_STATE_CACHED_EMPTY:// 如果是empty进程// 如果empty进程数量 > (MAX_CACHED_PROCESSES(32) / 2) / 2 = 8并且该进程最近活跃时间在30以内,则进行主动kill// 一般empty只允许存活30分钟,但如果超过了8个也主动kill,所以empty进程超过30分钟,但在8个以内则不会被杀if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES&& app.getLastActivityTime() < oldTime) {app.killLocked("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME- app.getLastActivityTime()) / 1000) + "s",ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_TRIM_EMPTY,true);} else {// 会先执行else来计算empty进程数量numEmpty++;// 如果进程数量超过16个,不解释,直接killif (numEmpty > emptyProcessLimit) {app.killLocked("empty #" + numEmpty,ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,true);}}break;default:mNumNonCachedProcs++;break;}// 如果是孤立进程且没有服务在运行,则也直接killif (app.isolated && psr.numberOfRunningServices() <= 0&& app.getIsolatedEntryPoint() == null) {// If this is an isolated process, there are no services// running in it, and it's not a special process with a// custom entry point, then the process is no longer// needed.  We agressively kill these because we can by// definition not re-use the same process again, and it is// good to avoid having whatever code was running in them// left sitting around after no longer needed.app.killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);} else {// Keeping this process, update its uid.updateAppUidRecLSP(app);}// 大于>home基本上都是cache或者empty进程if (state.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME&& !app.isKilledByAm()) {// 会记录这种进程的次数,此时还不做处理numTrimming++;}}}mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(activeUids);// 传递cached进程数量,empty数量,numTrimming数量,决定内存处于哪个阶段等级return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);}

所以以内逻辑可以分为:

  • 获取cached进程和empty进程的limit数量限制,都为16个
  • 遍历所有进程,如果发现上一个进程和该进程是同一个组内,则排除这种进程被kill(只排除cached进程类别)
  • 首先处理cached进程,如果cached进程数量超过了16个则直接kill
  • 其次处理empty进程,如果数量超过16个则直接kill,如果进程数量没超过16个,但是进程活跃时间超过30分钟并且进程数量大于了8个,也会被kill—也就是empty进程数量超过8个,并且当前进程30分钟内没有活跃会被kill
  • 然后进入updateLowMemStateLSP调整内存等级

三.内存水平决策分析

frameworks/base/services/core/java/com/android/server/am/AppProfiler.java,主要分析updateLowMemStateLSP

	@GuardedBy({"mService", "mProcLock"})boolean updateLowMemStateLSP(int numCached/*cached数量*/, int numEmpty/*empty数量*/, int numTrimming/*进程>=PROCESS_STATE_HOME的数量*/) {final long now = SystemClock.uptimeMillis();// 内存因子int memFactor;// 如果kernel支持mLowMemDetector功能,才会有mLowMemDetector对象存在// 如果mLowMemDetector存在,默认内存因子为ADJ_MEM_FACTOR_NORMAL(0)if (mLowMemDetector != null && mLowMemDetector.isAvailable()) {memFactor = mLowMemDetector.getMemFactor();} else {// 讨论此类情况// 如果cached进程数量 <= (MAX_CACHED_PROCESSES(32) - 16) / 3 = 5个(因为是int)// 并且empty数量 <= 16 / 2 = 8个才会去调整内存因子,内存因子越大,内存压力越大if (numCached <= mService.mConstants.CUR_TRIM_CACHED_PROCESSES&& numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) {// 计算empty+cached总进程数量final int numCachedAndEmpty = numCached + numEmpty;// 如果总数量 <= 3---微调临界阈值,则内存因子为3if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {memFactor = ADJ_MEM_FACTOR_CRITICAL;// 如果总数量 <= 5,则赋予5,所以数量在3~5个,则内存因子为2} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {memFactor = ADJ_MEM_FACTOR_LOW;} else {// 其他情况,为1memFactor = ADJ_MEM_FACTOR_MODERATE;}} else {// 如果进程数量大于了规定的5 + 8个,则赋予0memFactor = ADJ_MEM_FACTOR_NORMAL;}}...// 记录当前的内存因子为最新调整的一次mLastMemoryLevel = memFactor;mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();boolean allChanged;int trackerMemFactor;synchronized (mService.mProcessStats.mLock) {// 将内存因子保存到ProcessStats中或ServiceState中allChanged = mService.mProcessStats.setMemFactorLocked(memFactor,mService.mAtmInternal == null || !mService.mAtmInternal.isSleeping(), now);// 获取上次的内存因子trackerMemFactor = mService.mProcessStats.getMemFactorLocked();}// 如果内存因子不属于正常情况,进入低内存状态if (memFactor != ADJ_MEM_FACTOR_NORMAL) {if (mLowRamStartTime == 0) {mLowRamStartTime = now;}// 内存水平int fgTrimLevel;// 通过内存因子,得到内存TrimLevel等级switch (memFactor) {// 如果内存因子是3,则fgTrimLevel为15case ADJ_MEM_FACTOR_CRITICAL:// 您正在运行的进程应该释放尽可能多的非关键资源,以便将内存用于其他地方fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;break;case ADJ_MEM_FACTOR_LOW: // 如果内存因子为2,则fgTrimLevel为10// 您正在运行的进程应该释放不需要的资源,以便将内存用于其他地方。fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;break;default:// 您正在运行的进程可能希望释放一些不需要的资源,以便在其他地方使用,fgTrimLevel为5fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;break;}...// 最大内存等级,80final int[] curLevel = {ComponentCallbacks2.TRIM_MEMORY_COMPLETE};// 遍历所有进程mService.mProcessList.forEachLruProcessesLOSP(true, app -> {final ProcessProfileRecord profile = app.mProfile;// 获取该进程的内存水平final int trimMemoryLevel = profile.getTrimMemoryLevel();final ProcessStateRecord state = app.mState;// 获取进程状态final int curProcState = state.getCurProcState();IApplicationThread thread;// 如果进程状态>=home,代表进程状态不是很好if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {...// 默认设置最大内存水平thread.scheduleTrimMemory(curLevel[0])} else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT&& !app.isKilledByAm()) {...thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);} else {if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND|| state.isSystemNoUi()) && profile.hasPendingUiClean()) {thread.scheduleTrimMemory(level);}if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {try {if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName+ " to " + fgTrimLevel);}thread.scheduleTrimMemory(fgTrimLevel);} catch (RemoteException e) {}}profile.setTrimMemoryLevel(fgTrimLevel);}});} else {// 内存水平正常的情况,代表内存不紧张的情况mService.mProcessList.forEachLruProcessesLOSP(true, app -> {final ProcessProfileRecord profile = app.mProfile;final IApplicationThread thread;final ProcessStateRecord state = app.mState;if (allChanged || state.hasProcStateChanged()) {mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);state.setProcStateChanged(false);}if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND|| state.isSystemNoUi()) && profile.hasPendingUiClean()) {thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);}profile.setTrimMemoryLevel(0);});}return allChanged;}

以上就是根据cached进程数量和empty进程数量来确定内存因子,再由内存因子决定内存水平(fgTrimLevel),然后再根据进程状态调用setTrimMemoryLevelscheduleTrimMemory设置不同的内存水平。

当前的cachedProcess和emptyProcess进程的数量来综合判定当前进程的等内存级,这两类进程的数量越少, 表示系统内存越紧张,内存等级越高。

lowmemeorykiller的机制又会在系统可用内存不足时杀死这些进程, 所以在后台进程和空进程数量少于一定数量时,便表示了系统以及触发了lowmemrorykiller的机制,即表示当前系统内存的紧张程度

总结:后台进程与空进程越多表示内存越富裕,越少表示内存越紧张,这些内存level会传递给app,如果activity重写了OnTrimMemory,则会将当前level传递给activity,开发者可以根据level情况来释放资源,然后内存level也会传递给WindowManagerGlobal#trimMemory,如果内存level≥60,则销毁与已知窗口关联的所有surface和layer,以及资源也会被销毁,但进程可能仍然存在,不过再次进入该进程,需要重新加载资源了

三.参数介绍

内存因子参数

  1. public static final int ADJ_MEM_FACTOR_NORMAL = 0;表示正常的内存等级,内存不紧张,通常empty和cached进程数量较多的情况下会是正常
  2. public static final int ADJ_MEM_FACTOR_MODERATE = 1;表示存在6~13个empty&&cached进程,内存紧张水平不算太高—不是很紧张
  3. public static final int ADJ_MEM_FACTOR_LOW = 2;表示存在4~5个empty&&cached进程,属于低内存状态了
  4. public static final int ADJ_MEM_FACTOR_CRITICAL = 3;表示存在1~3个empty&&cached进程,也属于低内存状态

内存因子对应的内存level介绍

  1. static final int TRIM_MEMORY_COMPLETE = 80;最大内存水平,代表内存非常紧张,表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能地把一切可以释放的东西都进行释放**,如果进程状态≥Home,会设置这个状态**
  2. static final int TRIM_MEMORY_MODERATE = 60;表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险。如果进程状态≥Home,会设置这个状态
  3. static final int TRIM_MEMORY_BACKGROUND = 40;表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足,从而让我们的程序更长时间地保留在缓存当中,这样当用户返回我们的程序时会感觉非常顺畅,而不是经历了一次重新启动的过程。如果进程状态≥Home,会设置这个状态并且进程状态为PROCESS_STATE_HEAVY_WEIGHT的时候会设置
  4. static final int TRIM_MEMORY_UI_HIDDEN = 20;表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见.这时候应该释放一些资源。如果进程状态≥PROCESS_STATE_IMPORTANT_BACKGROUND,会设置这个水平
  5. static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了部分缓存的进程了。这个时候我们应当尽可能地去释放任何无用的资源,不然的话系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务,该如果内存因子为ADJ_MEM_FACTOR_CRITICAL,会设置这个水平
  6. static final int TRIM_MEMORY_RUNNING_LOW = 10;表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能。该如果内存因子为*ADJ_MEM_FACTOR_LOW*,会设置这个水平
  7. static final int TRIM_MEMORY_RUNNING_MODERATE = 5;表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了,该水平对应了内存因子的:default,只要不是ADJ_MEM_FACTOR_CRITICALADJ_MEM_FACTOR_LOW默认内存因子对应的水平都是这个

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

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

相关文章

2023年中国体育赛事行业现状及趋势分析:体育与科技逐步融合,推动产业高质量发展[图]

体育赛事运营是指组织体育赛事或获取赛事版权&#xff0c;并进行赛事推广营销、运营管理等一系列商业运作的运营活动。体育赛事运营相关业务主要包括赛事运营与营销、赛事版权运营两个部分。 体育赛事运营行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#x…

MySQL面试题合集

MySQL面经知识整理 文章目录 MySQL面经知识整理一、查询相关1.什么是MySQL的连接查询&#xff0c;左连接&#xff0c;右连接&#xff0c;内外连接2.SQL慢查询优化的方法3.大表查询如何优化 二、索引相关1.在MySQL中,可以通过哪些命令来查看查询是否使用了索引2.MySQL的最左匹配…

实验三十四、串联型稳压电路参数的选择

一、题目 电路如图1所示。已知输入电压为 50 Hz 50\,\textrm{Hz} 50Hz 的正弦交流电&#xff0c;来源于电源变压器副边&#xff1b;输出电压调节范围为 5 ∼ 20 V 5\sim20\,\textrm V 5∼20V&#xff0c;满载为 0.5 A 0.5\,\textrm A 0.5A&#xff1b; C 3 C_3 C3​ 为消振…

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

结构和基本尺寸

声明 本文是学习GB-T 586-2015 船用法兰铸钢止回阀. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了法兰连接尺寸和密封面按 CB/T 4196、GB/T 2501 的船用法兰铸钢止回阀(以下简 称止回阀)的分类和标记、要求、试验方法、检验规…

使用Java操作Redis

要在Java程序中操作Redis可以使用Jedis开源工具。 一、jedis的下载 如果使用Maven项目&#xff0c;可以把以下内容添加到pom中 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId>…

【LeetCode热题100】--114.二叉树展开为链表

114.二叉树展开为链表 方法一&#xff1a;对二叉树进行先序遍历&#xff0c;得到各个节点被访问到的顺序&#xff0c;利用数组存储下来&#xff0c;然后在先序遍历之后更新每个节点的左右节点的信息&#xff0c;将二叉树展开为链表 /*** Definition for a binary tree node.* …

Vue+ElementUI实现动态树和表格数据的分页模糊查询

目录 前言 一、动态树的实现 1.数据表 2.编写后端controller层 3.定义前端发送请求路径 4.前端左侧动态树的编写 4.1.发送请求获取数据 4.2.遍历左侧菜单 5.实现左侧菜单点击展示右边内容 5.1.定义组件 5.2.定义组件与路由的对应关系 5.3.渲染组件内容 5.4.通过动态…

FFmpeg 命令:从入门到精通 | ffmpeg filter(过滤器 / 滤镜)

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09; FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09;ffmpeg fliter 基本内置变量视频裁剪文字水印图片水印画中画视频多宫格处理 FFmpeg 命…

希尔排序(C++实现)

文章目录 前言1. 基础概念2. 动图演示3. 代码实现4. 排序过程5. 效率分析6. 总结 前言 上篇文章讲了直接插入排序算法。 首先&#xff0c;在待排序的数组中&#xff0c;元素本身就是有序的情况下&#xff0c;就不需要移动任何元素&#xff0c;所以直接插入排序最好情况时间复…

进程调度的时机,切换与过程以及方式

1.进程调度的时机 进程调度&#xff08;低级调度〉&#xff0c;就是按照某种算法从就绪队列中选择一个进程为其分配处理机。 1.需要进行进程调度与切换的情况 1.当前运行的进程主动放弃处理机 进程正常终止运行过程中发生异常而终止进程主动请求阻塞&#xff08;如等待l/O)…

数据结构与算法-顺序表

数据结构与算法 &#x1f388;1.线性表&#x1f50e;1.1基本操作&#x1f50e;1.2线性表的存储结构 &#x1f388;2.线性表的顺序表示和实现&#x1f50e;2.1线性表的顺序存储表示&#x1f52d;2.1.1静态顺序表&#x1f52d;2.1.2动态顺序表 &#x1f50e;2.2顺序表基本操作的实…

MYSQL8解压版 windows 主从部署步骤及配置(包含配置文件,教程文件,免积分下载)

MYSQL8解压版 windows 主从部署步骤及配置 一.安装MSYQL 这里只讲大概,详细步骤、my.ini文件、安装包等会在页尾文件中(正常情况按首个mysql安装,只是名字有区别) 1.主库my.ini配置 [mysqld] #典型的值是5-6GB(8GB内存)&#xff0c;8-11GB(16GB内存), 20-25GB(32GB内存)&…

阿里云对象存储OSS SDK的使用

官方文档 https://help.aliyun.com/zh/oss/developer-reference/java 准备工作 windows安装好JDK&#xff0c;这里使用JDK1.8为例 windows安装好IDEA&#xff0c;这里使用IDEA2022 登录阿里云控制台&#xff0c;通过免费试用OSS或开通OSS 步骤 配置访问凭证 有临时和长期…

【Go】go-es统计接口被刷数和ip访问来源

go-es模块统计日志中接口被刷数和ip访问来源 以下是使用go的web框架gin作为后端&#xff0c;展示的统计页面 背景 上面的数据来自elk日志统计。因为elk通过kibana进行展示&#xff0c;但是kibana有一定学习成本且不太能满足定制化的需求&#xff0c;所以考虑用编程的方式…

mysql-binlog

1. 常用的binlog日志操作命令 1. 查看bin-log是否开启 show variables like log_%;2. 查看所有binlog日志列表 show master logs;3.查看master状态 show master status;4. 重置&#xff08;清空&#xff09;所有binlog日志 reset master;2. 查看binlog日志内容 1、使用mysqlb…

竞赛 机器视觉人体跌倒检测系统 - opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器视觉人体跌倒检测系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&…

腾讯云服务器完整建站过程(新手搭建网站教程)

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网分享使用腾讯云服务器建站教程&#xff0c;新手站长搭…

接口测试复习

一。基本概念 接口概念&#xff1a;系统与系统之间 数据交互的通道。 接⼝测试概念&#xff1a;校验 预期结果 与 实际结果 是否⼀致。 特征&#xff1a; 测试⻚⾯测试发现不了的问题。&#xff08;因为&#xff1a;接⼝测试 绕过前端界⾯。 &#xff09; 符合质量控制前移理…

ChainForge:衡量Prompt性能和模型稳健性的GUI工具包

ChainForge是一个用于构建评估逻辑来衡量模型选择&#xff0c;提示模板和执行生成过程的GUI工具包。ChainForge可以安装在本地&#xff0c;也可以从chrome浏览器运行。 ChainForge可以通过聊天节点对多个对话可以使用不同的llm并行运行。可以对聊天消息进行模板化&#xff0c;并…