1.内存管理基础知识

内存管理是指控制和协调计算机的内存。使用适当的内存管理技术,我们可以确保在操作系统OS)中运行的不同进程和应用程序之间适当分配内存块。

操作系统促进应用程序与计算机硬件之间的交互,使软件应用程序能够与计算机硬件交互,并监督系统硬件和软件资源的管理。

操作系统协调如何在多个进程之间分配内存,以及如何在执行期间在主内存和设备磁盘之间移动空间。内存包括在使用过程中跟踪并在进程完成操作后释放的块。

虽然您可能不需要了解操作系统的所有内部工作原理以及它如何与应用程序和硬件交互,但了解如何编写应用程序以充分利用操作系统提供给我们的设施至关重要,这样我们才能编写高效的应用程序。

在本章中,我们将探讨内存管理的内部概念,并开始从高层次探讨以下主题:

  • 内存管理的基础知识
  • 垃圾收集的工作原理
  • 内存管理的优缺点
  • 内存管理对应用程序性能的影响

在本章结束时,您应该更好地理解确保应用程序充分利用内存的思维过程,并了解内存分配和释放的移动部分。

让我们从内存管理的工作原理概述开始。

内存管理概述

现代计算机旨在在应用程序运行时存储和检索数据。每台现代设备或计算机都旨在允许一个或多个应用程序在读取和写入支持数据的同时运行。数据可以长期或短期存储。对于长期存储,我们使用硬盘等存储介质。这就是我们所说的非易失性存储。如果设备断电,数据会保留下来,以后可以重新使用,但这种类型的存储并未针对高速情况进行优化。

除了需要长时间存储的数据外,应用程序还必须在进程之间存储数据。当应用程序执行各种操作时,数据会不断被写入、读取和删除。这种数据类型最好存储在易失性存储或内存缓存和数组中。在这种情况下,当设备断电时数据会丢失,但在使用过程中数据会以非常高的速度读取和写入。

出于性能原因,最好使用易失性内存而不是非易失性内存的一个实际例子是计算机系统中的缓存内存。缓存是一种小型、快速的易失性存储器,用于存储经常访问的数据和指令以加快处理速度。它通常比从非易失性存储器(例如硬盘驱动器HDD)或固态驱动器SSD))访问数据更快。当处理器需要访问数据时,它会首先检查缓存。如果数据在缓存中(缓存命中),处理器可以快速检索数据,从而提高整体性能。但是,如果数据不在缓存中(缓存未命中),处理器需要从速度较慢的非易失性存储器访问数据,从而导致处理延迟。

在这种情况下,使用易失性存储器(缓存)代替非易失性存储器(HDD 或 SSD)可以提高性能,因为易失性存储器的访问时间要快得多。这在延迟令人担忧的系统中尤其重要,例如高性能计算、游戏和实时处理应用程序。总体而言,利用易失性存储器有助于减少访问常用数据所需的时间,从而提高系统性能和响应能力。

内存是 CPU 和所连接设备共享的一组可快速获取的信息的统称。当处理器执行操作时,程序和信息会占用此内存。处理器会非常快速地将指令和信息移入和移出处理器。这就是为什么缓存(在 CPU 级别)和随机存取存储器RAM)是处理过程中立即使用的存储位置的原因。CPU 有三个缓存级别:L1、L2 和 L3。L1 是最快的,但存储容量较低,并且每个级别越高,空间就越大,但速度越慢。它们最靠近 CPU,用于临时存储和高速访问。图 1.1 描述了不同缓存级别的布局。

在这里插入图片描述

图 1.1 – CPU 和每个 CPU 核心中的不同缓存级别

每个处理器核心都包含 L1 和 L2 缓存的空间,这使得每个核心能够尽快完成小任务。对于可能跨核心共享的较不频繁的任务,将使用 L3 缓存,并在 CPU 的核心之间共享。

原则上,内存管理是一个广义的表达,指的是用于优化 CPU 在其处理过程中对内存的有效使用的技术。考虑到每个设备都有规格,概述了 CPU 速度,更相关的是存储和内存大小。可用内存量将直接影响设备可以支持的应用程序类型以及应用程序在这种设备上的运行效率。内存管理技术的存在是为了帮助我们作为开发人员确保我们的应用程序充分利用可用资源,并确保设备在使用设定的内存量来处理多个操作和进程的同时尽可能平稳地运行。

