内存是什么?

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存是什么?相关的知识,希望对你有一定的参考价值。

最近在大佬的视频中学习内存知识:
视频地址【计算机知识串讲】从下到上看内存


文章目录


1.内存条,总线,以及DMA 认识


内存条:

CPU与内存条之间使用了 数据总线地址总线进行连接使用;

总线还有IO总线,控制总线,局部总线,PCIE总线…

IO总线:比如USB,也就是通用串行总线,

PCIE总线:比如使用的显卡就是通过PCIE总线与CPU进行交互连接;

在散热板下还有一部分即南桥;

这个南桥会接入DMA控制芯片;

这里DMA的知识, 在学习IO多路复用知识时之前有提到过;

这里CPU为DMA下达指令时,也为他发放了使用总线的权限; 他们之间是轮流使用总线的控制权;像是一种隔离关系.


2.内存管理与分配


操作系统的内存管理


逻辑地址:也就是程序自身看到的内存空间,作为一个抽象的地址;逻辑内存需要映射到物理内存上以后才能完成对内存的操作.

那么为什么程序操作的是逻辑内存地址, 而不是直接操作物理地址呢?或者直接对内存条操作?

  • 由于程序无法知道真正的物理地址,那么就需要进行映射;
  • 程序硬件可用的地址时变化的,由于涉及到进程的操作;
  • 除非使用的是单进程的机器,否则都有可能因为进程变化出现安全问题;

逻辑地址与物理地址映射处理

(1)首先看这种简单的基于偏移量的映射;
虽然比较便捷,但是存在着安全问题:
程序使用的内存是无法固定计算的,在运行使用的过程中会发生变化;

  • 假如程序实际使用的内存比这个预算的偏移量还要小,那么也就是说有很多内存没有被完全利用,也就是产生的内碎片问题;
  • 假如程序运行之后,空间被释放;但是下一段程序需要使用的连续空间比这块空闲的空间还大一些,那么就无法使用,长期无法使用的闲置内存也就产生了外碎片问题.

(2)分页式的内存映射关系;

逻辑内存与物理内存之间采用页表的映射关系作为中介,查找对应的关系.

当然,不同的进程之间肯定不能使用同一个页表进行操作;


  • 内存中的一个地址存放的数据量为一个字节[byte];
  • 32位的操作系统物理地址个数为232 , 所以仅使用4GB内存;
  • 多个程序使用的内存总和会大于物理内存,那么就需要借助于磁盘来进行存储,不经常使用的内存就会先存入到磁盘,然后页表对应的帧号显示时记录的是磁盘.
  • 采用了分页之后,每个程序都可以拥有一块很大的逻辑空间,通过映射磁盘与相应的置换算法,使得内存可用空间变大;
  • 分页时对不同的进行保持内存隔离,降低了内存碎片问题.


内存案例分析:

机器 : 32位系统 , 256MB内存 ,4KB大小的页;
程序: 32位程序;

4K = 12Bit (212)
逻辑地址: 32bit = 20bit页号 + 12bit偏移
物理地址: 28bit = 16bit帧号 + 12bit偏移

0x000011a3
那么即可拆分为 页号 : 00001 + 偏移量: 1a3


实际上,对于这样简单的分页机制,还可以用TLB快表进行时间上的优化:
将常用的几个页表项(8-128个) 存入到访问速度较快的硬件中,比如:MMU内存管理单元.

先进行寻址查找到TLB快表,然后再查询PT页表,这个TLB快表的命中率非常高,因为程序常访问的页面并不会太多


程序内部的内存管理实际使用了一种逻辑上的分段机制;

malloc若申请了大于128KB的内存时,就会调用mmap,在堆和栈之间的区域申请内存,
也就是此处的lib位置,由于都是页,映射磁盘即文件映射内存时使用的系统调用


对于分段和分页结合的方式:
一个段分为多个页,在页表中存储了页号.段号的唯一映射的物理地址帧号;


由于虚拟地址有时候会比真实的物理地址还要大,那么就可以将 虚拟地址的页号直接映射对应磁盘;
那么在之后使用时,由于该页号对应的是磁盘,那么就有可能出现缺页中断的问题.


在Linux中 使用了一块swap区域作为磁盘的交换分区.

