Windows程序员学习Linux环境内存管理

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Windows程序员如何学习Linux环境内存管理。由于很多程序在Windows环境下开发好后,还要部署到Linux服务器上去,所以作为Windows程序员有必要学习Linux环境的内存管理知识。

那么什么是内存,内存是用于存放数据的硬件,程序执行前需要先放到内存中才能被cpu处理。如果计算机"按字节编址", 每个存储单元大小为8 bit,如果计算机"按字编址", 每个存储单元大小为16 bit。指令的编指一般采用逻辑地址, 即相对地址。而物理地址 = 起始地址 + 逻辑地址 。操作系统负责内存空间的分配与回收、操作系统需要提供某种技术从逻辑上对内存空间进行扩充、操作系统需要实现地址转换功能, 负责程序的逻辑地址和物理地址的转换、操作系统需要提供内存保护功能, 保证各进程在各自存储空间内运行, 互不干扰。覆盖, 交换, 虚拟存储技术常用于实现内存空间的扩充,如下:

覆盖技术

覆盖技术的思想 : 将程序分为多个段, 常用的段常驻内存, 不常用的段在需要的时候调入内存。内存中分为一个"固定区" 和若干个"覆盖区", 常用的段放在固定区, 不常用的段放在覆盖区。缺点 : 必须由程序员声明覆盖结构, 对用户不透明, 增加了用户的编程负担, 覆盖技术只用于早期的操作系统中。

交换技术

交换技术的思想 : 内存空间紧张时, 系统将内存中某些进程暂时换出外存, 把外存中某些已具备运行条件的进程换入内存(即进程在内存与磁盘间动态调度)

内存空间的分配和回收

连续分配管理方式

单一连续分配

在单一连续分配的方式中, 内存被分为系统区和用户区, 系统区用于存放操作系统的相关数据, 用户区用于存放用户进程的相关数据, 内存中只能有一道用户程序, 用户程序独占整个用户区空间。优点 : 实现简单, 无外部碎片; 可以采用覆盖技术扩充内存; 不一定需要采取内存保护。缺点 : 只能用于单用户, 单任务的操作系统中; 有内部碎片; 存储器利用率极低。内部碎片 : 分配给某进程的内存区域有一部分没有用上, 即存在" 内部碎片 "。外部碎片 : 内存中的某些空闲分区由于太小而难以利用。

固定分区分配

在产生了支持多道程序的系统后, 为了能在内存中装入多道程序而互相之间不产生干扰, 将整个用户区划分为若干个固定大小的分区(分区大小可以相等也可以不相等), 在每个分区中只能装入一道作业, 形成了最早的可运行多道程序的内存管理方式。

操作系统建立一个数据结构----分区说明表, 来实现各个分区的分配和回收, 每个表对应一个分区, 通常按分区大小排列。每个表项包括对应分区的大小, 起始地址, 状态。优点 : 实现简单, 无外部碎片;缺点 : 有内部碎片; 存储器利用率不高;

动态分区分配

动态分区分配又称为可变分区分配, 这种分配方式不会预先划分内存分区. 而是在进程装入内存时根据进程大小动态地建立分区, 并使得分区的大小正好适合进程的需要.

好了,简单介绍了内存的一些情况。说到这里,我有必要更正现在一个普遍的误区,有很多人认为内存管理知识不再重要。但其实,内存管理是计算机编程最为基本的知识领域之一。如今很多的脚本语言中确实不必考虑内存如何管理, 但是这并不能说明内存管理已经不再重要。在实际的编程中,理解自己的内存管理器的能力与局限性至关重要。在C/C++语言编程中仍需进行内存管理,这种内存管理增强并提高了C/C++程序的功能和灵活性。

我们现在先要了解内存的分类。一个程序中使用到的数据都是存放在内存空间中的,那么执行一个程序,考虑其高效性和灵活性等就要合理地分配内存。根据内存空间分配方式的不同,可以将内存分为动态内存和静态内存。下面分别对动态内存和静态内存进行讲解。

动态内存

通常当用户无法确定空间大小,或者空间太大、栈上无法分配时,会采用动态内存方式分配内存。比如你要用C语言写一个图书馆借书管理系统,你是无法确定整个系统要处理多少书的借与还工作,书可能很多,也可能很少,书是1000本,还是10000本,还是100000本?你是不是无法确定总数?那怎么办,一般就要用结构体来实现一本书,用链表来实现书册的管理,对每本书的操作那就只能用动态内存分配来做,把一个结构体拿出来,修改信息后再放回链表,就可以实现图书管理。在使用动态内存时,程序员可以自行控制内存的分配和释放。关于分配多少,何时分配与释放等信息,都由程序员根据需要随时实现。