内存管理考虑了几个因素,例如:

  • 内存设备的容量:通常可用的内存越少,需要的内存分配效率就越高。无论如何,效率是最关键的管理因素,如果效率不高,设备性能就会下降。
  • 根据需要恢复内存空间:进程运行后释放内存至关重要。有些进程运行时间长,需要长时间使用内存,但运行时间短的进程会暂时使用内存。如果不回收内存,数据就会滞留,从而为未来的进程创建不可用的内存块。
  • 通过虚拟内存扩展内存空间:设备通常具有易失性(内存)和非易失性(硬盘)存储。当易失性内存面临最大限度的风险时,硬盘会提供额外的内存块。这个额外的内存块称为交换页面文件。当物理内存已满时,操作系统可以将不常使用的数据从 RAM 转移到交换文件,以释放空间用于更频繁使用的数据。

带有 CPU 的设备通常具有一个操作系统,用于协调进程中的资源分配。此资源分配与可用硬件有关,而应用程序的性能则与内存管理效率有关。

典型的设备有三个主要领域,其中内存管理对于该设备的性能至关重要。这三个领域是硬件、应用程序和操作系统。让我们回顾一下在这些级别上控制内存管理的一些细微差别。

内存管理级别

内存管理有三个级别,如下所示:

  • 硬件:RAM 和中央处理器 (CPU) 缓存是主要参与内存管理活动的硬件组件。RAM 是物理内存。此组件独立于 CPU,虽然用于临时存储,但其容量比缓存大得多,而且速度要慢得多。它通常存储较大且临时性较差的数据,例如长时间运行的应用程序所需的数据。
  • 内存管理单元 (MMU) 是一种跟踪逻辑或虚拟内存和物理地址映射的专用硬件组件,可管理 RAM 和 CPU 缓存的使用情况。其主要功能包括为系统上同时运行的各种进程分配、释放和保护内存空间。图 1.2 显示了 MMU 的高级表示以及它如何映射到系统中的 CPU 和内存。

在这里插入图片描述

图 1.2 – CPU 和 MMU 以及它们如何连接到 RAM 中的内存空间

  • 操作系统:带有 CPU 的系统往往具有一个在根级别协调进程和操作的系统。这称为操作系统OS)。操作系统确保进程成功启动和停止,并协调内存如何在许多进程的不同内存存储中分配。它还跟踪内存块,以确保它知道哪些资源正在被使用以及由哪个进程根据需要回收内存以供后续进程使用。操作系统还采用多种内存分配方法来确保系统以最佳方式运行。如果物理内存用尽,操作系统将使用虚拟内存,即设备存储介质上预先分配的空间。存储空间比 RAM 慢得多,但它有助于操作系统确保尽可能多地使用系统资源,以防止系统崩溃。

  • 应用程序:不同的应用程序有其内存要求。

开发人员可以编写内存分配逻辑,以确保应用程序控制内存分配,而不是其他系统(如 MMU 或操作系统)。

应用程序通常使用两种方法来管理其内存分配:

  • 分配:内存根据请求分配给程序组件,并专门锁定以供该组件使用,直到不再需要为止。开发人员可以手动和明确地编写分配逻辑,或自动允许内存管理器使用分配器组件处理分配。内存管理器选项通常包含在编程语言/运行时中。如果没有,则需要手动分配。
  • 回收:这通过称为垃圾收集的过程来处理。我们稍后将更详细地介绍这个概念,但简而言之,此过程将回收以前分配的和不再使用的内存块。这对于确保内存正在使用或等待使用,但不会在中间丢失至关重要。一些语言和运行时会自动执行此过程;否则,开发人员必须手动提供逻辑。

应用程序内存管理器有几个障碍需要克服。必须考虑到内存管理需要 CPU,这将导致此进程与其他进程之间争夺系统资源。此外,每次发生内存分配或回收时,应用程序都会暂停,因为焦点会集中在该操作上。这种情况发生得越快,最终用户就越不容易察觉,因此必须尽可能高效地处理。最后,分配和使用的内存越多,内存就会变得越碎片化。内存分布在非连续的块中,导致运行时分配时间更长,读取速度更慢。

