相关历史文章(阅读本文之前,您可能需要先看下之前的系列)
色谈Java序列化:女孩子慎入 - 第280篇
烦不烦,别再问我时间复杂度了:这次不色,女孩子进来吧 - 第281篇
双向链表,比西天还远?- 第282篇
面试不再怕,让LRU无处可逃 - 第283篇
爱我,就要懂我 – Memcached- 第284篇
内存管理,难于上青天?- memcached - 第285篇
你懂她,可惜你不懂我「LRU 」- Memcached- 第286篇
分布式算法真是吊炸天 – memcached- Memcached - 第287篇
悟纤:师傅,什么是内存泄露、内存溢出、内存碎片化?
师傅:徒儿,你问题有点小多呐,要不这次你自己探索下?
悟纤:好的,师傅徒儿这就去探索下。
师傅:那你去探索下吧,重点了解下内存碎片化。
悟纤:徒儿这就去,筋斗云..
师傅:徒儿…百度就可以了。
「话还没出口,悟纤已经到了十万八千里了。」
一、内存区
悟纤:要想了解内存泄露之类的,需要先查查数据存在哪里吧?
悟纤:嗯、很有道理,这就走起。
在内存中供用户使用的内存区有:静态存储区、动态存储区、程序存储区。
悟纤:啥是静态存储区?这个我得去查查。
静态存储区:内存在程序编译的时候就已经分配好,这块内存在整个计算机内存中位于较低的地址,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
悟纤:要理解动态存储区,要先理解下动态存储分配。
动态存储分配:在目标程序或操作系统运行阶段动态地为源程序中的量分配存储空间,动态存储分配包括栈式或堆两种分配方式。
动态存储区:允许目标程序或操作系统运行阶段动态地为源程序中的量分配的内存空间就是动态存储区。
悟纤:那程序存储区就是存放程序语句的,我真是太棒了。
悟纤:师傅,我探索的怎么样?
师傅:不错,不错,继续不要停。
二、泄露/溢出/碎片
悟纤:吃饱喝足,继续研究。
2.1 内存泄露
动态存储区一般是在程序运行过程中根据需要动态去分配和释放的内存区域。这块内存区域需要开发人员在使用完毕之后进行释放,如果没有释放动态分配的内存区域就会造成内存泄漏。相应的这块区域也不能够被使用。
BTW:简单来说,就是你使用了一块内存区域,但是却没有释放,那么这块内存区域谁都用不了了,这就是内存泄漏。
2.2 内存溢出
当一个程序向系统申请的所需内存大于系统可提供使用的内存时,这个时候就会产生内存溢出。
举个例子:有一个可以装50ml水的空杯子,但是你非得向这个被子里面倒了100ml水,那么这个时候多余的水肯定会流出来,这就是水溢出了。那么放到内存来讲,就是内存溢出。
BTW:内存泄漏积累到一定程度,会占用很多内存资源,从而导致内存溢出。
2.3 内存碎片
内存碎片分为外部碎片和内部碎片。
(1)外部碎片
外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。
产生的原因:外部碎片是出于任何已分配区域或页面外部的空闲存储块。这些存储块的总和可以满足当前申请的长度要求,但是由于它们的地址不连续或其他原因,使得系统无法满足当前申请。
(2)内部碎片
内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的空间。
产生原因:由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免。
三、内存碎片产生原因及解决办法
3.1 产生原因分析
使用最原始的标记分配方法:系统通过维护一个内存信息表来管理内存的状态。如下:
(1)当程序申请一个长度为3的内存空间后:
(2)当程序再申请一个长度为2,以及长度为4的内存空间后:
此时,只剩1个可用空间。如果这时程序再来申请长度大于1的空间,就申请不了,也就是内存不够。
(3)现在,释放掉ID=2的空间:
我们发现,现在可用内存空间为3,但是,这3个空闲空间,并不是连续的。所以,如果程序现在申请长度为3的内存空间,同样会申请不了,会出现内存不够。业界把这种情况,称之为【内存碎片】。
3.2 解决方法
明明剩余有3个空间,却申请不了3个内存空间???
于是,工程师们,发明了基于页面的内存管理方式:首先,把物理内存,按照某种尺寸,进行平均分割。
(1)比如我现在以2个内存单位,来分割内存,也就是每两个连续的内存空间,组成一个内存页:
(2)接着,程序再申请长度为1,长度为2的空间:
(3)释放掉ID=2,内存页ID为3的那条内存空间信息:
(4)现在,就出现了之前的情况:目前一共有4个内存空间,但是不连续。不过,因为现在是分页管理机制,因此,现在仍然可以继续申请长度为4的内存空间:
这种方案是不是爽得多?没有碎片,能够尽量地全部用完空间,但也存在一些问题。
前面那种内存分配方式,虽然容易出现碎片,并且内存空间的利用率低,但是使用性能高,程序能直接从内存信息表获取内存地址,接着就可以直接按照地址来使用内存空间了。
但下面这种分页的方式,程序需要记录的是内存页ID,每次使用时,需要从内存页ID翻译成实际内存地址,多了一次转换。而且这种模式,会浪费一些内存,比如上面申请3个内存空间,实际分配了2个页面共4个内存空间,浪费了1个内存空间。
以上就是基本原理,实际系统中会做非常多的优化。目前各种主流操作系统都是分页的方式,因此你不需要太关心碎片。
四、悟纤小结
师傅:徒儿,不错,不错,探索的不错。总结下今天学习到的知识吧。
悟纤:徒儿这就总结一下。
总结:
(1)内存存储区:动态、静态、程序。
(2)动态存储区:运行阶段可分配的存储空间。
(3)静态存储区:编译阶段就分配好的存储空间。
(4)程序存储区:存放程序语句的存储空间。
(5)内存泄露:没有释放动态分配的内存区域就会造成内存泄露。
(6)内存溢出:程序申请的内存空间大于系统可申请的内存空间就会造成内存溢出。
(7)内存碎片:无法将内存分配给进程(外部碎片)以及分配出去的却不能被使用的空间(内部碎片)。
(8)内存管理方式:标记分配管理(容易产生内存碎片)和基于页面的内存管理(通过浪费内存空间来减少内存碎片)。
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à悟空学院:https://t.cn/Rg3fKJD
学院中有Spring Boot相关的课程:
à悟空学院:https://t.cn/Rg3fKJD
SpringBoot视频:http://t.cn/A6ZagYTi
Spring Cloud视频:http://t.cn/A6ZagxSR
SpringBoot Shiro视频:http://t.cn/A6Zag7IV
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/A6Zad1OH
SpringSecurity5.0视频:http://t.cn/A6ZadMBe
Sharding-JDBC分库分表实战:http://t.cn/A6ZarrqS
分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr