【图解八股-操作系统篇③-内存管理/其他】

作者简介和专栏内容见专栏介绍:https://www.nowcoder.com/creation/manager/columnDetail/0eL5bM

麻烦看到贴子的伙伴点点赞大家点赞订阅支持下,提前祝各位offer多多,有问题评论区见~~

图解版

内存管理

虚拟内存:举单片机例子讲为什么有虚拟内存,如何管理虚拟地址和物理地址

例子

为了防止多进程运行时造成的内存地址冲突,内核引入了虚拟内存地址

虚拟技术你了解吗? (虚拟内存)

虚拟技术把一个物理实体转换为多个逻辑实体。

主要有两种虚拟技术:时(时间)分复用技术和空(空间)分复用技术。

多进程与多线程:多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占用处 理器,每次只执行一小个时间片并快速切换。

虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。

虚拟内存的目的是什么?

虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。

为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。

虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说 一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。 例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。

  • 第一,虚拟内存可以使得进程对运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。
  • 第二,由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的。进程也没有办法访问其他进程的页表,所以这些页表是私有的,这就解决了多进程之间地址冲突的问题。
  • 第三,页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。

内存管理机制:虚拟内存---物理内存

为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套虚拟地址空间,每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。作为程序,也不用关心物理地址的事情。

每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。

那既然有了虚拟地址空间,那必然要把虚拟地址「映射」到物理地址,这个事情通常由操作系统来维护。

在Linux中,虚拟内存和物理内存之间的映射是通过页表来实现的。Linux将虚拟内存地址空间划分为固定大小的页(通常为4KB),并将物理内存划分为相同大小的页帧。虚拟内存中的每个页都映射到物理内存中的一个页帧,这个映射关系由页表来维护。

当进程访问虚拟内存中的某个页时,Linux首先检查该页是否已经在物理内存中,如果是,则直接访问物理内存中的对应页帧;如果不是,则触发页面调度(Page Fault)机制,将该页从磁盘中读入到物理内存中,并建立虚拟内存页和物理内存页帧之间的映射关系,然后再次尝试访问该页,这时就可以直接访问物理内存中的对应页帧了。

页面调度机制的实现涉及到多个模块,包括内存管理、磁盘I/O、进程调度等。当Linux发现某个进程需要访问的页不在物理内存中时,它会触发页面调度机制,这时操作系统会按照一定的策略(例如最近最少使用算法)选择一个页帧进行替换,将该页帧中的内容写回到磁盘中,并将该页帧分配给需要访问的页。如果物理内存中没有可用的空闲页帧,则需要将其他进程占用的页帧进行替换。

映射方式的演变

那么对于虚拟地址与物理地址的映射关系,可以有分段分页的方式,同时两者结合都是可以的。

内存分段是根据程序的逻辑角度,分成了栈段、堆段、数据段、代码段等,这样可以分离出不同属性的段,同时是一块连续的空间。但是每个段的大小都不是统一的,这就会导致外部内存碎片和内存交换效率低的问题。

于是,就出现了内存分页,把虚拟空间和物理空间分成大小固定的页,如在 Linux 系统中,每一页的大小为 4KB。由于分了页后,就不会产生细小的内存碎片,解决了内存分段的外部内存碎片问题。同时在内存交换的时候,写入硬盘也就一个页或几个页,这就大大提高了内存交换的效率。

再来,为了解决简单分页产生的页表过大的问题,就有了多级页表,它解决了空间上的问题,但这就会导致 CPU 在寻址的过程中,需要有很多层表参与,加大了时间上的开销。于是根据程序的局部性原理,在 CPU 芯片中加入了 TLB,负责缓存最近常被访问的页表项,大大提高了地址的转换速度。

那么为什么不分级的页表就做不到这样节约内存呢?

我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。

我们把二级分页再推广到多级页表,就会发现页表占用的内存空间更少了,这一切都要归功于对局部性原理的充分应用。

另外,Linux 系统中虚拟空间分布可分为用户态内核态两部分,其中用户态的分布:代码段、全局变量、BSS、函数栈、堆内存、映射区。

什么是快表(TLB),你知道多少关于快表的知识?

快表,又称联想寄存器(TLB) ,是一种访问速度比内存快很多的高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,内存中的页表常称为慢表。

多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。

地址变换中,有快表和没快表,有什么区别?

会先查快表,没有再查页表

如果系统中具有快表后,那么地址的转换过程变成什么样了?

①CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。

②如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。