在这里插入图片描述

图 1.3 – 计算机内存层次结构的高级表示

计算机硬件和操作系统中的每个组件在应用程序内存管理方式中都发挥着关键作用。如果我们要确保设备性能达到最佳,我们需要了解资源分配是如何发生的,以及不良的资源管理和分配可能导致的一些潜在危险。接下来我们将回顾这些主题。

内存管理和分配的基础知识

内存管理是一种足够具有挑战性的技术,可以融入应用程序开发中。最大的挑战之一是知道何时保留或丢弃数据。虽然这个概念听起来很简单,但它是一个完整的研究领域,这一事实说明了一切。理想情况下,程序员不需要担心中间的细节,但了解不同的技术以及如何使用它们来确保最高效率是必不可少的。

连续分配是最古老、最直接的分配方法。当一个进程即将执行并请求内存时,将所需内存与可用内存进行比较。如果可以找到足够的连续内存,则进行分配,进程可以成功执行。如果找不到足够数量的连续内存块,则进程将处于不确定状态,直到找到足够的内存。

图 1*.4* 显示了内存块如何对齐以及如何在连续分配中尝试分配。从概念上讲,内存块是按顺序排列的,当需要分配时,最好将分配的数据放在彼此相邻或连续的块中。这使得依赖分配数据的应用程序中的读/写操作更加高效。

在这里插入图片描述

图 1.4 – 连续分配的工作原理

当我们考虑到连续内存块比非连续块更易于读取和操作时,连续分配块的偏好显而易见。然而,一个缺点是内存可能无法有效使用,因为整个分配必须成功,否则分配将失败。因此,内存可能不会分配给较小的连续块。

作为开发人员,我们可以使用以下提示作为指导方针,以确保在我们的应用程序中进行连续分配:

  • 静态分配 – 我们可以确保使用已知固定大小并在应用程序运行时分配的变量和数据结构。例如,数组在内存中连续分配。
  • 动态分配 – 我们可以手动管理固定大小的内存块。某些语言(例如 C 和 C++)允许您使用诸如 malloc()calloc() 之类的函数动态分配内存。同样,您可以通过在不再使用内存时释放内存来防止碎片化。这可确保尽可能高效地使用和释放内存。
  • 内存池 – 您可以在应用程序启动时保留固定的内存空间。应用程序运行时,内存中的此固定空间将专门用于任何资源需求。如前所述,内存块的分配和释放也将手动处理。

这些技术可以帮助开发人员编写应用程序,以确保在必要时为某些系统和性能关键型应用程序分配连续的内存。

对于连续内存,我们有堆栈和堆分配的选项。堆栈分配预先安排内存分配并在编译期间实现,而堆分配在运行时完成。堆栈分配更常用于连续分配,这是一种完美的匹配,因为分配发生在预定的块中。堆分配有点困难,因为系统必须找到足够的内存,这可能是不可能的。因此,堆分配适合非连续分配。

非连续分配与连续分配相反,允许在可能不相邻的多个内存块中分配内存。这意味着,如果需要分配两个块,并且它们不相邻,则分配仍将成功。

图 1.5显示要分配给进程的内存块,但可用插槽位于连续块的两端。在此模型中,进程仍将收到其分配请求,并且内存块将得到有效利用,因为空闲空间将根据需要使用。

在这里插入图片描述

图 1.5 – 非连续分配的工作原理,其中即使空内存块是分开的,也会使用它们

当然,这种方法是以牺牲最佳读/写性能为代价的,但它确实有助于应用程序继续其进程,因为它可能不需要等待太长时间就能找到内存来满足其请求。这也会导致一个常见的问题,称为碎片化,我们将在后面进行回顾。

即使有了这些技术和建议,在许多情况下,内存管理的实施不当也会影响程序的稳健性和速度。典型问题包括:

  • 过早释放:当程序放弃内存但稍后尝试访问它时,会导致崩溃或意外行为。

  • 悬空指针:当程序结束时,但留下对分配它的内存块的悬空引用。

  • 内存泄漏:当程序不断分配内存但从不释放内存时。这将导致设备上的内存耗尽。

  • 碎片:碎片是指一个实体被分割成许多块。当内存以线性方式分配时,程序运行最佳。当使用太多小块分配内存时,会导致分配不充分。最终,尽管有足够的备用内存,但它无法再提供足够大的内存。

  • 引用局部性差:当连续的内存访问彼此更接近时,程序运行最佳。与碎片问题一样,如果内存管理器将程序将使用的块放置得很远,这将导致性能问题。

