精华内容
下载资源
问答
  • 内存工作设置(工作集)、提交大小概念简述
    万次阅读 多人点赞
    2018-01-08 00:28:58

    内存工作设置(工作集)、提交大小概念简述

    前言

    Windows7的任务管理器(以中文版为例)里面"进程"Tab页的列里面跟内存相关的展示项有7项(分页池和非页面缓冲池跟内核内存有关,暂不讨论),做软件工程师多年,大家真的懂任务管理器中这些内存相关的列吗?譬如什么工作集,专用工作集等等,另外像其它的一些常用工具,譬如ProcessExplorer里面可能又是叫Working Set,Private Bytes。

    另外像SetProcessWorkingSet,EmptyWorkingSet这些函数真的是洪水猛兽,完全不能使用吗?本文将简单讨论下这些问题,另外简单介绍下VMMap工具的使用。

    名词解释

    先进行一下名词解释,其实这个地方容易搞混的一个原因也是不同的工具的描述不一样,导致有些混乱,因此这里先把这些概念统一下,然后再进行解释(WS:Working Set的简称,none:表示无对应的显示选项)。

    Win7任务管理器中名称Process Explorer中名称VMMap中的名称
    工作设置(内存)Working SetTotal WS
    内存(专用工作集)WS PrivatePrivate WS
    提交大小Private BytesPrivate(or Private Bytes)
    内存(共享工作集)*WS ShareableShareable WS
    noneWS SharedShared WS
    noneVirtrual SizeSize
    nonenoneCommitted

    *Win10上面有共享工作集的展示

    工作设置(内存)/Working Set/Total WS: 专用(私有)工作集(当前进程独占)中的物理内存数量与进程正在使用且可以和其他进程共享的物理内存数量的总和,因此可以这么理解,该值就是该进程所占用的总的物理内存,但是这个值是由两部分组成,即"专用工作集"和"共享工作集"(Win10的任务管理器里面可以看到共享工作集)。在深入解析Windows操作系统里面是这样描述的:物理上驻留在内存中的那一部分子集称为工作集(Working Set)。

    峰值工作设置(内存): 进程的工作设置(内存)的最值,可以这么理解,因为工作设置(内存)是波动的,这个项专门记录最大的那个值。

    内存(专用工作集)/WS Private/ Private WS: 工作集的子集,它专门描述某个进程正在使用且无法与其他进程共享的物理内存值。这个值对于一个进程来说也是最重要的,它代表了一个进程到底独占了多少物理内存。

    内存(共享工作集)/ WS Shareable/ Shareable WS: 进程和可以和别的进程共享的物理内存值(注意:是可以共享的,不一定共享了)。比较常见的,譬如,加载系统的一些DLL所占用的物理内存,文件共享内存(文件映射),命名共享内存等等。

    WS Shared/ Shared WS: WS Shareable的子集,这部分是表示已经和别的进程共享的物理内存。

    提交大小/ Private Bytes/ Private: 给当前进程使用而保留的私有虚拟内存的数量,从名字里面的Private可以看出它是专有的,但是和上面的WS Private的区别在于,WS Private是纯物理内存,而Private Bytes实际上是虚拟内存的概念,是包含WS Private的,另外一部分是在换页文件(被从物理内存里面换出去了)里面,有些内存,虽然你提交,但是如果一直没有使用,也是在页面文件(换页文件:PageFile)里面。另外,多说一句,如果要查内存泄漏,可以关注这个值。

    Virtrual Size/Size: 当前进程使用的所有的虚拟内存空间,包含共享,非共享,物理,页面,甚至为程序保留但还未分配的内存。

    Committed: Virtual Size减去为程序保留的内存(未分配)。怎么理解为程序保留的但未分配的内存?就是告诉系统我要一块内存,但暂时还用不上,不过分配的地址得给我,系统就给程序一个不用的地址,但不分配内存,等程序真的要使用时(读写),就从页面或物理内存中分配出来映射到那个地址上。

    保留(预定)的内存: 将虚拟内存空间中线性地址0xXXXXXXXX-0xYYYYYYYY标记为预定状态,但是并没有分配实际的内存。这样的好处是我先预定一部分线性地址,以免后面进程空间中没有这么大的地址范围可用(一般来讲只有服务器上面这样用得多)。这样预定后,0xXXXXXXXX-0xYYYYYYYY这块地址就被占用,地址空间也是资源,虽然还没有分配任何内存。

    提交的内存: 系统从物理内存或者换页内存分配给进程的那一部分。这部分内存在虚拟内存的线性地址中是连续的,不过在物理内存或者换页内存中,不一定是连续的。提交但未使用的内存一般都在换页内存里面,只有去使用的时候,才会换到物理内存里面,这点要注意。

    换页内存: 也属于已经提交的内存,不过因为不常用,可能被系统置换到磁盘上面以节省物理内存,后面如果要使用会发生换页错误(缺页中断),再从磁盘上面置换到物理内存。

    缺页中断: 当程序要访问某个地址,系统发现这个地址不在物理内存里,就会产生中断,然后去读取页面文件,把页面文件中与内存相关的数据拷贝到物理内存,然后标记一下这个地址已经在物理内存中了,然后继续让程序运行。

    虚拟内存、物理内存和换页内存: (整个概念还是有一些复杂,这里只简单描述一下)虚拟内存一般是指整个进程用到的(虚拟)地址空间,之所以是虚拟的,因为中间被系统内存管理器抽象了一层,说到这里就牵涉到一个进程的虚拟内存空间的问题,win32下面一般应用层的虚拟地址空间是2G,然后从虚拟内存地址到物理内存有一个映射关系,这个映射是由内存管理器来完成的,对应用程序透明。而虚拟内存里面一般分成保留内存(压根就还没分配的,只是占了地址空间的坑),物理内存(正在使用)和换页内存(从物理内存换出去的,或者分配后一直未使用),另外物理内存和换页内存都属于已经提交的内存。

    分页池: 由内核或驱动程序代表进程分配的可分页内核内存的数量。可分页内存是可以写入其他存储媒介(例如硬盘)的内存。

    非分页缓冲池: 由内核或驱动程序代表进程分配的不可分页的内核内存的数量。不可分页的内存是不能写入其他存储媒介的内存。内核在处理低优先级的中断时,仍可以发生(处理)高优先级的中断,反过来则不行。缺页过程也是一个中断过程(缺页中断),那么就遇到了一个问题,即缺页中断和其他中断的优先级的问题。如果在高于缺页中断的中断优先级上再发生缺页中断,内核就会崩溃。所以在DISPATCH_LEVEL级别以上,绝对不能使用分页内存,一旦使用分页内存,就有发生缺页中断的可能,如果发生就会导致内核崩溃(蓝屏)。

    简单总结下:

    1、工作设置(内存),又叫工作集,即在物理内存中的数据的集合且等于专用工作集与共享工作集的和,Working Set = WS Private + WS Sharable。

    2、把所有的"工作集"相加后的值会大于任务管理器中提示的物理内存的使用值,因为工作集包含了共享工作集,这部分数据会重复计算。但是如果你只把专用工作集全部加起来又会发现小于任务管理器中提示的物理内存的使用值,因为你完全没有计算共享工作集。

    3、通俗的讲工作设置(内存)是程序占用的物理内存(包含与其他程序共享的一部分),内存专用工作集是程序独占的物理内存,提交大小(Private Bytes)是程序独占的内存(包含物理内存和换页内存)。

    4、Committed = VM Private Committed + VM Shareable Committed(VM:虚拟内存)

    5、Committed = Working Set + Page File Committed

    6、Private Bytes = WS Private + Page File Private

    介绍一个测试Windows极限的工具TestLimit,这个玩意功能挺多的,可以探测Windows系统物理内存,虚拟内存,分页&非分页内存,进程&线程,句柄,用户对象和GDI资源等的极限,这里用来探测一下内存的极限。以后有空了可以聊聊Windows其它一些系统资源的极限情况。

    命令行含义参考PID
    TestLimit -r 2048 -c 1Reserve 2G内存13520
    TestLimit -m 2048 –c 1Commit 1G 内存, 但不访问这些内存12448
    TestLimit –d 2048 –c 1Commit 1G内存, 而且访问这些内存13208

    *参考了网上的实验。

    根据PID进行对照,会发现,不同的情况影响的内存参数并不一样,大家可以自己做做这个实验体会下,对于系统内存机制会有更进一步的了解。

    内存优化的概念

    1、进行内存优化时,我们主要关注的指标有,工作设置(内存)/Working Set,内存(专用工作集)/WS Private,Private Bytes等几个,具体实践时要针对性优化。譬如要优化私有物理内存的使用,那么主要关注当前进程模块分配内存的特征和大小,找到主要的矛盾点。如果要优化共享内存的使用,除了关注命名共享内存的大小,文件映射之外,还要排除下进程加载的模块的大小是否有影响。

    2、高峰工作集(内存)这个指标一般关注比较小,也较少针对性的优化,主要是评估下内存使用是否平滑。在一些内存资源非常紧张的系统,要注意这个地方,防止内存高峰时,整个系统响应速度雪崩式下降。

    3、在进行内存优化时,首先要用相关工具进行内存分配的聚类和分析,然后再制订优化的方案和计划,首先要分清是内存使用不合理导致的问题,还是某些算法使用内存时可以进行技术上优化,不同的情况消耗的时间是完全不一样的。

    4、补充说明下经常被视为"洪水猛兽"的"刷内存",也就是调用SetProcessWorkingSet和EmptyWorkingSet(实际上是和使用特殊参数调用SetProcessWorkingSet类似)把强制系统把某个进程的部分物理内存置换到换页内存里面,其实Committed是不会变化的,但是短时间内Working Set会有明显的变化,为什么说是短时间内,因为虽然某些物理内存页临时置换到换页内存了,但是因为进程马上可能就用到的,又会立即被换出来,因此做完这个动作之后,关注下该进程的Page Fault值,会发现有明显的增量,反而可能影响系统性能。这两点(性能反而下降,假内存优化)也是这种方法被长期诟病的一些原因。不过,任何事情都有两面性,霸蛮点说,系统为什么提供这种API?完全没用的话,系统为什么不干掉。其实系统自己也在使用,虚拟内存管理器也会通过这个方法释放更多的物理内存给应用程序使用。当然,无节操的调用当然不行,什么一秒调用一次这种奇葩的操作,这样肯定会影响性能,那一般应该怎样使用呢?这里总结了几点,大家可以根据情况参考下:

    (1) 维持专用工作集(内存)和提交大小(Private Bytes)在一个合适的比例,不停的刷,物理内存600 KB,虚拟内存50M,这一看就不合理,一般来讲(个人经验),维持在1 : 2左右比较合理。实践中建议根据进程稳定之后的内存来设置这个值。

    (2) 建议在程序暂时不被使用的时候(例如最小化,闲时),或者刚刚完成了一件很消耗物理内存的动作之后,进行一次合理的设置。

    (3) 启动2~5分钟之后进行一次设置,这个跟启动时内存消耗的特点有关系,很多对象的生命周期都是和进程一样长,在初始化时,使用了很多STL,ATL或者自定义的对象,消耗了很多内存,但是这些对象可能就启动时用到了,后面大部分情况可能都用不到,但是它们的生命周期又很长,因此可以在启动一段时间之后,把这部分内存置换出去。另外,不建议在进程的生命周期中定时的去刷这个内存,即使要刷,也要降低刷的频率,甚至不刷。

    VMMap常用功能举例

    (略,后面有空再补充,这个工具本身就很简单)。

    参考文档

    1、https://www.cnblogs.com/walfud/articles/3256233.html Windows 任务管理器中的几个内存概念

    2、https://www.zhihu.com/question/19858114 Windows 7 里进程管理器里面的各列是什么含义?主要是和内存有关的内存-专用工作集,内存-工作集,内存-提交大小,这些之间有什么区别?

    3、https://www.cnblogs.com/awpatp/archive/2012/09/17/2688315.html Windows内存的一些知识点

    4、http://shashanzhao.com/archives/832.html windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解

    5、https://superuser.com/questions/185318/process-explorer-not-showing-the-biggest-user-of-my-ram Process Explorer not showing the biggest user of my RAM VMMAP显示和process explorer不一样?

    6、http://www.cnblogs.com/georgepei/archive/2012/03/07/2383548.html 内存详解

    7、http://www.cnblogs.com/georgepei/archive/2012/03/07/2383445.html 你真的懂任务管理器中有关内存的参数Private(提交大小)和working set(工作设置)吗?

    8、http://blog.csdn.net/lantianjialiang/article/details/5620647 process explorer中的visual size vs working set; private bytes vs WS private

    9、http://www.cnblogs.com/awpatp/archive/2010/01/26/1656651.html Task Manager跟Performance Monitor的区别(Working set和Private bytes)

    10、https://social.microsoft.com/Forums/zh-CN/155e9b0e-8dd9-449a-960c-ce585850a049?prof=required 为什么任务管理器里面所有进程占用的内存加起来远远小于内存使用量?

    11、https://blogs.technet.microsoft.com/markrussinovich/2008/11/17/pushing-the-limits-of-windows-virtual-memory/ Pushing the Limits of Windows: Virtual Memory

    更多相关内容
  • Android 内存管理机制

    千次阅读 多人点赞 2018-09-03 14:41:45
    内存管理基础:从整个计算机领域简述主要内存管理技术。 Linux的内存管理机制:Android毕竟是基于Linux内核实现的操作系统,因此有必要了解一下Linux的内存管理机制。 Android的内存管理相关知识:Android又...

    本文主要包括三大部分内容:

    内存管理基础:从整个计算机领域简述主要的内存管理技术。
    Linux的内存管理机制:Android毕竟是基于Linux内核实现的操作系统,因此有必要了解一下Linux的内存管理机制。
    Android的内存管理相关知识:Android又不同于Linux,它是一个移动操作系统,因此其内存管理上也有自己的特性,这一部分详细讲述Android的内存管理相关知识,包括 内存管理机制进程管理

    内存管理基础

    概述

    CPU只能访问其寄存器(Register)和内存(Memory), 无法直接访问硬盘(Disk)。 存储在硬盘上的数据必须首先传输到内存中才能被CPU访问。从访问速度来看,对寄存器的访问非常快,通常为1纳秒; 对内存的访问相对较慢,通常为100纳秒(使用缓存加速的情况下);而对硬盘驱动器的访问速度最慢,通常为10毫秒。

    寄存器(Register):CPU内部的高速存储区域

    当一个程序加载到内存中时,它由四个内存区域组成:

    • 堆栈(Stack):存储由该程序的每个函数创建的临时变量
    • 堆(Heap):该区域特别适用于动态内存分配
    • 数据(Data):存储该程序的全局变量和静态变量
    • 代码(Code):存储该程序的指令

    主要的内存管理技术

    • Base and limit registers(基址寄存器和界限寄存器)
    • Virtual memory(虚拟内存)
    • Swapping(交换)
    • Segmentation(分段)
    • Paging(分页)
    Base and limit registers(基址寄存器和界限寄存器)

    必须限制进程,以便它们只能访问属于该特定进程的内存位置。

    每个进程都有一个基址寄存器和限制寄存器:

    • 基址寄存器保存最小的有效存储器地址
    • 限制寄存器指定范围的大小

    例如,process 2的有效内存地址是300040到420940

    那么每个来自用户进程的内存访问都将首先针对这两个寄存器进行一次检查:

    操作系统内核可以访问所有内存位置,因为它需要管理整个内存。

    Virtual memory(虚拟内存)

    虚拟内存(VM)是OS为内存管理提供的基本抽象。

    • 所有程序都使用虚拟内存地址
    • 虚拟地址会被转换为物理地址
    • 物理地址表示数据的实际物理位置
    • 物理位置可以是内存或磁盘

    虚拟地址到物理地址的转换由存储器管理单元(MMU - Memory Management Unit)处理。MMU使用重定位寄存器(relocation register),其值在硬件级别上被添加到每个内存请求中。

    Swapping(交换)

    交换是一种可以暂时将进程从内存交换到后备存储,而之后又可以将其返回内存以继续执行的技术。

    后备存储通常是一个硬盘驱动器,其访问速度快,且大小足以存储内存映像的副本。

    如果没有足够的可用内存来同时保留内存中的所有正在运行的进程,则某些当前未使用CPU的进程可能会被交换到后备存储中。

    交换是一个非常缓慢的过程。 主要耗时部分是数据传输。例如,如果进程占用10MB内存并且后备存储的传输速率为40MB/秒,则需要0.25秒来进行数据传输。 再加上将数据交换回内存的时间,总传输时间可能是半秒,这是一个巨大的延迟,因此,有些操作系统已经不再使用交换了。

    Segmentation(分段)

    分段是一种将内存分解为逻辑片段的技术,其中每个片段代表一组相关信息。 例如,将每个进程按照堆栈,堆,数据以及代码分为不同的段,还有OS内核的数据段等。

    将内存分解成较小的段会增加寻找空闲内存的机会。

    每个段都有一对寄存器:

    • 基址寄存器:包含段驻留在内存中的起始物理地址
    • 限制寄存器:指定段的长度

    **段表(Segment table)**存储每个段的基址和限制寄存器信息。

    使用分段时,虚拟内存地址是一对:<段号,偏移量>

    • 段号(Segment Number):用作段表的索引以查找特定条目
    • 偏移量(Offset):首先与限制寄存器进行比较,然后与基址结合以计算物理内存地址

    Paging(分页)

    有时可用内存被分成许多小块,其中没有一块足够大以满足下一个内存需求,然而他们的总和却可以。这个问题被称为碎片(Fragmentation),许多内存分配策略都会受其影响。

    分页是一种内存管理技术,它允许进程的物理内存不连续。它通过在称为页面(Page)的相同大小的块中分配内存来消除碎片问题,是目前比较优秀的内存管理技术。

    分页将物理内存划分为多个大小相等的块,称为帧(Frame)。并将进程的逻辑内存空间也划分为大小相等的块,称为页面(Page)

    任何进程中的任何页面都可以放入任何可用的帧中。

    **页表(Page Table)**用于查找此刻存储特定页面的帧。

    使用分页时,虚拟内存地址是一对:<页码,偏移量>

    • 页码(Page Number):用作页表的索引,以查找此页面的条目
    • 偏移量(Offset):与基址相结合,以定义物理内存地址

    举一个分页地址转换的例子:

    虚拟内存地址为0x13325328,页表项0x13325包含的值是0x03004,那么物理地址是什么?

    答案:
    物理地址是0x03004328
    页码为0x13325,偏移量为0x328
    相应的帧号是0x03004

    Linux的内存管理机制

    在Linux系统下,监控内存常用的命令是free、top等,下面是一个free命令的执行结果:

    要了解Linux的内存管理,首先要明白上例中各个名词的意义:

    • total:物理内存的总大小。
    • used:已经使用的物理内存多小。
    • free:空闲的物理内存值。
    • shared:多个进程共享的内存值。
    • buffers / cached:用于磁盘缓存的大小(这部分是从物理内存中划出来的)。
    • 第二行Mem:代表物理内存使用情况。
    • 第三行(-/+ buffers/cached):代表磁盘缓存使用状态。
    • 第四行:Swap表示交换空间内存使用状态(这部分实际上是从磁盘上虚拟出来的逻辑内存)。

    free命令输出的内存状态,可以从两个角度来看:内核角度、应用层角度。

    1.从内核角度来查看内存的状态:

    就是内核目前可以直接分配到,不需要额外的操作,即free命令第二行 Mem 的输出。从上例中可见,41940 + 16360492 = 16402432,也就是说Mem行的 free + used = total,注意,这里的free并不包括buffers和cached。

    2.从应用层角度来查看内存的状态:

    也就是Linux上运行的程序可以使用的内存大小,即free命令第三行 -/+ buffers/cache 的输出。再来做一个计算41940+(465404+12714880)=13222224,即Mem行的free + buffers + cached = -/+ buffers/cache行的free,也就是说应用可用的物理内存值是Mem行的free、buffers和cached三者之和,可见-/+ buffers/cache行的free是包括buffers和cached的。

    对于应用程序来说,buffers/cached占有的内存是可用的,因为buffers/cached是为了提高文件读取的性能,当应用程序需要用到内存的时候,buffers/cached会很快地被回收,以供应用程序使用。

    物理内存和虚拟内存

    物理内存就是系统硬件提供的内存大小,是真正的内存。在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为 交换空间(Swap Space)

    linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。而进行这种交换所遵循的依据是“LRU”算法(Least Recently Used,最近最少使用算法)。

    Buffers和Cached有什么用

    在任何系统中,文件的读写都是一个耗时的操作,当应用程序需要读写文件中的数据时,操作系统先分配一些内存,将数据从磁盘读入到这些内存中,然后应用程序读写这部分内存数据,之后系统再将数据从内存写到磁盘上。如果是大量文件读写甚至重复读写,系统读写性能就变得非常低下,在这种情况下,Linux引入了缓存机制。

    buffers与cached都是从物理内存中分离出来的,主要用于实现磁盘缓存,用来保存系统曾经打开过的文件以及文件属性信息,这样当操作系统需要读取某些文件时,会首先在buffers与cached内存区查找,如果找到,直接读出传送给应用程序,否则,才从磁盘读取,通过这种缓存机制,大大降低了对磁盘的IO操作,提高了操作系统的数据访问性能。而这种磁盘高速缓存则是基于两个事实:第一,内存访问速度远远高于磁盘访问速度;第二,数据一旦被访问,就很有可能短期内再次被访问。

    另外,buffers与cached缓存的内容也是不同的。buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序

    为了验证我们的结论是否正确,可以通过vi打开一个非常大的文件,看看cached的变化,然后再次vi这个文件,感觉一下是不是第二次打开的速度明显快于第一次?

    接着执行下面的命令:

    find /* -name  *.conf
    

    看看buffers的值是否变化,然后重复执行find命令,看看两次显示速度有何不同。

    Linux内存管理的哲学

    Free memory is wasted memory.

    Linux的哲学是尽可能多的使用内存,减少磁盘IO,因为内存的速度比磁盘快得多。Linux总是在力求缓存更多的数据和信息,内存不够时,将一些不经常使用的数据转移到交换分区(Swap Space)中以释放更多可用物理内存,当然,如果交换分区的数据再次被读写时,又会被转移到物理内存中,这种设计思路提高了系统的整体性能。而Windows的处理方式是,内存和虚拟内存一起使用,不是以内存操作为主,结果就是IO的负担比较大,可能拖慢处理速度。

    Linux和Windows在内存管理机制上的区别

    在Linux系统使用过程中,你会发现,无论你的电脑内存配置多么优越,仍然不时的发生可用内存吃紧的现象,感觉内存不够用了,其实不然。这是Linux内存管理的优秀特性,无论物理内存有多大,Linux都将其充分利用,将一些程序调用过的硬盘数据缓存到内存,利用内存读写的高速性提高系统的数据访问性能。而Window只在需要内存时,才为应用分配内存,不能充分利用大容量的内存空间。换句话说,每增加一些内存,Linux都能将其利用起来,充分发挥硬件投资带来的好处,而Windows只将其作为摆设。

    所以说,一般我们不需要太关注Linux的内存占用情况,而如果Swap占用率一直居高不下的话,就很有可能真的是需要扩展内存了

    Android的内存管理机制

    Android使用虚拟内存和分页,不支持交换

    垃圾收集

    无论是ART还是Dalvik虚拟机,都和众多Java虚拟机一样,属于一种托管内存环境(程序员不需要显示的管理内存的分配与回收,交由系统自动管理)。托管内存环境会跟踪每个内存分配, 一旦确定程序不再使用一块内存,它就会将其释放回堆中,而无需程序员的任何干预。 回收托管内存环境中未使用内存的机制称为垃圾回收

    垃圾收集有两个目标:

    • 在程序中查找将来无法访问的数据对象;
    • 回收这些对象使用的资源。

    Android的垃圾收集器不带压缩整理功能(Compact),即不会对Heap做碎片整理。

    Android的内存堆是分代式(Generational)的,意味着它会将所有分配的对象进行分代,然后分代跟踪这些对象。 例如,最近分配的对象属于年轻代(Young Generation)。 当一个对象长时间保持活动状态时,它可以被提升为年老代(Older Generation),之后还能进一步提升为永久代(Permanent Generation)

    每一代的对象可占用的内存总量都有其专用上限。 每当一代开始填满时,系统就会执行垃圾收集事件以试图释放内存。 垃圾收集的持续时间取决于它在收集哪一代的对象以及每一代中有多少活动对象

    虽然垃圾收集速度非常快,但它仍然会影响应用程序的性能。通常情况下你不需要控制代码中何时执行垃圾收集事件。 系统有一组用于确定何时执行垃圾收集的标准。 满足条件后,系统将停止执行当前进程并开始垃圾回收。 如果在像动画或音乐播放这样的密集处理循环中发生垃圾收集,则会增加处理时间。 这种增加可能会导致你的应用程序中的代码执行超过建议的16ms阈值。

    为实现高效,流畅的帧渲染,Android建议绘制一帧的时间不要超过16ms。

    此外,你的代码可能会执行各种工作,这些工作会导致垃圾收集事件更频繁地发生,或使其持续时间超过正常范围。 例如,如果在Alpha混合动画的每个帧期间在for循环的最内部分配多个对象,则大量的对象就会污染内存堆。 此时,垃圾收集器会执行多个垃圾收集事件,并可能降低应用程序的性能。

    共享内存

    Android可以跨进程共享RAM页面(Pages)。 它可以通过以下方式实现:

    • 每个应用程序进程都是从名为Zygote的现有进程分叉(fork)出来的。 Zygote进程在系统引导并加载framework代码和资源(例如Activity Themes)时启动。 要启动新的应用程序进程,系统会fork Zygote进程,然后在新进程中加载并运行应用程序的代码。 这种方法允许在所有应用程序进程中共享大多数的为framework代码和资源分配的RAM页面

    • 大多数静态数据都被映射到一个进程中。 该技术允许在进程之间共享数据,并且还允许在需要时将其Page out。这些静态数据包括:Dalvik代码(通过将其置于预链接的.odex文件中进行直接的memory-mapping),app资源(通过将资源表设计为可以mmap的结构并通过对齐APK的zip条目) 和传统的项目元素,如.so文件中的本地代码。
    • 在许多地方,Android使用显式分配的共享内存区域(使用ashmem或gralloc)在进程间共享相同的动态RAM。 例如,Window surface在应用程序和屏幕合成器之间使用共享内存,而游标缓冲区在Content Provider和客户端之间使用共享内存。

    分配和回收应用的内存

    Android为每个进程分配内存的时候,采用了弹性分配方式,也就是刚开始并不会一下分配很多内存给每个进程,而是给每一个进程分配一个“够用”的虚拟内存范围。这个范围是根据每一个设备实际的物理内存大小来决定的,并且可以随着应用后续需求而增加,但最多也只能达到系统为每个应用定义的上限。

    堆的逻辑大小与其使用的物理内存总量并不完全相同。 在检查应用程序的堆时,Android会计算一个名为“比例集大小”(PSS)的值,该值会考虑与其他进程共享的脏页面和干净页面,但其总量与共享该RAM的应用程序数量成正比。 此PSS总量就是系统认为是你的物理内存占用量。

    Android会在内存中尽量长时间的保持应用进程,即使有些进程不再使用了。这样,当用户下次启动应用的时候,只需要恢复当前进程就可以了,不需要重新创建进程,进而减少应用的启动时间。只有当Android系统发现内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android就会决定关闭某些进程以回收内存。关于这部分内容,稍后再细说。

    限制应用的内存

    为了维护高效的多任务环境,Android为每个应用程序设置了堆大小的硬性限制。 该限制因设备而异,取决于设备总体可用的RAM。 如果应用程序已达到该限制并尝试分配更多内存,则会收到 OutOfMemoryError

    在某些情况下,你可能希望查询系统以准确确定当前设备上可用的堆空间大小,例如,确定可以安全地保留在缓存中的数据量。 你可以通过调用 getMemoryClass() 来查询系统中的这个数字。 此方法返回一个整数,指示应用程序堆可用的兆字节数。

    切换应用

    当用户在应用程序之间切换时,Android会将非前台应用程序(即用户不可见或并没有运行诸如音乐播放等前台服务的进程)缓存到一个最近最少使用缓存(LRU Cache)中。例如,当用户首次启动应用程序时,会为其创建一个进程; 但是当用户离开应用程序时,该进程不会退出。 系统会缓存该进程。 如果用户稍后返回应用程序,系统将重新使用该进程,从而使应用程序切换更快。

    如果你的应用程序具有缓存进程并且它保留了当前不需要的内存,那么即使用户未使用它,你的应用程序也会影响系统的整体性能。 当系统内存不足时,就会从最近最少使用的进程开始,终止LRU Cache中的进程。另外,系统还会综合考虑保留了最多内存的进程,并可能终止它们以释放RAM。

    当系统开始终止LRU Cache中的进程时,它主要是自下而上的。 系统还会考虑哪些进程占用更多内存,因为在它被杀时会为系统提供更多内存增益。 因此在整个LRU列表中消耗的内存越少,保留在列表中并且能够快速恢复的机会就越大。

    Android对Linux系统的内存管理机制进行的优化

    Android对内存的使用方式同样是“尽最大限度的使用”,这一点继承了Linux的优点。只不过有所不同的是,Linux侧重于尽可能多的缓存磁盘数据以降低磁盘IO进而提高系统的数据访问性能,而Android侧重于尽可能多的缓存进程以提高应用启动和切换速度。Linux系统在进程活动停止后就结束该进程,而Android系统则会在内存中尽量长时间的保持应用进程,直到系统需要更多内存为止。这些保留在内存中的进程,通常情况下不会影响系统整体运行速度,反而会在用户再次激活这些进程时,加快进程的启动速度,因为不用重新加载界面资源了,这是Android标榜的特性之一。所以,Android现在不推荐显式的“退出”应用

    那为什么内存少的时候运行大型程序会慢呢,原因是:在内存剩余不多时打开大型程序会触发系统自身的进程调度策略,这是十分消耗系统资源的操作,特别是在一个程序频繁向系统申请内存的时候。这种情况下系统并不会关闭所有打开的进程,而是选择性关闭,频繁的调度自然会拖慢系统。

    Android中的进程管理

    说到Android的内存管理,就不得不提到进程管理,因为进程管理确确切切的影响着系统内存。在了解进程管理之前,我们首先了解一些基础概念。

    当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,你也可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。

    Android应用模型的设计思想取自Web 2.0的Mashup概念,是基于组件的应用设计模式。在该模型下,每个应用都由一系列的组件搭建而成,组件通过应用的配置文件描述功能。Android系统依照组件的配置信息,了解各个组件的功能并进行统一调度。这就意味着,来自不同应用的组件可以有机地结合在一起,共同完成任务,各个Android应用,只有明确的组件边界,而不再有明确的进程边界和应用边界。这种设计,也令得开发者无需耗费精力去重新开发一些附属功能,而是可以全身心地投入到核心功能的开发中。这样不但提高了应用开发的效率,也增强了用户体验(比如电子邮件中选择图片作为附件的功能,可以直接调用专门的图片应用的功能,不用自己从头开发)。

    系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行(四大组件的各个生命周期回调方法都是在UI线程中触发的)。

    进程的生命周期

    Android的一个不寻常的基本特征是应用程序进程的生命周期并非是由应用本身直接控制的。相反,进程的生命周期是由系统决定的,系统会权衡每个进程对用户的相对重要程度,以及系统的可用内存总量来确定。比如说相对于终止一个托管了正在与用户交互的Activity的进程,系统更可能终止一个托管了屏幕上不再可见的Activity的进程,否则这种后果是可怕的。因此,是否终止某个进程取决于该进程中所运行组件的状态。Android会有限清理那些已经不再使用的进程,以保证最小的副作用。

    作为应用开发者,了解各个应用组件(特别是Activity、Service和BroadcastReceiver)如何影响应用进程的生命周期非常重要。不正确的使用这些组件,有可能导致系统在应用执行重要工作时终止进程。

    举个常见的例子, BroadcastReceiver 在其 onReceive() 方法中接收到Intent时启动一个线程,然后从该函数返回。而一旦返回,系统就认为该 BroadcastReceiver 不再处于活动状态,因此也就不再需要其托管进程(除非该进程中还有其他组件处于活动状态)。这样一来,系统就有可能随时终止进程以回收内存,而这也最终会导致运行在进程中的线程被终止。此问题的解决方案通常是从 BroadcastReceiver 中安排一个 JobService ,以便系统知道在该进程中仍有活动的工作。

    为了确定在内存不足时终止哪些进程,Android会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。必要时,系统会首先杀死重要性最低的进程,以此类推,以回收系统资源。这就相当于为进程分配了优先级的概念。

    进程优先级

    Android中总共有5个进程优先级(按重要性降序):

    Foreground Process:前台进程(正常不会被杀死)

    用户当前操作所必需的进程。有很多组件能以不同的方式使得其所在进程被判定为前台进程。如果一个进程满足以下任一条件,即视为前台进程:

    • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
    • 托管某个 Service,后者绑定到用户正在交互的 Activity
    • 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
    • 托管正执行其 onReceive() 方法的 BroadcastReceiver

    通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

    Visible Process:可见进程(正常不会被杀死)

    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。杀死这类进程也会明显影响用户体验。 如果一个进程满足以下任一条件,即视为可见进程:

    • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,启动了一个对话框样式的前台 activity ,此时在其后面仍然可以看到前一个Activity。

      运行时权限对话框就属于此类。
      考虑一下,还有哪种情况会导致只触发onPause而不触发onStop?

    • 托管通过 Service.startForeground() 启动的前台Service。

      Service.startForeground():它要求系统将它视为用户可察觉到的服务,或者基本上对用户是可见的。

    • 托管系统用于某个用户可察觉的特定功能的Service,比如动态壁纸、输入法服务等等。

    可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。如果这类进程被杀死,从用户的角度看,这意味着当前 activity 背后的可见 activity 会被黑屏代替。

    Service Process:服务进程(正常不会被杀死)

    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,后台网络上传或下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

    已经运行很久(例如30分钟或更久)的Service,有可能被降级,这样一来它们所在的进程就可以被放入Cached LRU列表中。这有助于避免一些长时间运行的Service由于内存泄漏或其他问题而消耗过多的RAM,进而导致系统无法有效使用缓存进程的情况。

    Background / Cached Process:后台进程(可能随时被杀死)

    这类进程一般会持有一个或多个目前对用户不可见的 Activity (已调用 Activity 的 onStop() 方法)。它们不是当前所必须的,因此当其他更高优先级的进程需要内存时,系统可能随时终止它们以回收内存。但如果正确实现了Activity的生命周期,即便系统终止了进程,当用户再次返回应用时也不会影响用户体验:关联Activity在新的进程中被重新创建时可以恢复之前保存的状态。

    在一个正常运行的系统中,缓存进程是内存管理中唯一涉及到的进程:一个运行良好的系统将始终具有多个缓存进程(为了更高效的切换应用),并根据需要定期终止最旧的进程。只有在非常严重(并且不可取)的情况下,系统才会到达这样一个点,此时所有的缓存进程都已被终止,并且必须开始终止服务进程。

    Android系统回收后台进程的参考条件
    LRU算法:自下而上开始终止,先回收最老的进程。越老的进程近期内被用户再次使用的几率越低。杀死的进程越老,对用户体验的影响就越小。
    回收收益:系统总是倾向于杀死一个能回收更多内存的进程,因为在它被杀时会为系统提供更多内存增益,从而可以杀死更少的进程。杀死的进程越少,对用户体验的影响就越小。换句话说,应用进程在整个LRU列表中消耗的内存越少,保留在列表中并且能够快速恢复的机会就越大。

    这类进程会被保存在一个伪LRU列表中,系统会优先杀死处于列表尾部(最老)的进程,以确保包含用户最近查看的 Activity 的进程最后一个被终止。这个LRU列表排序的确切策略是平台的实现细节,但通常情况下,相对于其他类型的进程,系统会优先尝试保留更有用的进程(比如托管用户主应用程序的进程,或者托管用户看到的最后一个Activity的进程,等等)。还有其他一些用于终止进程的策略:对允许的进程数量硬限制,对进程可以持续缓存的时间量的硬限制,等等。

    在一个健康的系统中,只有缓存进程或者空进程会被系统随时终止,如果服务进程,或者更高优先级的可见进程以及前台进程也开始被系统终止(不包括应用本身糟糕的内存使用导致OOM),那就说明系统运行已经处于一个亚健康甚至极不健康的状态,可用内存已经吃紧。

    Empty Process:空进程(可以随时杀死)

    不含任何活跃组件的进程。保留这种进程的的唯一目的是用作缓存(为了更加有效的使用内存而不是完全释放掉),以缩短下次启动应用程序所需的时间,因为启动一个新的进程也是需要代价的。只要有需要,Android会随时杀死这些进程。

    内存管理中对于前台/后台应用的定义,与用于Service限制目的的后台应用定义不同。从Android 8.0开始,出于节省系统资源、优化用户体验、提高电池续航能力的考量,系统进行了前台/后台应用的区分,对于后台service进行了一些限制。在该定义中,如果满足以下任意条件,应用将被视为处于前台:

    • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
    • 具有前台 Service。
    • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
      IME
      壁纸 Service
      通知侦听器
      语音或文本 Service
      如果以上条件均不满足,应用将被视为处于后台。详见后台Service限制

    Android系统如何评定进程的优先级

    根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程同时托管着 Service 和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

    此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

    由于运行服务的进程其级别高于托管后台 Activity 的进程,因此,在 Activity 中启动一个长时间运行的操作时,最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,一个文件上传的操作就可以考虑使用服务来完成,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理, BroadcastReceiver 也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

    Home键退出和返回键退出的区别

    Home键退出,程序保留状态为后台进程;而返回键退出,程序保留状态为空进程,空进程更容易被系统回收。Home键其实主要用于进程间切换,返回键则是真正的退出程序。

    从理论上来讲,无论是哪种情况,在没有任何后台工作线程(即便应用处于后台,工作线程仍然可以执行)的前提下,被置于后台的进程都只是保留他们的运行状态,并不会占用CPU资源,所以也不耗电。只有音乐播放软件之类的应用需要在后台运行Service,而Service是需要占用CPU时间的,此时才会耗电。所以说没有带后台服务的应用是不耗电也不占用CPU时间的,没必要关闭,这种设计本身就是Android的优势之一,可以让应用下次启动时更快。然而现实是,很多应用多多少少都会有一些后台工作线程,这可能是开发人员经验不足导致(比如线程未关闭或者循环发送的Handler消息未停止),也可能是为了需求而有意为之,导致整个Android应用的生态环境并不是一片干净。

    作为用户,你需要手动管理内存吗?

    你有“内存使用率过高”恐慌症吗?

    无论是使用桌面操作系统还是移动操作系统,很多人都喜欢随时关注内存,一旦发现内存使用率过高就难受,忍不住的要杀进程以释放内存。这种习惯很大程度上都是源自Windows系统,当然这在Windows下也确实没错。然而很多人在使用Linux系统时仍然有这个习惯,甚至到了Android系统下,也改不掉(尤其是Android手机刚出现的几年),Clean Master等各种清理软件铺天盖地。毫不客气的说,Windows毒害了不少人!当然,这也不能怪Windows,毕竟Windows的普及率太高了,而大部分普通用户(甚至一些计算机相关人员)又不了解Windows和Linux在内存管理方面的差别。

    何时需要清理手机的RAM?

    考虑到许多手机厂商都内置了“清理”功能,那这个东西可能也有些道理。事实上,关闭应用程序以节省内存的做法,仅在少数情况下是值得尝试的 —— 当应用崩溃或无法正常运行时。比如以下情况:

    • 你的微信在启动时需要加载很久
    • 某个应用启动时闪退或者运行过程中闪退
    • 系统响应速度非常缓慢

    这些症状可能非常多样化,甚至原因不明的手机发热也可能是由于某个崩溃的应用造成的。

    管理你的手机RAM:结论

    究竟需不需要手动清空内存?答案是:No!

    清空内存意味着你需要不断重启应用,这需要花费时间和电量,甚至会缩短电池寿命。内存占用高其实并非是一件坏事,甚至是需要的。因为Android是基于Linux内核的操作系统,而Linux的内存哲学是:

    Free memory is wasted memory.

    你只需要在手机明显变慢时采取行动。一般来说,系统的自动RAM管理才是最快最高效的,也是Android标榜的优势之一。关闭应用可能释放一些内存,但却对高效使用内存毫无作用。

    Leave the memory management to Android, and it will leave the fun to you.

    参考资料

    浅谈Linux的内存管理机制
    Processes and Threads Overview
    Process and Application Lifecycle
    Overview of Memory Management
    RAM management on Android: why you shouldn’t clear memory

    展开全文
  • C++内存管理(面试版)

    千次阅读 多人点赞 2020-03-29 13:43:42
    C++的内存管理 一、C++内存管理详解 1、内存的分配方式 (a)(a)(a)栈:编译器分配的内存,用来存储函数的局部变量,函数调用结合素则自动释放内存。 (b)(b)(b)堆:程序员用new分配的内存,一般存储指针;如果程序运行...

    C++的内存管理

    重新校正相关内容,让语言描述更加清晰,内容更加具体——2020-0816。

    一、C++内存管理详解

    1、内存的分配方式

    本文会用一段C语言程序说明内存在操作系统中的分布,会从内存的低地址一直延生到内存的高地址。如果有什么错误或者模糊的地方,麻烦各位读者在评论区批评指正。

    #include <stdio.h>
    #include <stdlib.h>
    int x;
    int y=15;
    int main(int agrc,char* argv[]){
      int *value;
      int i;
      value = (int*)malloc(size0f(int)*5);
      for(i=0;i<5;i++)
        value[i] = i;
      return 0;
    }
    

    地址从低到高
    ( a ) (a) (a)代码区(.text):代码区,主要是用来存储代码的区域,也就是计算机所要执行的指令,如上述例子中的所有代码。
    ( b ) (b) (b)静态数据区(.data):存储静态变量和已经初始化的全局变量,如int y = 15;
    ( c ) (c) (c).bss:用来存储为未初始化的全局变量,如int x;
    ( d ) (d) (d)堆(.heap):使用malloc()函数分配的内存,如value = (int*)malloc(size0f(int)*5);
    ( e ) (e) (e)进程间的文件共享区域:使用mmap映射的一块内存用于进程间的文件共享。
    ( f ) (f) (f)栈(.stack):用来存储局部变量,比如函数参数或者等等,在执行完后会释放这部分内存。
    在这里插入图片描述
    mmap的解释在以下链接中:
    https://www.cnblogs.com/huxiao-tee/p/4660352.html
    举例:C++中的int* p = new int[5];
    此语句在内存中的存储情况如下:
    在这里插入图片描述
    分析:以上C++语句实际用到了堆和栈的概念,因为指针p是局部变量,所以局部变量指针p应该在栈区,用new来分配int[5],则应该存放在堆区。所以删除内存是delete []p;而不是delete p;

    2、堆和栈的区别

    答:
    ( a ) (a) (a)管理方式:栈的管理是编译器来进行分配和管理,堆的管理一般是程序员通过new和delete来对内存进行分配和释放。
    ( b ) (b) (b)空间大小:堆在系统中一般可以分配几个G大小的内存,而栈一般分配几M大小的内存。
    ( c ) (c) (c)碎片问题:堆在不断的执行new和delete操作中,内存逐渐被碎片化,使得程序的执行效率变低;栈采用后进先出的策略,所以不会出现碎片化的问题。
    ( d ) (d) (d)增长方向:堆的增长方向是向着地址增大的方向进行的,而栈采用的是后进先出的策略,所以元素的增长方向是朝着地址减少的方向进行的。
    ( e ) (e) (e)分配方式:堆一般是动态分配的;而栈既有动态分配的方式也有静态分配的方式,静态分配使用alloc函数执行,也是由编译器分配,动态由编译器分配。
    ( f ) (f) (f)分配效率:一般情况下,栈的分配效率高于堆,因为栈分配的时候由编译器和操作系统底层将临时变量和相关地址放在特定的寄存器中,读写效率高,而堆有上层复杂的数据结构会造成效率低的问题。
    PS:若是需要占用大块内存还是分配给堆比较好。需要注意栈和堆的越界问题,会造成内存泄露。

    3、控制C++的内存分配

    答:因为C++分配内存的过程特别简单,程序员过度的使用new和delete会造成内存中堆破裂的问题。
    解决方案:从不同固定大小的内存池中分配不同类型的对象。
    实践方法:对每个类重载new和delete就提供了这种控制。

    void *operator new(size_t size)
    {
    	void *p=malloc(size);
    	return (p);
    }
    void operator delete(void *p)
    {
    	free(p);
    }
    

    如果需要创建对象数组,也就是同时创建多个对象,调用系统的new[]和delete[]操作,则会调用系统的内存,同样会出现以上的问题,所以也需要重载new[]和delete[]操作符。

    class TestClass{
    	public: 
    		void *operator new[](size_t size);
    		void operator delete[](void *p);
    		//其他成员
    };
    void *Testclass::operator new[](size_t size)
    {
    	void *p = malloc(size);
    	return (p);
    }
    void *Testclass::operator delete[](void *p)
    {
    	free(p);
    }
    

    4、C++内存分配可能出现的问题

    答:
    ( a ) (a) (a)内存分配未成功,却使用了它。原因:系统每次分配内存不一定会成功,如果使用了它,则会造成不可预计的错误。
    a.用malloc或new申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值为NULL的内存。如果指针p是函数的参数,那么在函数的入口处assert(p!=NULL);如果是使用malloc或者new来申请内存,用if(p==NULL)

    ( b ) (b) (b)内存分配虽然成功,但是尚未初始化就使用了它。原因:开发人员没有初始化的观念;误认为内存的缺省时初值为0,导致引用初值错误。
    b.不要忘记为数组和动态内存赋初值,防止将未被初始化的内存作为右值使用。

    ( c ) (c) (c)内存分配成功并且已经初始化,但操作越过了内存边界,比如数组越界。
    c.避免数组或者指针的下标越界。

    ( d ) (d) (d)内存分配成功后,忘记释放了内存,造成内存泄漏。
    d.动态内存的申请与释放必须配对,防止内存泄露。

    ( e ) (e) (e)释放了内存却还使用了它,原因:一是,程序对象的调用过程过于复杂,难以搞清对象是否释放了内存;二是,返回了栈内存中的指针和引用,因为该内存在函数体结束时自动销毁;三是,使用free/delete释放内存,没有将指针设置为NULL,导致产生野指针。
    防止以上问题,采用以下的解决方案:
    e.用free或delete释放了内存之后,立即将指针设置为NULL,防止产生野指针。

    5、指针与数组之间的对比
    答:数组在静态存储区或者栈上被创建,数组名对应着一块内存,其地址与容量在生命周期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,是可以随时改变的。指针比数组灵活,但是也更加危险。

    6、数组和指针容易发生的错误
    答:char *p = "world";
    编译器会给出错误,const char* 实体“world”不能初始化char*。因为字符串”world"是常量,所以更不能用p[0]='x'来修改内容。
    不能用数组名进行比较和赋值,需要借助strcpy函数和strcmp函数。同时还有需要注意的一点是当数组作为函数的参数进行传递的时候,该数组自动退化为同类型的指针。实例代码如下:

    #include <iostream>
    using namespace std;
    void Func(char a[100]);
    
    int main()
    {
    	char a[] = "Hello World";
    	char b[100]="ddddd";
    	cout << "通过strcpy将数组a[]的指针a赋值给数组b[]的指针" << endl;
    	cout<<strcpy(b, a)<<endl;//p=a
    	cout << "通过strcmp对a指针指向的数组与b指针指向的数组进行比较" << endl;
    	cout<<strcmp(b,a)<<endl;//
    	cout << b << endl;
    	char* p = (char*)malloc(sizeof(char) * (strlen(a) + 1));
    	cout << "将数组a的内容赋值给指针p" << endl;
    	cout<<strcpy(p,a)<<endl;
    	cout << "将指针p的内容与数组a[]进行比较" << endl;
    	cout << strcmp(p,a) << endl;
    	cout << "将数组作为参数传递给函数" << endl;
    	Func(a);
    	free(p);
    	p = NULL;
    	return 0;
    }
    
    void Func(char a[100])
    {
    	cout << "a的内容" << endl;
    	cout << a << endl;
    	cout << "指针a的大小" << endl;
    	cout << sizeof(a) << endl;
    	cout << "指针*a所指向的内容" << endl;
    	cout << *a << endl;
    }
    

    在这里插入图片描述
    7、指针参数是如何传递内存的
    答:如果函数的参数是一个指针,不能指望用该指针去申请动态内存。原因:编译器会为了每个函数的参数分配一个临时副本,指针参数p的副本是_p,编译器使_p=p,当你分配内存的时候,为_p分配了一块内存,但是指针p却没有指向这块内存,所以函数不会分配到内存。
    以下为错误代码:
    在这里插入图片描述
    正确代码:
    在这里插入图片描述
    8、什么是野指针,如何预防
    答:野指针不是NULL指针,是指向垃圾内存的指针。而且使用if(p==NULL)是判断不了野指针的。
    原因:a.定义指针的时候没有初始化。它的缺省值会随机指向内存中的一个地址,这是相当危险的。解决办法如下:

    	char* p = NULL;
    	char* str = (char*)malloc(100);
    

    b.指针p被free或者delete之后,没有置为NULL,让人认为还是一个合法的指针。
    c.指针的操作超越了变量的作用域范围。

    class A
    	{
    	public:
    		void Func(void) { cout << "Func of Class A" << endl; };
    	};
    
    	void Test(void)
    	{
    		A* p;
    		{
    			A a;
    			p = &a;
    		}//到这里指针p已经被A的默认析构函数释放了
    		p->Func();//p是野指针
    	}
    

    9、malloc/free和new/delete的区别和联系?
    答:malloc/free是系统的库函数,new/delete是操作符,其中编译器可以管理new/delete而不能直接管理malloc、free。所以一个类中的构造函数和析构函数是使用new和delete这样操作时比较方便的。

    	class Obj
    	{
    	public:
    		Obj(void) { cout << "Initialization" << endl; }
    		~Obj(void) { cout << "Destroy" << endl; }
    		void Initialize(void) { cout << "Initialization" << endl; }
    		void Destroy(void) { cout << "Destroy" << endl; }
    	};
    
    	void UseMallocFree(void)
    	{
    		Obj* a = (obj*)malloc(sizeof(obj)); // 申请动态内存
    		a->Initialize(); // 初始化
    		//…
    		a->Destroy(); // 清除工作
    		free(a); // 释放内存
    	}
    
    	void UseNewDelete(void)
    	{
    		Obj* a = new Obj; // 申请动态内存并且初始化
    		//…
    		delete a; // 清除并且释放内存
    	}
    

    补充:malloc/free要点,malloc函数返回的是void类型指针所以需要类型转换,内存大小是sizeof(char)等等来判断大小。
    new/delete要点,new和delete函数是可以初始化的,如obj o = new obj(1);

    10、内存耗尽怎么办?
    答:内存耗尽会返回NULL,使用if判断使用return或者exit(-1)来返回或者终止程序,推荐使用exit(-1);

    后续待补充。。。。

    参考文献:
    [1]https://www.cnblogs.com/findumars/p/5180490.html

    展开全文
  • 【带你吃透C++】C++动态内存管理

    千次阅读 多人点赞 2022-01-19 14:26:12
    作为一名编程初学者,通常学习中,发生内存错误是件非常麻烦的事情。编译器不能自动发现...本文主要针对C++动态内存管理内容进行分享,如果想了解C语言动态内存管理可以参考:【C语言进阶】动态内存管理(详细介绍)

    前言

    作为一名编程初学者,通常学习中,发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。 了解内存分配并掌握运用它是必不可少的。本文主要针对C++动态内存管理内容进行分享,如果想了解C语言动态内存管理可以参考:【C语言进阶】动态内存管理(详细介绍)


    1. C/C++内存分布

    在这里插入图片描述
    在C/C++程序地址空间当中,地址的增长方向是自底向上。分别是代码段、已初始化全局数据区、未初始化全局数据区、堆区、共享区(内存映射段)、栈区、命令行参数、环境变量。

    • 栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
    • 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共
      享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
    • 堆用于程序运行时动态内存分配,堆是可以上增长的。
    • 数据段–存储全局数据和静态数据。
    • 代码段–可执行的代码/只读常量。

    注意:一般例如堆区,理论上而言,后malloc的内存地址比先malloc的要大,但是也不一定,因为有可能下一次申请的是之前释放回来的
    在这里插入图片描述
    这几个区域的特点:1.这几个

    例题理解

    我们来看下面的一段代码和相关问题:

    int globalVar = 1;
    static int staticGlobalVar = 1;
    void Test()
    {
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = {1, 2, 3, 4};
    char char2[] = "abcd";
    char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof (int)*4);
    int* ptr2 = (int*)calloc(4, sizeof(int));
    int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
    free (ptr1);
    free (ptr3);
    }
    

    1. 选择题:
    选项: A.栈 B.堆 C.数据段 D.代码段

    globalVar在哪里?____ staticGlobalVar在哪里?____
    staticVar在哪里?____  localVar在哪里?____
    num1 在哪里?____
    char2在哪里?____ *char2在哪里?___
    pChar3在哪里?____ *pChar3在哪里?____
    ptr1在哪里?____ *ptr1在哪里?____
    

    2. 填空题:

    sizeof(num1) = ____;
    sizeof(char2) = ____; strlen(char2) = ____;
    sizeof(pChar3) = ____; strlen(pChar3) = ____;
    sizeof(ptr1) = ____;
    

    我们来揭晓答案:
    在这里插入图片描述

    需要特别注意以下几个:

    char char2[] = "abcd";
    char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof (int)*4);
    

    char2是定义在栈区的数组,"abcd"存放与数组中

    pChar3是定义在栈区的指针,存放的是定义在常量区"abcd"的地址,"abcd"通过指针pChar3由栈区指向常量区

    同理: ptr1是定义在栈区的指针,存的是在堆区开辟的空间的地址

    因此:
    char2在栈区,*char2也在栈区
    pChar3在栈区,对pChar3解引用操作(*pChar3)则指向了常量区(代码段)
    ptr1在栈区,对 ptr1解引用操作(* ptr1)则指向了堆区

    2.C语言中动态内存管理方式( malloc/calloc/realloc和free)

    一、malloc

    malloc函数的功能是开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。传参时只需传入需要开辟的字节个数。

    二、calloc

    calloc函数的功能也是开辟指定大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个NULL。calloc函数传参时需要传入开辟的内存用于存放的元素个数和每个元素的大小。calloc函数开辟好内存后会将空间内容中的每一个字节都初始化为0。

    三、realloc

    realloc函数可以调整已经开辟好的动态内存的大小,第一个参数是需要调整大小的动态内存的首地址,第二个参数是动态内存调整后的新大小。realloc函数与上面两个函数一样,如果开辟成功便返回开辟好的内存的首地址,开辟失败则返回NULL。

    四、free

    free函数的作用就是将malloc、calloc以及realloc函数申请的动态内存空间释放,其释放空间的大小取决于之前申请的内存空间的大小。

    我们今天主要说说C++中动态内存管理方式:

    3.C++中动态内存管理方式

    首先,C语言内存管理的方式在C++中可以继续使用。但有些地方不够完善而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

    new和delete操作内置类型

    一、动态申请单个某类型的空间

    //动态申请单个int类型的空间
    int* p1 = new int; //申请
    
    delete p1; //销毁
    

    其作用等价于C语言中的:

    int* p2 = (int*)malloc(sizeof(int)); //申请
    
    free(p2); //销毁
    

    区别在于:
    1.C语言的malloc属于库函数,C++中属于操作符 ,同时new/delete也是两个关键字
    2.如果动态申请的对象是内置类型,用malloc和new没区别,如果动态申请的对象是自定义类型,有区别

    new和delete操作自定义类型

    new/delete不仅仅会开空间/释放空间,还会调用构造函数和析构函数
    举个例子:

    #include<iostream>
    
    using namespace std;
    
    class A
    {
    public:
    	A(int a=0)//构造
    		:_a(a)
    	{
    		cout << "A()" << endl;
    	}
    	~A()//析构
    	{
    		cout << "A()" << endl;
    	}
    private:
    	int _a;
    };
    
    int main()
    {
    	A* p4 = new A(10);//申请空间,传参(10),调用构造函数对开的空间初始化
    	delete(p4);
    	return 0;
    }
    

    通过监视可以看到申请的空间初始化为了10
    在这里插入图片描述
    动态申请多个类的空间

    用new和delete操作符:

    Test* p3 = new Test[10]; //申请,调用十次构造函数
    
    delete[] p3; //销毁
    

    用malloc和free函数:

    Test* p4 = (Test*)malloc(sizeof(Test)* 10); //申请
    
    free(p4); //销毁
    

    注意

    • 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
    • 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会
    • 建议在C++中无论是内置类型还是自定义类型的申请和释放,尽量都使用new和delete。
    • 申请(malloc new int[] new int)和 释放(free delete[] delete)一定要匹配使用,否则可能会导致崩溃

    4.operator new和operator delete函数

    ————————————————
    粗略理解:
    new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new和delete在底层是通过调用全局函数operator new和operator delete来申请和释放空间的。

    ————————————————

    operator new():指对new的重载形式,它是一个函数。并不是运算符。对于operator new的重载方式而言,分为两种,一种是全局重载而另一种是类重载。
    全局重载 void* ::operator new(size_t size)
    在类中重载 void* A::operator new(size_t size)

    实际上:

    • operator new的底层是通过调用malloc函数来申请空间的,当malloc申请空间成功时直接返回;若申请空间失败,则尝试执行空间不足的应对措施,如果该应对措施用户设置了,则继续申请,否则抛异常
    • operator delete的底层是通过调用free函数来释放空间的,和free没有任何差别。

    new和operator new()之间的关系(重点):

    首先理解对于A* a = new A中到底干了些什么事情?

    1. 分配内存 ;
    2. 调用构造函数生成类对象;
    3. 返回相应指针。

    第一步中分配内存的操作由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t )。

    同理,operator new[]、operator delete、operator delete[]也是可以重载的。

    new 不能被重载,它先调用operator new分配内存(要实现不同的内存分配行为,应该重载operator new,而不是new 。),然后调用构造函数初始化那段内存。

    下面通过一张图来梳理一下他们之间的关系:
    在这里插入图片描述
    new和delete的实现原理
    内置类型

    如果申请的是内置类型的空间,new/delete和malloc/free基本类似,不同的是,new/delete申请释放的是单个元素的空间,new[ ]/delete [ ]申请释放的是连续的空间,此外,malloc申请失败会返回NULL,而new申请失败会抛异常。

    自定义类型

    • new的原理
       1、调用operator new函数申请空间。
       2、在申请的空间上执行构造函数,完成对象的构造。

    • delete的原理
       1、在空间上执行析构函数,完成对象中资源的清理工作。
       2、调用operator delete函数释放对象的空间。

    • new T[N]的原理
       1、调用operator new[ ]函数,在operator new[ ]函数中实际调用operator new函数完成N个对象空间的申请。
       2、在申请的空间上执行N次构造函数。

    • delete[ ] 的原理
       1、在空间上执行N次析构函数,完成N个对象中资源的清理。
       2、调用operator delete[ ]函数,在operator delete[ ]函数中实际调用operator delete函数完成N个对象空间的释放。

    5.定位new表达式(placement-new)

    定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

    使用格式:

    new(place_address)type 或者 new(place_address)type(initializer-list)

    其中place_address必须是一个指针,initializer-list是类型的初始化列表。

    使用场景:
     定位new表达式在实际中一般是配合内存池使用,因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,就需要使用定位new表达式进行显示调用构造函数进行初始化。

    #include <iostream>
    using namespace std;
    class Test
    {
    public:
    Test()
    : _data(0)
    {
    cout<<"Test():"<<this<<endl;
    }
    ~Test()
    {
    cout<<"~Test():"<<this<<endl;
    }
    private:
    int _data;
    };
    void Test()
    {
    // pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
    Test* pt = (Test*)malloc(sizeof(Test));
    new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
    }
    

    6.内存泄漏

    什么是内存泄漏,内存泄漏的危害?

    内存泄漏:

    内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

    我们来看一个例子:
    在32位下运行下面的程序:
    在这里插入图片描述
    我们看到申请了1G的内存,但是没进行释放
    在这里插入图片描述

    我们在性能中也明确看到运行一次内存从5.8GB变到了4.8GB,如果多次运行,按理说每次都会造成1G的内存泄露。但是事实是多次运行可用内存还是4.8GB,对我们系统也好像没有影响。

    我们来解释一下,一个进程正常结束后,会吧映射的内存都会释放掉,所以上面的进程,我们没有主动释放,但是进程正常结束后也释放了。那么内存泄露好像没什么影响?答案是否定的。

    内存泄漏的危害:

    看看下面的场景:
    1.进程没有正常结束:比如僵尸进程,那么就可能存在一些资源没释放,造成内存泄漏。
    2.长期运行的服务器程序:比如王者荣耀后台服务(长期运行,只有升级的时候才会停)—内存泄露会导致可用内存越来越少,程序越来越慢,甚至挂掉,导致一些事故。
    3.物联网设备:扫地机器人,冰箱等—内存很小,也经不起内存泄漏的影响。

    内存泄漏分类?

    在C/C++中我们一般关心两种方面的内存泄漏:

    1、堆内存泄漏(Heap Leak)

    堆内存指的是程序执行中通过malloc、calloc、realloc、new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete释放。假设程序的设计错误导致这部分内容没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap
    Leak。

    2、系统资源泄漏

    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

    如何避免内存泄漏?

    • 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记住匹配的去释放。
    • 采用RALL思想或者智能指针来管理资源。
    • 有些公司内部规范使用内部实现的私有内存管理库,该库自带内存泄漏检测的功能选项。
    • 出问题了使用内存泄漏工具检测。

    内存泄漏非常常见,解决方案分为两种:
     1、事前预防型。如智能指针等。
     2、事后查错型。如泄漏检测工具
    ————————————————

    8. 拓展知识:如何在电脑申请较大(1GB以上)的内存:

    仅在代码角度假设我们申请2G的内存:

    #include <iostream>
    using namespace std;
    int main()
    {
        try
        {
            char* p = new char[0x7fffffff];//申请2GB
            printf("%p\n", p);//申请成功,打印p
        }
        catch (const exception& e)
        {
            cout << "申请内存失败!" << endl;
        }
        return 0;
    }
    

    在这里插入图片描述
    在32位下看到显示申请内存失败,那我们怎么办呢?

    首先我们应该理解一些概念,我们在堆上申请空间,实际上是在虚拟地址空间上的堆区申请,而在32位下,虚拟地址空间只有4G的空间,其中1G的空间还是分配给内核空间的,剩下的3G,除了其它区域需要的空间,在堆空间实际最多只能申请出来1G左右空间,但如果在64位下,虚拟地址空间就有2^34GB的空间大小,完全足够满足我们所需了。

    备注:关于虚拟地址空间具体理解,可以参考博文:【Linux练习生】两万字带你看透进程(相关概念),最后一点有详细介绍。

    所以我们可以通过将编译器上的win32改为x64(即64位平台),这样我们便可以一次性在堆上申请2G的内存了。
    在这里插入图片描述

    – the End –

    以上就是我分享的C/C++内存管理,感谢阅读!

    本文收录于专栏C++
    关注作者,持续阅读作者的文章,学习更多知识!
    https://blog.csdn.net/weixin_53306029?spm=1001.2014.3001.5343

    2022/1/21
    ————————————————

    展开全文
  • Android内存管理机制官方详解文档

    千次阅读 2020-12-09 12:40:54
    很早之前写过一篇《Android内存管理机制详解》点击量已7万+,现把Google官方文档整理输出一下,供各位参考。 一、内存管理概览 Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射来管理内存。这意味着应用...
  • 堆的内存管理简介

    千次阅读 2022-02-14 00:17:22
    参考:进程分配内存的两种方式--brk() 和mmap()(不设计共享内存)_鱼思故渊的专栏-CSDN博客_mmap分配内存 堆相关数据结构 - CTF Wiki 浅析堆的申请释放及相应保护机制 | Introspelliam 堆的概述 在程序运行...
  • Linux内存管理机制(最透彻的一篇)

    万次阅读 多人点赞 2018-08-05 14:10:09
    摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux的内存管理与使用。在本章最后,...
  • 几周前,我们新开了一系列文章,旨在深入 JavaScript,探寻其工作原理。我们认为通过了解 JavaScript 的构建方式和其运行规则,我们能写出更好的代码和应用。 第一篇文章重点介绍了引擎、运行时和调用栈的概述。...
  • MMU内存管理单元详解

    千次阅读 2020-04-28 23:15:07
    在传统的批处理系统如 DOS 系统,应用程序与操作系统在内存中的布局大致如下图: 应用程序直接访问物理内存,操作系统占用一部分内存区。 操作系统的职责是“加载”应用程序,“运行”或“卸载”应用程序。...
  • Spark中内存模型管理

    千次阅读 2018-07-18 12:08:52
    Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优。本文旨在梳理出 Spark 内存管理的...
  • Linux内存管理(最透彻的一篇)

    万次阅读 多人点赞 2019-05-16 14:27:22
    【转】Linux内存管理(最透彻的一篇) 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友...
  • 内存管理:页式虚拟内存管理

    千次阅读 2019-12-17 21:00:38
    页式存储管理——虚拟内存——缺页中断,页面替换算法 开章明意: 创建一个进程(创建进程是在磁盘中),进程以字节为单位编号,然后再进程分为许多页(每页4KB),内存中有对应的页框(设定同页)。通过页表(记录...
  • Spark 统一内存管理模型详解

    万次阅读 2018-09-17 15:24:59
    堆内内存(On-heap ...Execution 内存主要用于存放 Shuffle、Join、Sort、Aggregation 等计算过程中的临时数据 Storage 内存主要用于存储 spark 的 cache 数据,例如RDD的缓存、unroll数据; 用户内存(Us...
  • 操作系统——内存分配管理

    千次阅读 2019-08-29 15:39:51
    1. 连续分配 1.1 单一连续分配 1.2 固定分区分配 1.3 动态分区分配 2. 非连续分配 2.1 基本分页存储管理方式 2.2 基本分段存储管理方式 2.3 段页式存储管理方式
  • Go的内存管理(最新学习)

    万次阅读 2021-06-11 15:21:30
    标题Go的内存管理 内存管理的设计 内存空间有堆区和栈区。栈一般存储局部变量,方法有关的数据,由编译器自动管理,。堆用来存放对象,java和go都是通过垃圾收集器回收,不需要手动对内存进行释放和管理。 内存管理...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 ...在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换
  • JVM内存管理及GC机制

    万次阅读 2021-12-06 21:42:40
    Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。...
  • 内存寻址 、硬件中的分段与分页 、Linux内存管理 页与内存管理区 、kmalloc()和vmalloc()
  • 包括:内存管理单元MMU的作用,虚拟内存与物理内存之间的映射方式,页表的概念,高速缓存(Cache)的作用,物理内存与高速缓存之间的映射关系等。当然,想要深入了解,本文并不适合,本文只是从原理上,讲述以上几者...
  • Java内存管理

    万次阅读 多人点赞 2016-03-08 21:36:06
    不过看了一遍《深入Java虚拟机》再来理解Java内存管理会好很多。接下来一起学习下Java内存管理吧。请注意上图的这个:我们再来复习下进程与线程吧:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,...
  • Java内存管理-内存分配与回收

    千次阅读 2017-12-29 17:48:00
    Java内存管理-内存分配与回收
  • unity 内存管理

    千次阅读 2018-08-17 14:19:42
    转载至以下文章: https://onevcat.com/2012/11/memory-in-unity3d/ Unity 3D中的内存管理 https://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html iOS stripping level(官网) ...
  • windows虚拟内存管理

    千次阅读 2016-07-21 09:22:16
    内存管理是操作系统非常重要的部分,处理器每一次的升级都会给内存管理方式带来巨大的变化,向早期的8086cpu的分段式管理,到后来的80x86 系列的32位cpu推出的保护模式和段页式管理。在应用程序中我们无时不刻不在和...
  • JVM的内存管理

    千次阅读 2018-11-19 16:02:03
    一:JVM内存模型图示 图1 图2 上面两个图都展示了虚拟机内存管理模式,整个运行时数据区又分为不同的内存区域,不同区域承担不...
  • 一篇文章搞清spark内存管理

    千次阅读 2020-03-27 15:02:38
    内存空间分配2.1 早期的静态内存管理2.2 统一内存管理机制3. 存储内存(Storage)缓存RDD怎么存储4. 执行内存(Execution)怎么管理 ​ 在执行Spark的应用程序时,Spark集群会启动Driver和Executor两种JVM进程,前者为...
  • Java应用程序是运行在JVM上的,得益于JVM的内存管理和垃圾收集机制,开发人员的效率得到了显著提升,也不容易出现内存溢出和泄漏问题。但正是因为开发人员把内存的控制权交给了JVM,一旦出现内存方面的问题,如果不...
  • C++内存管理(特详细)

    千次阅读 多人点赞 2019-06-02 11:28:30
    为了更好的进行学习,因此写在自己的...内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C...
  • 20 张图揭开「内存管理」的迷雾,瞬间豁然开朗

    万次阅读 多人点赞 2020-06-30 14:40:44
    每日英语,每天进步一点点 前言 之前有不少读者跟我反馈,能不能写图解操作系统? 既然那么多读者想看,我最近就在疯狂的复习操作系统的知识。 操作系统确实是比较难啃的一门...本篇跟大家说说内存管理内存管理还是
  • 原标题:内存和外存的主要区别之处竟是在这里...计算机工作时,把要处理的数据先从外存调入内存,再从内存调入CPU;CPU处理完毕以后,又将数据送回到内存,最后再保存到外存中去。 当我们要求计算机完成一项较大的工...
  • python内存管理机制

    千次阅读 2019-04-09 00:17:58
    1. 内存管理架构 第0层: 是操作系统提供的内存管理接口,比如c运行时提供的malloc和free接口。这一层是由操作系统实现并管理的,python不能干涉这一层的行为。 第1层:基于第0层操作系统提供的内存管理接口包装...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 599,489
精华内容 239,795
关键字:

内存管理的主要工作是什么