③如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表, 以便后面可能的再次访问。但若快表已满,则必须按照-定的算法对旧的页表项进行替换)

由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。因为局部性原理,–般来说快表的命中率可以达到90%以上。

内核态和用户态介绍

内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:

  • 内核空间,这个内存空间只有内核程序可以访问;
  • 用户空间,这个内存空间专门给应用程序使用;

用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。因此,当程序使用用户空间时,我们常说该程序在用户态执行,而当程序使内核空间时,程序则在内核态执行。

怎么进入内核态

应用程序如果需要进入内核空间,就需要通过系统调用,

内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作

系统调用的作用

系统调用(System Call)是操作系统提供的一种机制,允许用户空间程序请求内核执行某些操作,例如读写文件、创建进程、发送网络数据等。系统调用的作用包括:

  1. 提供访问系统资源的方式:系统调用是用户空间程序访问系统资源的主要方式。操作系统通过系统调用暴露出一些API函数,例如read、write、open等,这些API函数允许用户空间程序读写文件、创建进程、发送网络数据等,从而实现对系统资源的访问。
  2. 实现进程间通信(IPC):操作系统提供了多种进程间通信的方式,例如管道、消息队列、信号量等。这些通信方式都需要通过系统调用来实现,例如pipe、msgget、semop等。
  3. 管理进程和线程:系统调用还可以用于管理进程和线程,例如创建进程、杀死进程、等待进程退出、创建线程等。操作系统提供了一些API函数,例如fork、exit、wait、pthread_create等,允许用户空间程序对进程和线程进行操作。
  4. 实现文件系统:文件系统也是操作系统提供的一种资源,用户空间程序可以通过操作系统提供的文件系统API函数来读写文件、创建目录、删除文件等。这些文件系统API函数也是通过系统调用来实现的。
  5. 管理设备和驱动程序:系统调用还可以用于管理设备和驱动程序,例如查询设备状态、打开设备、关闭设备、发送数据到设备等。操作系统提供了一些API函数,例如ioctl、open、close、write等,允许用户空间程序对设备和驱动程序进行操作。

总的来说,系统调用是用户空间程序与内核空间进行交互的主要方式,它提供了一种访问系统资源、管理进程和线程、实现文件系统、管理设备和驱动程序等功能。

系统调用的方法

系统调用是操作系统为用户进程和硬件设备交互提供的一组接口。在 Linux 下,有三种常见的系统调用方法

  • 通过 glibc 提供的库函数,例如chmod、open、read等,这种方法简单方便,具有良好的移植性,但是可能会有一些性能损失
  • 使用syscall直接调用,这种方法需要知道系统调用号和参数,可以避免 glibc 的开销,但是不太通用,可能会有兼容性问题
  • 使用远程调用指令,这种方法利用了 x86 分段内存模型的特性,可以直接切换到内核态执行系统调用,但是比较复杂,需要了解底层的机制

为什么划分内核态、用户态?

用户态和内核态是操作系统的两种运行状态。

内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。

用户态:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

为了保证系统的稳定性、安全性,需要在系统中划分内核态、用户态。所有涉及IO操作、内存操作等,均在内核态中完成,因为当这些操作出现差错时,可能会导致整个计算机系统的崩溃。用户写的程序可能是含有导致这些操作出现差错的bug的,所以,用户编写的不涉及IO、内存等操作的程序在用户态中完成,而涉及这些操作时,则需要进行用户态到内核态的切换。将实际操作交付给内核态,内核态完成操作后,将结果传递至用户态

内存分布情况

通过这张图你可以看到,用户空间内存,从低到高分别是 7 种不同的内存段:

  • 程序文件段,包括二进制可执行代码;
  • 已初始化数据段,包括静态常量;
  • 未初始化数据段,包括未初始化的静态变量;
  • 堆段,包括动态分配的内存,从低地址开始向上增长;
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

常见内存分配方式有哪些?

内存分配方式

(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

内核申请内存vmalloc和kmalloc的区别

vmalloc和kmalloc都是用于在内核中申请内存的函数,但是它们有以下几个区别:

  • vmalloc分配的内存虚拟地址是连续的,而物理地址无须连续。而kmalloc确保页在物理地址上是连续的,自然虚拟地址也是连续的。
  • vmalloc相比较于kmalloc效率不高,因为获得的页必须转换为虚拟地址空间上连续的页,必须专门建立页表项。
  • vmalloc仅在不得已时才使用——典型的就是为了申请大块内存。该函数可能睡眠,因此不能从中断上下文中调用,也不能从其他不允许阻塞的情况下进行调用。
  • kmalloc分配的内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。
  • kmalloc一般用于申请小于一页的物理内存;vmalloc一般用于申请较大的内存空间。

操作系统的内存分配一般有哪几种方式,各有什么优缺点?

(1)分页存储管理:优点是不需要连续的内存空间,且内存利用率高(只有很小的页内碎片);缺点是不易于实现内存共享与保护。

(2)分段存储管理:优点是易于实现段内存共享和保护;缺点是每段都需要连续的内存空间,且内存利用率较低(会产生外部碎片)。

(3)段页式存储管理:优点是不需要连续的内存空间,内存利用率高(只有很小的页内碎片),且易于实现段内存共享和保护;缺点是管理软件复杂性较高,需要的硬件以及占用的内存也有所增加,使得执行速度下降。

内存分配的过程是怎样的

应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。

当应用程序读写了这块虚拟内存,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理。

缺页中断处理函数会看是否有空闲的物理内存,如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。

如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作,回收的方式主要是两种:直接内存回收和后台内存回收。

内存满了会怎么样

内核在给应用程序分配物理内存的时候,如果空闲物理内存不够,那么就会进行内存回收的工作,主要有两种方式:

  • 后台内存回收:在物理内存紧张的时候,会唤醒 kswapd 内核线程来回收内存,这个回收内存的过程异步的,不会阻塞进程的执行。
  • 直接内存回收:如果后台异步回收跟不上进程内存申请的速度,就会开始直接回收,这个回收内存的过程是同步的,会阻塞进程的执行。

什么是内存泄漏和内存溢出

内存泄漏和内存溢出都是与内存管理相关的问题,但它们的含义和表现形式有所不同。

内存泄漏(Memory Leak)指的是程序在使用完内存后,没有及时释放这些内存,导致内存资源浪费或耗尽的问题。内存泄漏通常是由于程序在动态分配内存后,没有在不再需要这些内存时将其释放。内存泄漏会导致系统的可用内存越来越少,最终导致系统崩溃或者出现不可预期的错误。

内存溢出(Memory Overflow)指的是程序在申请内存空间时,申请的内存超出了系统或进程所能分配的内存空间限制,导致内存访问越界的问题。内存溢出通常是由于程序在对已分配的内存进行操作时,超出了该内存块的边界而导致的。内存溢出会导致程序崩溃或者出现不可预期的错误。

总的来说,内存泄漏和内存溢出都是内存管理的问题,但它们的表现形式和原因有所不同。内存泄漏是指未能及时释放已经分配的内存,而内存溢出是指申请了过多的内存,超出了系统或进程所能分配的内存空间限制。在开发过程中,需要避免出现内存泄漏和内存溢出的问题,可以通过合理地使用内存分配和释放函数、定期检查内存使用情况等方式来解决。

运行过程中出现内存泄漏,有哪些定位手段?

内存泄漏是指程序中申请的内存没有被释放,导致内存空间逐渐消耗殆尽的问题。为了定位内存泄漏,可以采用以下几种方法:

  1. 静态分析工具:使用一些静态分析工具,如Coverity、Lint、PVS-Studio等,可以在代码编译阶段进行检查,识别并定位内存泄漏的位置。
  2. 动态分析工具:使用一些动态分析工具,如Valgrind、DrMemory、AddressSanitizer等,可以在程序运行时检测内存泄漏,并输出相关信息,帮助定位内存泄漏的位置。
  3. 日志记录:在程序中加入日志记录功能,记录程序的运行过程和内存使用情况,以便在内存泄漏发生时进行排查。
  4. 内存分析工具:使用一些内存分析工具,如Heaptrack、Memcheck、MAT等,可以对程序的内存使用情况进行跟踪和分析,帮助定位内存泄漏的位置。
  5. 代码审查:对程序中的关键代码进行仔细审查,查看是否有没有释放内存的情况,并进行修复。

在4GB物理内存的机器上申请8G内存

先讲内存分配过程,要考虑32位(1G+3G)还是64位(128T), 有无swap

  • 在 32 位操作系统,因为进程理论上最大能申请 3 GB 大小的虚拟内存,所以直接申请 8G 内存,会申请失败。
  • 在 64位 位操作系统,因为进程理论上最大能申请 128 TB 大小的虚拟内存,即使物理内存只有 4GB,申请 8G 内存也是没问题,因为申请的内存是虚拟内存。如果这块虚拟内存被访问了,要看系统有没有 Swap 分区:

在执行malloc申请内存的时候,操作系统是怎么做的?

从操作系统层面上看,malloc是通过两个系统调用来实现的: brk和mmap

brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小。mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。

通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。

进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。

  • malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用
  • malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放

为什么不全使用mmap分配内存

因为向操作系统申请内存,是要通过系统调用的,执行系统调用是要进入内核态的,然后在回到用户态,运行态的切换会耗费不少时间。

所以,申请内存的操作应该避免频繁的系统调用,如果都用 mmap 来分配内存,等于每次都要执行系统调用。

另外,因为 mmap 分配的内存每次释放的时候,都会归还给操作系统,于是每次 mmap 分配的虚拟地址都是缺页状态的,然后在第一次访问该虚拟地址的时候,就会触发缺页中断。

也就是说,频繁通过 mmap 分配的内存话,不仅每次都会发生运行态的切换,还会发生缺页中断(在第一次访问虚拟地址后),这样会导致 CPU 消耗较大

mmap原理

mmap(Memory Map)是一种内存映射文件的机制,在Linux系统中广泛应用于文件读写、共享内存、动态链接库等领域。mmap的原理可以简单概括为以下几个步骤:

  1. 打开文件:首先需要使用open系统调用打开文件,获取文件描述符。
  2. 映射文件到内存:使用mmap系统调用将文件映射到进程的虚拟地址空间中。mmap系统调用将文件内容映射到一个或多个虚拟内存页中,这些页可以是匿名页(即没有对应的文件)或者文件页(即与文件关联的页)。映射可以是只读的,也可以是可写的。
  3. 访问映射区域:通过访问映射区域,即读取或写入映射区域的数据,可以实现文件读写、共享内存等功能。这些操作会直接访问对应的内存页,而不需要通过系统调用来进行文件读写或者共享内存的操作。
  4. 取消映射:使用munmap系统调用取消映射,释放虚拟内存页,将对应的文件内容写回到磁盘中(如果是文件页),并释放对应的文件描述符。

总之,mmap的原理是将文件映射到进程的虚拟地址空间中,通过访问映射区域来实现文件读写、共享内存等功能。mmap的优点是可以避免频繁的文件读写操作,提高了文件读写的效率,并且可以实现进程间的共享内存,方便了进程之间的通信。同时,mmap还可以通过设置权限位来控制映射区域的访问权限,提高了数据的安全性。

free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?

malloc 返回给用户态的内存起始地址比进程的堆空间起始地址多了 16 字节。这个多出来的 16 字节就是保存了该内存块的描述信息,比如有该内存块的大小。

这样当执行 free() 函数时,free 会对传入进来的内存地址向左偏移 16 字节,然后从这个 16 字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。

常见内存分配内存错误

(1)内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

(2)内存分配虽然成功,但是尚未初始化就引用它。

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

(3)内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

(4)忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然挂掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)

