Android 系统中内存回收的触发点大致可分为三种情况:
第一种情况:用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖
第二种情况:按下Back键,会调用finishActivityLocked,然后把Activity的finishing标志设为true,然后再调用startPausingLocked,当目标actiity完成暂停后,就会通知Ams,此时Ams从completePaused开始执行,由于此时暂停的Activity的finising状态已经变为true,所以会执行finishingActivtyLocked。
第三种情况:启动一个新的应用程序。向Ams发送一个Idle消息,这会导致Ams开始执行activityIdleInternal方法,该方法首先处理mStoppingActivities中的对象,接着处理mFinishingActivities列表,然后再调用trimApplications
这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternalLocked().
1.当 ActivityManagerService 接收到异步消息 DLE_NOW_MSG 时 将会被调用。
2.当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 时将会被调用。
3.ActivityManagerService.的activityIdle中将会被调用
一,
1.IDLE_NOW_MSG 由 Activity 的切换以及 Activiy 焦点的改变等事件引发.
2.IDLE_TIMEOUT_MSG 在 Activity 启动超时的情况下引发,一般这个超时时间设为 10s,如果 10s 之内一个 Activity 依然没有成功启动,那么将发送异步消息 IDLE_TIMEOUT_MSG 进行资源回收。
文件路径:
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
private final class ActivityStackSupervisorHandler extends Handler {
。。。。。。。。。
void activityIdleInternal(ActivityRecord r) {
synchronized (mService) {
activityIdleInternalLocked(r != null ? r.appToken : null, true, null);
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
.......
case IDLE_TIMEOUT_MSG: {
if (DEBUG_IDLE) Slog.d(TAG_IDLE,
"handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj);
if (mService.mDidDexOpt) {
mService.mDidDexOpt = false;
Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);
nmsg.obj = msg.obj;
mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT);
return;
}
// We don't at this point know if the activity is fullscreen,
// so we need to be conservative and assume it isn't.
activityIdleInternal((ActivityRecord)msg.obj);
} break;
case IDLE_NOW_MSG: {
if (DEBUG_IDLE) Slog.d(TAG_IDLE, "handleMessage: IDLE_NOW_MSG: r=" + msg.obj);
activityIdleInternal((ActivityRecord)msg.obj);
} break;
}
}
3.activityIdle是在handleResumeActivity添加一个空闲任务,然后在looper线程空闲的时候调用
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
@Override
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
final long origId = Binder.clearCallingIdentity();
synchronized (this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
ActivityRecord r =
mStackSupervisor.activityIdleInternalLocked(token, false, config);
if (stopProfiling) {
if ((mProfileProc == r.app) && (mProfileFd != null)) {
try {
mProfileFd.close();
} catch (IOException e) {
}
clearProfilerLocked();
}
}
}
}
Binder.restoreCallingIdentity(origId);
}
主要就是检测当前activity栈是否为空,如果栈中有activity,那么就调用ActivityStactSupervisor.activityIdleInternalLocked方法
Z:\HLOS\frameworks\base\core\java\android\app\ActivityThread.java
调用ActivityManagerService的activityIdle方法在ActivityThread的内部handler Idler中。
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
if (mBoundApplication != null && mProfiler.profileFd != null
&& mProfiler.autoStopProfiler) {
stopProfiling = true;
}
if (a != null) {
mNewActivities = null;
IActivityManager am = ActivityManagerNative.getDefault();
ActivityClientRecord prev;
do {
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null && a.activity.mFinished));
if (a.activity != null && !a.activity.mFinished) {
try {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
if (stopProfiling) {
mProfiler.stopProfiling();
}
ensureJitEnabled();
return false;
}
}
这是Message队列的内部类闲置handler,这个queueIdle回调在消息队列中没有消息可以处理的空闲时期被调起,此前已经分析过了。
queueIdle()
return true,表示保留,当queueIdle执行完毕之后,不会移除这个IdleHandler
return false,表示这个IdleHandler不需要保留,也就是只需要执行一遍。
接着应该看Looper.myQueue().addIdleHandler()什么时候把Idler 添加进去的了
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
..............
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
也就是说,在activity执行resume方法之后,系统会在当前的线程中添加一个空闲任务。
这边需要科普一下,一般我们在调用了finish方法,或者是启动了一个新的应用或者是activity方法之后,当前的activity会处于后台,并且处于空闲,因此就会触发queueIdle的方法,从而触发AMS的activityIdle的方法。
二,
现在我们可以主要分析activityIdleInternalLocked方法了
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
ArrayList<ActivityRecord> finishes = null;
ArrayList<UserState> startingUsers = null;
int NS = 0;
int NF = 0;
boolean booting = false;
boolean activityRemoved = false;
ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
if (DEBUG_IDLE) Slog.d(TAG_IDLE, "activityIdleInternalLocked: Callers="
+ Debug.getCallers(4));
mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
r.finishLaunchTickingLocked();
if (fromTimeout) {
reportActivityLaunchedLocked(fromTimeout, r, -1, -1);
}
// This is a hack to semi-deal with a race condition
// in the client where it can be constructed with a
// newer configuration from when we asked it to launch.
// We'll update with whatever configuration it now says
// it used to launch.
if (config != null) {
r.configuration = config;
}
// We are now idle. If someone is waiting for a thumbnail from
// us, we can now deliver.
r.idle = true;
//Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
if (isFocusedStack(r.task.stack) || fromTimeout) {
booting = checkFinishBootingLocked();
}
}
//1.通知所有需要内存回收的进程进行内存回收(这些进程都保存在mProgressToGc列表中)
if (allResumedActivitiesIdle()) {
if (r != null) {
mService.scheduleAppGcsLocked();
}
if (mLaunchingActivity.isHeld()) {
mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
if (VALIDATE_WAKE_LOCK_CALLER &&
Binder.getCallingUid() != Process.myUid()) {
throw new IllegalStateException("Calling must be system uid");
}
mLaunchingActivity.release();
}
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
2. 分别拿到所有要stop和finish的activity存放在stops和finishs容器中,然后将记录清空
// Atomically retrieve all of the other things to do.
// 获取已经暂停的activity列表
final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(true);
NS = stops != null ? stops.size() : 0;
if ((NF = mFinishingActivities.size()) > 0) {
// 获取已经触发了finish方法的列表
finishes = new ArrayList<>(mFinishingActivities);
mFinishingActivities.clear();
}
if (mStartingUsers.size() > 0) {
startingUsers = new ArrayList<>(mStartingUsers);
mStartingUsers.clear();
}
// Stop any activities that are scheduled to do so but have been
// waiting for the next one to start.
for (int i = 0; i < NS; i++) {
r = stops.get(i);
final ActivityStack stack = r.task.stack;
if (stack != null) {
//如果该被暂停的activity已经调用了finish方法,那么就调用栈的finish当前的activity的方法
if (r.finishing) {
stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
} else {
// 否则调用栈的stopActivity方法
stack.stopActivityLocked(r);
}
}
}
// Finish any activities that are scheduled to do so but have been
// waiting for the next one to start.
// 遍历finish列表中的每一个activity,如果当前栈不为空,就去触发栈的destroyActivityLocked方法
for (int i = 0; i < NF; i++) {
r = finishes.get(i);
final ActivityStack stack = r.task.stack;
if (stack != null) {
activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
//并没有真正意义上改变内存的使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的 trimApplications() 函数中
}
}
if (!booting) {
// Complete user switch
if (startingUsers != null) {
for (int i = 0; i < startingUsers.size(); i++) {
mService.mUserController.finishUserSwitch(startingUsers.get(i));
}
}
}
mService.trimApplications();//真正开始杀进程回收
//dump();
//mWindowManager.dump();
if (activityRemoved) {
resumeFocusedStackTopActivityLocked(); //删除成功后恢复焦点堆栈activity的显示
}
return r;
}
一、scheduleAppGcsLocked()分析//通知各进程要GC
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
/**
* Schedule the execution of all pending app GCs.
*/
final void scheduleAppGcsLocked() {
mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG);
//从mProcessesToGc列表中取出下一个ProcessRecord ,并发送一个延迟消息,由performAppGcsIfAppropriateLocked()来执行
if (mProcessesToGc.size() > 0) {
// Schedule a GC for the time to the next process.
ProcessRecord proc = mProcessesToGc.get(0);
Message msg = mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG);
long when = proc.lastRequestedGc + GC_MIN_INTERVAL;
long now = SystemClock.uptimeMillis();
if (when < (now+GC_TIMEOUT)) {
when = now + GC_TIMEOUT;
}
mHandler.sendMessageAtTime(msg, when);
}
}
final class MainHandler extends Handler {
......
case GC_BACKGROUND_PROCESSES_MSG: {
synchronized (ActivityManagerService.this) {
performAppGcsIfAppropriateLocked();
}
} break;
......
}
/**
* If all looks good, perform GCs on all processes waiting for them.
*/
final void performAppGcsIfAppropriateLocked() {
if (canGcNowLocked()) {
performAppGcsLocked();
return;
}
// Still not idle, wait some more.
scheduleAppGcsLocked();
}
/**
* Returns true if things are idle enough to perform GCs.
*/
//判断是否足够闲置可以来执行GC
private final boolean canGcNowLocked() {
boolean processingBroadcasts = false;
for (BroadcastQueue q : mBroadcastQueues) {
if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) {
processingBroadcasts = true;
}
}
return !processingBroadcasts
&& (isSleepingLocked() || mStackSupervisor.allResumedActivitiesIdle());
}
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
boolean allResumedActivitiesIdle() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
if (!isFocusedStack(stack) || stack.numActivities() == 0) {
continue;
}
final ActivityRecord resumedActivity = stack.mResumedActivity;
if (resumedActivity == null || !resumedActivity.idle) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "allResumedActivitiesIdle: stack="
+ stack.mStackId + " " + resumedActivity + " not idle");
return false;
}
}
}
// Send launch end powerhint when idle
mService.mActivityStarter.sendPowerHintForLaunchEndIfNeeded();
return true;
}
/**
* Perform GCs on all processes that are waiting for it, but only
* if things are idle.
*/
final void performAppGcsLocked() {
final int N = mProcessesToGc.size();
if (N <= 0) {
return;
}
if (canGcNowLocked()) {
while (mProcessesToGc.size() > 0) {
ProcessRecord proc = mProcessesToGc.remove(0); //在mProcessesToGc列表中逐个取出每个需要进行gc的ProcessRecord对象,同时移除
if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) {
if ((proc.lastRequestedGc+GC_MIN_INTERVAL)
<= SystemClock.uptimeMillis()) {
// To avoid spamming the system, we will GC processes one
// at a time, waiting a few seconds between each.
//如果超过了最小时间间隔,则从mProcessesToGc列表中取出下一个app,并发送一个延迟消息
performAppGcLocked(proc);
scheduleAppGcsLocked();
return;
} else {
// It hasn't been long enough since we last GCed this
// process... put it in the list to wait for its time.
//先检查app上一次进行gc的时间,并和当前时间进行对比,如果还没超过最小间隔,则将指定的app 加入到mProcessesToGc列表中
addProcessToGcListLocked(proc);
break;
}
}
}
scheduleAppGcsLocked();
}
}
/**
* Ask a given process to GC right now.
*/
final void performAppGcLocked(ProcessRecord app) {
try {
app.lastRequestedGc = SystemClock.uptimeMillis();
if (app.thread != null) {
if (app.reportLowMemory) {
app.reportLowMemory = false;
app.thread.scheduleLowMemory();
} else {
app.thread.processInBackground();
}
}
} catch (Exception e) {
// whatever.
}
}
Z:\HLOS\frameworks\base\core\java\android\app\ActivityThread.java
@Override
public void scheduleLowMemory() {
sendMessage(H.LOW_MEMORY, null);
}
public void processInBackground() {
mH.removeMessages(H.GC_WHEN_IDLE);
mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
}
private class H extends Handler {
......
case LOW_MEMORY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory");
handleLowMemory();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
.......
}
final void handleLowMemory() {
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
final int N = callbacks.size();
for (int i=0; i<N; i++) {
callbacks.get(i).onLowMemory(); //回调该进程所包含的所有组件的onLowMemory方法
}
// Ask SQLite to free up as much memory as it can, mostly from its page caches.
if (Process.myUid() != Process.SYSTEM_UID) {
int sqliteReleased = SQLiteDatabase.releaseMemory(); //不是SYSTEM_UID就释放SQLite模块占用的内存。
EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
}
// Ask graphics to free up as much as possible (font/image caches)
Canvas.freeCaches(); //释放应用中所有的canvas对象
// Ask text layout engine to free also as much as possible
Canvas.freeTextLayoutCaches();//释放应用中所有的Text Layout
BinderInternal.forceGc("mem"); //释放该进程的Binder对象
}
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);//正常GC都是任务空闲时执行
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
MIN_TIME_BETWEEN_GCS = 5*1000;
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
// + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//两次GC间隔不能小于5S
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg"); //释放该进程的Binder对象
}
}
Z:\HLOS\frameworks\base\core\java\com\android\internal\os\BinderInternal.java
public static void forceGc(String reason) {
EventLog.writeEvent(2741, reason);
VMRuntime.getRuntime().requestConcurrentGC(); //虚拟机GC
}
二、trimApplications()分析 //真正回收内存
final void trimApplications() {
synchronized (this) {
int i;
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
//1.必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;
//2.必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;
//3.进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.toShortString() + " ("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
app.kill("empty", false);
} else {
try {
app.thread.scheduleExit();//pid为0说明已经杀过了,pid==MY_PID说明是当前system进程
} catch (Exception e) {
// Ignore exceptions.
}
}
cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
mRemovedProcesses.remove(i);
if (app.persistent) {
addAppLocked(app.info, false, null /* ABI override */);
}
}
}
// Now update the oom adj for all processes.
updateOomAdjLocked();
}
}
Z:\HLOS\frameworks\base\core\java\android\app\ActivityThread.java
public final void scheduleExit() {
sendMessage(H.EXIT_APPLICATION, null);
}
private class H extends Handler {
......
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate(); //This method is for use in emulated process environments
}
Looper.myLooper().quit(); //退出
break;
......
}
Z:\HLOS\frameworks\base\services\core\java\com\android\server\am\ProcessRecord.java
void kill(String reason, boolean noisy) {
if (!killedByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (noisy) {
Slog.i(TAG, "Killing " + toShortString() + " (adj " + setAdj + "): " + reason);
}
if(toShortString().contains("com.android.dialer")||toShortString().contains("com.android.server.telecom")){
Slog.i(TAG, "Killing return leihujun dialer");
return;
}
EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
Process.killProcessQuiet(pid);
ActivityManagerService.killProcessGroup(uid, pid);
if (!persistent) {
killed = true;
killedByAm = true;
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
1.mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死

Android 操作系统中的内存回收相关推荐

  1. JavaScript中的内存回收机制

    JS的内存回收 在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间. 以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器 ...

  2. android系统中与内存有关的文件及路径

    基于android4.4(MTK平台): 1.在分区的情况下: 设置-存储: StorageMeasurement.java alps\packages\apps\Settings\src\com\a ...

  3. threadlocal的set()方法中的内存回收

    ThreadLocal在执行set()方法的时候,实际执行set()逻辑的是其内部类ThreadLocalMap. private void set(ThreadLocal<?> key, ...

  4. Android操作系统中11种传感器的介绍【转】

    本文转载自:http://www.oschina.net/question/163910_28354 在Android2.3 gingerbread系统中,google提供了11种传感器供应用层使用. ...

  5. 操作系统中的内存分配

    数据类型对应字节数(32位,64位 int 占字节数) 一.程序运行平台        不同的平台上对不同数据类型分配的字节数是不同的.        个人对平台的理解是CPU+OS+Compiler ...

  6. android 内存回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管 ...

  7. Android 操作系统的内存回收机制

    转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的 ...

  8. Android 操作系统的内存回收机制。

    转载自品略网:http://www.pinlue.com/article/2020/03/0808/089994336918.html Android APP 的运行环境 Android 是一款基于 ...

  9. Android 操作系统的内存回收机制之默认内存回收、OOM以及lowmemorykiller

    Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制本文不涉及. 本章所有代码可参见 Activi ...

最新文章

  1. linux sshpass 非交互的ssh密码验证 简介
  2. 三种平摊分析的方法分别为_[2020.Vol.188]表征城市树木滞尘:一种景观分析方法...
  3. 原生JAVA的TCP/UDP编程
  4. Intel 64/x86_64/IA-32/x86处理器 - SIMD指令集 - SSE扩展(2) - SSE程序设计环境概述
  5. Oracle中一把梭获取对象DDL创建语句
  6. 寄存器和立即数和内存单元
  7. 计算机网络第七版课后习题答案(第二章)(20210628)
  8. 洛谷P6014 斗牛
  9. 一年工作经验,两周的面试,拿到几个offer的面试经验总结
  10. 上海镇保城保四金比例
  11. 国瀚实业|个人如何投资理财
  12. 手写Vue个人组件库——fl-Lazyimg 图片懒加载
  13. HTML/设置网页背景图片+背景透明度设置
  14. python画极坐标图_Python matplotlib绘制极坐标图
  15. 芯片的本质是什么?(4)物质与数字世界接口
  16. Alpha GO核心原理
  17. 分类模型评价指标说明
  18. FineReport帆软报错:很抱歉,数据集行数过多触发保护机制,请减少查询数据量。若您是管理员,可于智能运维-内存管理-模板限制中更改此项限制。
  19. C语言 交替符号累加计算
  20. OCR性能优化:从认识BiLSTM网络结构开始

热门文章

  1. (2022,MoCA)Few-shot 图像生成的原型记忆(Prototype Memory)和注意力机制
  2. R数据分析:生存分析与有竞争事件的生存分析的做法和解释
  3. 利用深度学习进行生存分析——DeepSuv模型小结
  4. SIGCOMM 2022 Elasticity Detection:A Building Block for Internet Congestion Control
  5. L25词嵌入进阶GloVe模型
  6. scratch,我有话说。(1)
  7. 一个大学生拿到电脑怎么办
  8. 2021年,小灰都做了哪些事?
  9. 使用阿里云管理控制台
  10. 松翰单片机keil环境芯片包