对于缓冲缓存理解:
page页级别的缓存:

Page作为单位,缓存文件内容,缓存在Page Cache中的文件数据,可以快速被用户读取,且对于使用了buffer缓冲的写操作,数据在写入到Page Cache中时即可立刻返回,无需等待数据被持久化到磁盘中,提升了上层应用读写文件的性能.

Buffer缓冲区级别的缓存:

  • 在磁盘中的最小数据单位是 sector,每次读写磁盘都是以sector作为单位对磁盘进行操作,注意sector和实际的磁盘类型相关.
  • 无论用户期望读取的大小是多少,在最后访问磁盘时,都会以sector作为单位进行读取;
  • 假如直接裸读磁盘,那么对于数据的读取效率是比较差的,若用户期望向磁盘中的某位置写操作一个byte的数据,也必须刷新一个sector,那么在写入这1byte之前,需要将1byte数据所在的磁盘sector数据都给读取到,然后在内存中,修改对应的数据,然后再将整个修改结束的sector数据全部写入到磁盘中;
  • 为提升磁盘的访问性能,那么实际上在内核会为磁盘的sector构建一层缓存,以sector的整倍数力度为单位,在内存中缓存部分sector数据,当出现读取数据的请求时,可以直接从内存中读取对应的数据,也可以直接从内存中更新指定部分的数据,然后采用异步,将更新的数据写入对应磁盘的sector中.

可查看windows系统下使用的内存情况;这里已提交就是申请的内存量大小,


3.内存相关的系统调用


系统调用也就是用户态切换内核态,申请内存时会用到。


例如:分次申请内存进行使用。


若改为仅申请一个字节,然后将指针强转为int类型,
first+1 向后移动4个字节;
实际上申请了一页的内存;

若是向后移动4096个字节;但是已经超出了该页的范围.,所以无法存储;


当myLock 了128K以上的内存时,就会触发mmap系统调用, 对应释放内存采用了 munmap;
参数: addr:起始地址, length:长度; prot: 权限标志符; flags: 指定映射对象的类型; fd: 文件描述符; offset: 偏移量;
这里fd设为-1,即直接申请内存; 申请的长度为100页;
注意申请之后不使用的话,无法得到该段内存;这里for循环使用到每一页的内存;

在查看进程时;由于每次使用该页时,会出现短暂的缺页,所以产生minflt


若直接将文件通过文件描述符映射到内存中时;也是惰性的,即不使用内存就不通过申请;
即文件并没有直接读到内存中;

监控进程时,发现有一个比较大的错误;要使用的这一页对应的是磁盘,发生缺页错误;
之后将文件全部加载到物理内存中,后面的错误是因为要将虚拟内存对应到物理内存中.


mmap操作开始是在页表中存储页号对应磁盘地址;
在真正读取时就会触发缺页操作;将文件加载到内存中;
让用户空间与内核空间共享内存, 也就是 mmap操作节省了用户态与内核态之间的转换过程;[减少了内核空间->用户空间的拷贝];


4.Java中的内存


C++中的Klass使用java_mirror指针对应 Java中的Class对象

对象引用一般是指4个字节[开启了指针压缩];若未开启指针压缩则指的是8个字节;

  • 在Java中的对象是8字节对齐的,那么即可使用32bit地址 表示 232 *8Byte = 32GB的内存地址;即使用了普通对象指针压缩. 如果开启压缩,那么 元数据区就会为单独开出一部分压缩空间;这部分空间是连续的4GB大小,
  • 在堆内存中32G以内的都是默认开启指针压缩,每个对象地址使用4个字节表示,但超出32G就无法开启压缩,每个对象地址需要使用8字节表示.

开启/不开启指针压缩的区别;
开启后 出现压缩空间与非类空间;


案例,申请1GB内存的场景


以上是关于内存是什么?的主要内容,如果未能解决你的问题,请参考以下文章

腾讯一面:内存满了,会发生什么?

Java提高篇—— 简单介绍Java 的内存泄漏

老年代一直处于占满状态,为啥没有发生内存溢出

Java内存泄漏简单说明

什么是内存泄漏(转载自ImportNew)

腾讯一面被问到内存满了,会发生什么?