关于动态内存的使用,很多程序员采取能不使用就不使用的原则,原因在于动态内存资源的敏感性。若能够正确地使用并且利用好动态内存,自然会为程序的实现带来效率;但是一旦用不好,就有可能导致整个项目的崩溃。因此,关于动态内存的使用,不同程度的人应遵循不同的使用原则,目的是为了能够使程序安全、正确并且快速地实现其功能。

事物都是有利有弊的,动态内存也不例外。当动态内存为程序带来了巨大的效率的同时,也为程序带来了巨大的风险。使用动态内存会使内存管理变得很复杂。当程序员根据自己的需要动态地分配完内存,就可以得心应手地使用。但是,当不再使用时,切记释放所占的内存空间。所谓的内存泄露就是将内存分配后没有释放,而导致的内存空间减少的现象。计算机的内存空间是有限的,当分配了过多的内存而没有及时释放时,很有可能导致内存不够用,也就是通常所说的内存耗尽。内存分配与释放是配对的,分配的内存在哪里,使用完毕就要释放哪里的内存。在一个大型的项目中,如若多次分配内存,那么释放这些内存的顺序就成为难题。

好,这里我们遇到一个概念,就是内存泄露。我们就来仔细说说这个问题。顺便我们再讲一下内存溢出。内存泄漏(memory leak)是指程序在申请内存后,无法释放已申请的内存空间,有时一次内存泄漏就会造成一些影响,更不要说多处内存泄漏后所引发的更严重的后果。内存溢出(out of memory)指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错内存溢出。

内存泄漏的积累最终会导致内存溢出。内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带铁围栏的田地,你存完东西之后把铁围栏锁上之后,把钥匙丢了,那么结果就是这片田地将无法供其他人使用,也无法被回收。内存溢出问题,比方说栈,栈满时再做进栈必定产生空间溢出叫上溢,栈空时再做退栈也产生空间溢出称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。

内存溢出的原因主要有:内存中加载的数据量过于庞大,如一次从数据库取出过多数据;集合类中有对对象的引用,使用完后未清空;代码中存在死循环或循环产生过多重复的对象实体;使用的第三方软件中的BUG;启动参数内存值设定的过小。内存溢出的解决方案主要有:修改启动参数,直接增加内存。检查错误日志,查看错误前是否有其它异常或错误。对代码进行走查和分析,找出可能发生内存溢出的位置。重点排查以下几点:检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。检查代码中是否有死循环或递归调用。检查是否有大循环重复产生新对象实体。检查List、Map等集合对象是否有使用完后未清除的问题。List、Map等集合对象会始终存有对对象的引用,使得这些对象不能被回收。也可以使用内存查看工具动态查看内存使用情况。

内存泄漏的分类(按发生方式来分类)有如下几种:

(1)常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每一次被执行的时候,都会导致一块内存泄漏。(2)偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。(3)一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。(4)隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。

静态内存

静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。程序中的各种变量,在编译源程序时系统就已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会自动释放所占用的内存空间。变量的分配与释放,都无须程序员自行考虑。不必像动态内存那样,要掌握分配内存的大小、何时分配与释放等细节,因此使用静态内存对程序员来说很方便控制。

使用静态内存减少了很多内存资源的风险,如内存泄露、内存耗尽等问题,但减少了风险的同时也带来了弊端。在使用一个数组时,静态内存会预先定义数组的大小,定义数组前并不确定数组中会存放多少数据,若在使用时,存放在数组中的数据大于数组的容量,那么,就会出现溢出问题;然而存放在数组中的数据小于数组的容量很多时,就会造成内存空间的浪费。

静态内存是由编译器来分配的,释放是由变量的作用域所决定的,即当一个变量定义在一个自定义的功能函数中时,如果这个函数结束,该变量也会随之释放。这样,使用指针由子函数向主函数传递数据类的问题就无法实现了。因为子函数中的变量在子函数结束时,就会被释放,所以无法将值带回到主函数。但是事情总会有解决的办法,那就是可以在主函数中定义变量,在子函数中使用主函数中定义的变量传递值。

动态内存与静态内存总结

动态内存与静态内存是两种不同的分配内存的方式,那么下面我们针对它们在分配方式上存在什么样的区别进行总结。

(1)静态内存的分配是在程序开始编译时完成的,不占用CPU资源;而动态内存的分配是在程序运行时完成的,动态内存的分配与释放都是占用CPU资源的。(2)静态内存是在栈上分配的;而动态内存是在堆上分配的。(3)动态内存分配需要指针和引用数据类型的支持,而静态内存不需要。(4)静态内存分配是在编译前就已经确定了内存块的大小,属于按计划分配内存;而动态内存的分配是在程序运行过程中,根据需要随时分配的,属于按需分配。(5)静态内存的控制权是交给编译器的,而动态内存的控制权是由程序员决定的。

好,我们今天就总结到这里。希望大家都能从上文中有所启发,都能有所收获。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值