(5)释放了内存却继续使用它。常见于以下有三种情况:

  • 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
  • 函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
  • 使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

从堆和栈上建立对象哪个快?(考察堆和栈的分配效率比较)

从两方面来考虑:

  • 分配和释放,堆在分配和释放时都要调用函数(malloc,free),比如分配时会到堆空间去寻找足够大小的空间(因为多次分配释放后会造成内存碎片),这些都会花费一定的时间,具体可以看看malloc和free的源代码,函数做了很多额外的工作,而栈却不需要这些。
  • 访问时间,访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正的数据,而栈只需访问一次。另外,堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换出去的。

如何避免预读失效和缓存污染的问题?

在问如何改进 LRU 算法

因为传统的 LRU 算法存在这两个问题:

  • 「预读失效」导致缓存命中率下降
  • 「缓存污染」导致缓存命中率下降

Redis 的缓存淘汰算法则是通过实现 LFU 算法来避免「缓存污染」而导致缓存命中率下降的问题(Redis 没有预读机制)。

MySQL 和 Linux 操

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

计算机实习秋招全阶段指南 文章被收录于专栏

作者简介:2个月时间逆袭嵌入式开发,拿下理想汽车-ssp、小米汽车-sp、oppo-sp、迈瑞医疗、三星电子等八家制造业大厂offer~ 专栏内容:涵盖算法、八股、项目、简历等前期准备的详细笔记和模板、面试前中后的各种注意事项以及后期谈薪、选offer等技巧。保姆级全阶段教程帮你获得信息差,早日收到理想offer~

全部评论

相关推荐

1 21 评论
分享
牛客网
牛客企业服务