正如我们所见,内存必须小心处理,并且我们必须意识到其局限性。最重要的限制之一是应用程序可用的空间量。在下一节中,我们将回顾如何测量内存空间。

内存存储单位

了解特定关键字在内存管理中代表的不同测量单位和总体大小至关重要。这将为我们讨论内存和内存使用情况奠定良好的基础。

  • :最小的信息单位。一个位可以具有两个可能的数值(1 和 0)之一,代表逻辑值(真和假)。多个位组合形成一个二进制数。

  • 二进制数:由一系列位或 1 和 0 组成的数值(通常是整数)。

序列中的每个位代表 2 的幂的值,每个 1 都对给定值的总和做出贡献。要将二进制数转换为十进制数,请将每个数字从左到右乘以 2 的幂。最右边的数字获得最低幂。

例如,二进制数 1101 表示 1 * 8 + 1 * 4 + 0 * 2 + 1 * 1,总计 13。图 1*.6* 显示了一个简单的表格,其中包含相对于 2 的幂的二进制位置。

Power2726252423222120
Base 10 Values1286432168421
131101
37100101
13210000100

图 1.6 – 带有示例值的简单二进制表

  • 二进制代码:表示字母数字和特殊字符的二进制序列。每个位序列都分配给特定数据。最流行的代码是 ASCII 代码,它使用 7 位二进制代码来表示文本、数字和其他字符。
  • 字节:字节是使用指定二进制代码对单个字符进行编码的 8 位序列。由于位和字节以字母 b 开头,因此使用大写字母 B 来表示此数据大小。它还用作基本测量单位,增量通常为数千(千 = 1000,兆 = 1,000,000,等等)

现在我们了解了内存管理及其重要性,让我们仔细看看内存以及它如何工作以确保我们的应用程序顺利运行。

内存工作原理的基础知识

在考虑应用程序的工作原理以及内存的使用和分配方式时,最好至少对计算机如何看待内存、内存可以存在的状态以及算法如何决定如何分配内存有一个高层次的了解。

首先,每个进程都有自己的虚拟地址空间,但会共享相同的物理内存。在开发应用程序时,您只需要使用虚拟地址空间,垃圾收集器会在托管堆上为您分配和释放虚拟内存。在操作系统级别,您可以使用本机函数与虚拟地址空间交互,以在本机堆上为您分配和释放虚拟内存。

虚拟内存可以处于以下三种状态之一:

  • 保留:内存块可供您使用,在提交之前无法访问
  • 空闲:内存块没有引用,可供分配
  • 已提交:内存块已分配给物理存储

随着内存的分配和更多进程的启动,内存可能会变得碎片化。这意味着,如前所述,内存被分割成几个不连续的内存块。这会导致地址空间出现空洞。内存碎片越多,虚拟内存管理器就越难找到一个足够大的空闲块来满足分配请求。即使您需要特定大小的空间并且累计有那么多可用的空间,如果无法在单个地址块上进行分配,分配尝试也可能会失败。通常,如果没有足够的虚拟地址空间可供保留或物理空间可供提交,您将遇到内存异常(如 C# 中的 OutOfMemoryException)。请参阅图 1**.5 以直观地了解碎片化的情况。已分配内存的进程必须检查两个不连续的插槽以获取相关信息。在进程运行时内存中有空闲空间,但在另一个进程请求之前无法使用。这是碎片化内存的一个例子。

在分配内存时,我们需要小心谨慎,根据每个新对象或进程对要分配的块进行排序。这可能是一项繁琐的任务,但幸运的是,.NET 运行时提供了为我们处理此问题的机制。让我们回顾一下 .NET 如何为我们处理内存分配。

.NET 中的自动内存分配

每个操作系统都拥有独特且上下文高效的内存分配技术。操作系统最终控制如何将内存和其他资源分配给每个新进程,以确保相对于硬件和可用资源的有效资源利用率。

在使用 .NET 运行时编写应用程序时,我们依赖其自动分配资源的能力。由于 .NET 允许您使用多种语言(C#、C++、Python 等)编写,因此它提供了一个通用语言运行时CLR),可将原始语言编译为称为托管代码的单一运行时语言,该语言在管理器****执行环境中执行。

使用托管代码,我们可以从跨语言集成和增强的安全性、版本控制和部署支持中受益。例如,我们可以用一种语言编写一个类,然后使用另一种语言从原始类派生一个本机类。您还可以在语言之间传递该类的对象。这是可能的,因为运行时定义了创建、使用、持久化和绑定不同引用类型的规则。

CLR 为我们提供了托管执行期间自动内存管理的好处。作为开发人员,您无需编写代码来执行内存管理任务。这消除了我们之前探讨的大多数(如果不是全部)负面分配场景。运行时为每个初始化的新进程保留一个连续的地址空间区域。此保留的地址空间是托管堆,最初设置为托管堆的基址。

CLR 定义的所有引用类型都分配在托管堆上。当应用程序创建其第一个引用类型实例时,内存分配在托管堆的基址处。对于每个初始化的对象,内存都分配在先前分配的空间之后的连续内存空间中。如果有地址空间可用,此分配方法将继续用于每个新对象。

此过程比非托管内存分配更快,因为运行时通过指针处理内存分配,这使得它几乎与直接从 CPU 堆栈分配内存一样快。由于连续分配的新对象连续存储在托管堆中,因此应用程序可以快速访问这些对象。

必须不断回收分配的内存空间以确保有效和高效的操作。您可以依靠一种称为垃圾收集器的内置机制来协调此过程,我们将在下文中从高层次讨论这一点。

垃圾收集器的作用

垃圾收集是控制程序如何释放不再用于其操作的内存空间的过程。此过程通过管理应用程序的内存分配和释放,充当自动内存管理器。

支持自动垃圾收集的编程语言使开发人员无需编写特定代码来执行内存管理任务。实现自动内存管理的语言允许我们构建应用程序,而无需考虑常见问题,例如内存泄漏或应用程序试图访问已释放对象的已释放内存。

每种语言处理垃圾收集的方式都不同,了解其在您的环境中的工作原理至关重要。如上所述,.NET 中的 CLR 自动实现它,但在 C 等低级编程语言中可能需要额外的库。例如,C 开发人员必须使用 malloc()dealloc() 函数处理分配和释放。相比之下,不建议 C# 开发人员处理这个问题,因为它已经处理好了。

回想一下,在 C# 中,分配是通过托管堆进行的,对象被放置在内存中的连续空间中。相比之下,在 C 中,对象被放置在有空闲内存的地方,并通过链接列表跟踪位置。内存分配在 CLR 支持的语言中会更快,因为分配是线性完成的,从而确保了连续的分配过程。在 C 中,必须遍历内存才能找到下一个可用位置,这会增加分配过程的时间。我们将在下一章中回顾 CLR 分配过程的细节。

以下是垃圾收集器的一些额外好处:

  • 高效地在托管堆上分配对象
  • 从不再使用的对象中回收内存,以便内存可用于将来的分配
  • 通过确保对象不能索取为另一个对象分配的内存来提供内存安全性

垃圾收集器拥有一个优化的引擎,它基于静态字段、线程堆栈上的局部变量、CPU 寄存器、GC 句柄和来自应用程序根的最终队列在最佳时间执行收集操作。每个根应引用托管堆上的对象或具有空值。垃圾收集器可以向运行时的其余部分询问这些根,并将使用此列表创建一个包含可从根访问的所有对象的图表。任何无法访问的对象都被归类为垃圾,并且它正在使用的内存将被释放。

垃圾收集发生在以下情况之一:

  • 操作系统或主机已通知内存不足。
  • 托管堆上分配的对象使用的内存超过可接受的阈值。
  • 开发人员调用 GC.Collect() 函数,该函数强制执行收集事件。这通常不是必需的,因为 GC 会自动运行。

GC 用于管理分配的托管堆分为三个部分,称为 。让我们仔细看看这些代是如何工作的,以及这种机制的优缺点。

.NET 中的垃圾收集

.NET 中的 GC 有三代,分别标记为 0、1 和 2。每代都专门用于根据对象的预期寿命跟踪对象。第 0 代存储短期对象,第 2 代存储更多长期对象。

  • 第 0 代:此代存储短期对象,例如临时变量。当此代已满并且要创建新对象时,GC 将通过检查第 0 代中的对象而不是托管堆中的所有对象来释放空间。

  • 第 1 代:此代位于第 0 代和第 2 代之间。在第 0 代发生 GC 事件后,对象将被压缩并提升到此代,在此代中它们将享有更长的寿命。当在此代上运行 GC 操作时,幸存的对象将提升到第 2 代。

  • 第 2 代:此代存储长期对象,例如静态数据和单例对象。在此级别的收集事件中幸存下来的任何东西都会保留下来,直到它在未来的收集中变得无法访问。此级别的收集也称为完整垃圾收集,因为它们会回收堆中的所有代。

垃圾收集器有一个用于大对象的额外堆,称为大型对象堆LOH)。此堆用于 85,000 字节或更大的对象。考虑到已清理对象的大小和寿命,LOH 和第 2 代上的收集事件通常需要很长时间。

垃圾收集从标记阶段开始,在此阶段查找并创建所有当前分配对象的列表。然后进入重新定位阶段,在此阶段更新与幸存对象相关的引用。然后,有一个压缩阶段,在此阶段从死对象中回收空间,并压缩幸存的对象。压缩只是将内存块彼此并排移动的过程,如前所述,这是 CLR 高效内存分配方法的一个重要因素。

应用程序由多个进程组成,进程在线程上运行。线程是操作系统分配处理器时间的基础。.NET 运行时和 CLR 管理线程,当垃圾收集操作开始时,除触发收集事件的线程外,所有托管线程都将被暂停。

出于多种原因,通常不建议手动运行 GC.Collect() 方法。此方法将暂停您的应用程序并允许收集器运行。这可能会导致您的应用程序无响应并降低其性能。此外,该过程不能保证从内存中释放所有未使用的对象,并且您的应用程序仍在使用的对象将不会被收集。仅当应用程序不再使用收集器先前收集的任何对象时才应使用此方法。

垃圾收集的缺点在于它对性能的影响。垃圾收集必须定期遍历程序,检查对象引用并回收内存。此过程消耗系统资源并经常需要程序暂停。

很容易看出为什么垃圾收集是一个很棒的工具,它使我们免于执行手动内存管理和空间回收。现在,让我们回顾一下内存管理对整体应用程序性能的一些影响。

内存管理对性能的影响

我们已经看到了在我们的应用程序中正确和有效的内存管理的重要性。幸运的是,.NET 通过实现自动垃圾收集来清理进程之间的对象,使我们开发人员的工作更加轻松。

内存管理操作会显著影响应用程序的性能,因为分配和释放活动需要系统资源,并且可能会与正在进行的其他进程竞争。以垃圾收集过程为例,它在遍历不同代以收集和处置旧对象时会暂停线程。

现在,让我们逐项列出并回顾一下应用程序的一些优势以及内存管理的潜在缺陷:

  • 响应性:高效的内存管理可以显著提高应用程序的响应性。当明智地分配和释放内存时,您的程序可以平稳运行,不会出现意外的减速或暂停。
  • 速度:内存访问时间对于应用程序速度至关重要。组织良好的内存管理可以带来更多缓存友好的数据结构和更少的缓存未命中,从而缩短执行时间。
  • 稳定性:内存泄漏和内存损坏是内存管理不完善的应用程序中常见的问题。当内存被分配但从未释放时,就会发生内存泄漏,从而导致资源逐渐消耗并可能崩溃。
  • 可扩展性:高效管理内存的应用程序可扩展性更强。它们可以处理大型数据集和用户负载,而不会遇到内存耗尽问题。
  • 资源利用率:高效的内存管理可最大限度地减少内存浪费,使您的应用程序可以在硬件规格较低的系统上运行。这可以扩大应用程序的潜在用户群并降低基础设施成本。
    当应用程序适当地管理内存时,我们可以期待这些切实的好处。同样,如果不采取正确的措施,也会产生一些不利影响。

内存管理不善的影响

如果处理不当,内存管理会对应用程序性能产生重大负面影响。以下是内存管理不善对应用程序性能产生不利影响的一些方式:

  • 内存泄漏:当应用程序无法释放任何不再需要的内存时,就会发生内存泄漏。随着时间的推移,这些泄漏的内存块会不断积累,消耗越来越多的内存资源。这可能会导致内存使用量过大、可用系统内存减少,并最终导致应用程序崩溃或速度变慢。
  • 内存使用效率低下:内存分配和释放策略效率低下可能会导致内存消耗量超过必要水平。这可能会导致应用程序使用的内存超过其所需内存量,从而降低整个系统的速度并降低应用程序的响应能力。
  • 碎片:当内存分配和释放的方式导致整个堆中散布着小块、不可用的内存间隙时,就会发生内存碎片。这种碎片可能会导致内存使用效率低下,难以为更广泛的数据结构或对象分配连续的内存块。这会导致内存访问时间变慢,应用程序性能下降。
  • 缓存抖动:缓存内存的访问速度比主系统内存快得多。由于内存访问模式效率低下,内存管理不善会导致 CPU 缓存频繁失效,并重新加载主内存中的数据。这会导致性能显著下降。
  • 分页和交换增加:当应用程序消耗过多内存时,操作系统可能会在 RAM 和磁盘存储之间进行分页或交换数据。这涉及从较慢的磁盘存储读取和写入数据,这会导致应用程序性能明显下降。
  • 并发问题:在多线程应用程序中,不正确的内存管理可能导致竞争条件、数据损坏和其他并发问题。多个线程的冲突内存访问可能导致意外行为和性能瓶颈。
  • 增加垃圾收集开销:在具有自动内存管理的语言(如 C#)中,低效的内存管理实践可能导致更频繁的垃圾收集周期。这些周期会短暂暂停应用程序以清理未引用的对象,这可能会导致明显的延迟并降低整体性能。
  • 资源争用:当应用程序消耗过多内存时,可能会导致与同一系统上其他正在运行的进程发生资源争用。这可能会导致对系统资源(CPU、内存、I/O)的竞争,从而导致所有正在运行的应用程序的性能下降。
  • 可扩展性差:内存管理效率低下的应用程序可能难以扩展。随着用户负载和数据大小的增加,应用程序的内存需求可能成为限制因素,阻止其有效处理更大的工作负载。

在确定应用程序范围时,我们必须考虑应用程序的适用环境、它将在哪个设备上运行以及可用的资源。这可能会导致我们选择特定的语言或开发堆栈。让我们回顾一些关键的考虑因素。

主要考虑因素

为确保最佳性能,开发人员应仔细考虑内存管理实践,并采用适当的技术和工具来缓解这些问题。

还要注意,一刀切的做法并不适用所有情况。在考虑将要实施的内存管理技术时,作为开发人员,我们必须考虑以下几点:

  • 支持的操作类型,以及它们是否会根据内存的分配和管理方式实现最佳性能
  • 目标设备和预期的系统资源,因为移动设备的分配与计算机的分配不同
  • 目标操作系统,因为每个操作系统都将实施其整体管理方法

现在我们了解了内存管理、技术和因素,让我们回顾一下我们所学到的知识。

总结

本章探讨了计算机系统中内存管理的基本概念。内存管理对于操作系统有效利用计算机的内存资源至关重要。

我们讨论了内存层次结构,它由各种级别的内存组成,从寄存器和缓存到 RAM 和存储设备。了解这个层次结构对于优化内存使用至关重要。然后,我们回顾了如何将内存分配给计算机上运行的不同进程或程序、分配策略及其对系统性能的影响。

我们还回顾了垃圾收集中一种流行的内存管理技术以及它在 .NET 中的工作方式。垃圾收集会自动识别和释放未使用的对象或数据内存,并使开发人员无需编写手动内存管理逻辑。此行为还可以减少内存开销并提高整体应用程序性能。

本章全面概述了计算机系统中的内存管理,强调了其在确保高效资源利用、进程隔离和系统可靠性方面的重要性。它为理解现代操作系统中内存管理的复杂性奠定了基础。

接下来,我们将更深入地研究垃圾收集并了解如何处理对象。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0neKing2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值