CPU模型,内存分页与调优,内核与用户空间

CPU模型

去过机房的同学都知道,一般在大型服务器上会配置多个CPU,每个CPU还会有多个核,这就意味着多个CPU或者多个核可以同时(并发)工作

1.1 CPU Register

CPU Register也就是 CPU 寄存器CPU寄存器CPU 内部集成的,在寄存器上执行操作的效率要比在主存上高出几个数量级

CPU中至少要有六类寄存器:指令寄存器(IR)、程序计数器(PC)、地址寄存器(AR)、数据寄存器(DR)、累加寄存器(AC)、程序状态字寄存器(PSW)。这些寄存器用来暂存一个计算机字,其数目可以根据需要进行扩充

按与CPU远近来分,离得最近的是寄存器,然后缓存,最后内存。所以,寄存器是最贴近CPU的,而且CPU只与寄存器中进行存取。寄存器从内存中读取数据,但由于寄存器和内存读取速度相差太大,所以有了缓存。即读取数据的方式为:

CPU〈------〉寄存器〈---->缓存<----->内存

当寄存器没有从缓存中读取到数据时,也就是没有命中,那么就从内存中读取数据

1.2 CPU Cache Memory

CPU Cache Memory也就是CPU 高速缓存。相对于硬盘读取速度来说内存读取的效率非常高,但是与 CPU 还是相差数量级,所以在 CPU 和主存间引入了多级缓存,目的是为了做一下缓冲。

CPU内部集成的缓存称为一级缓存(L1 Cache),外部的称为二级缓存(L2 Cache)。
一级缓存中又分为数据缓存(D-Cache)和指令缓存(I-Cache)。二者可以同时被CPU进行访问,减少了争用Cache所造成的冲突,提高了CPU的效能。
CPU的一级缓存通常都是静态RAM(Static RAM/SRAM),速度非常快,但是贵
二级缓存CPU性能表现的关键之一,在CPU核心不变化的情况下,增加二级缓存容量能使性能大幅度提高。而同一核心的CPU高低端之分往往也是在二级缓存上存在差异
三级缓存是为读取二级缓存后未命中的数据设计的一种缓存,在拥有三级缓存的CPU中,只有约5%的数据需要从内存中调用,这进一步提高了CPU的效率,从某种意义上说,预取效率的提高,大大降低了生产成本却提供了非常接近理想状态的性能

1.3 Main Memory

Main Memory 就是主存,主存比 L1、L2 缓存要大很多
注意:部分高端机器还有 L3 三级缓存。

内存中相关概念:

1.4 主存存取原理

目前计算机使用的主存基本都是随机读写存储器(RAM),现代RAM的结构和存取原理比较复杂,这里本文抛却具体差别,抽象出一个十分简单的存取模型来说明RAM的工作原理。

从抽象角度看,主存是一系列的存储单元组成的矩阵,每个存储单元存储固定大小的数据。每个存储单元有唯一的地址,现代主存的编址规则比较复杂,这里将其简化成一个二维地址:通过一个行地址和一个列地址可以唯一定位到一个存储单元。上图展示了一个4 x 4的主存模型。

主存的存取过程如下:

这里可以看出,主存存取的时间仅与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响,例如,先取A0再取A1和先取A0再取D3的时间消耗是一样的。

1.5 磁盘存取原理

与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。
下图是磁盘的整体结构示意图。

一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。
下图是磁盘结构的示意图。

盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。为了简单起见,我们下面假设磁盘只有一个盘片和一个磁头。
当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间

1.6 局部性原理与磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。

这样做的理论依据是计算机科学中著名的局部性原理:
当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

2 内存分页

虽然Java语言的这些特点很容易“惯坏”开发人员,使得我们不需要太关心到底程序是怎么使用内存的,使用了多少内存。但是我们最好也了解Java是如何管理内存的,当我们真的遇到OutOfMemoryError时不会奇怪地问,为什么Java也有内存泄漏:要快速地知道到底什么地方导致了 OutOfMemoryError,并能根据错误日志快速地定位出错原因

2.1 物理内存与虚拟内存

2.1.1 物理内存

所谓物理内存就是我们通常所说的RAM (随机存储器)。在计算机中,还有一个存储单元叫寄存器,它用于存储计算单元执行指令(如浮点、整数等运算时)的中间结果。寄存器的大小决定了一次计算可使用的最大数值。

连接处理器和RAM或者处理器和寄存器的是地址总线,这个地址总线的宽度影响了物理地址的索引范围,因为总线的宽度决定了处理器一次可以从寄存器或者内存中获取多少个bit。同时也决定了处理器最大可以寻址的地址空间,如32位地址总线可以寻址的范围为(0x0000 0000〜0ffff ffff。这个范围是2^32=4294967296个内存位置,每个地址会引用一个字节,所以32位总线宽度可以有4GB的内存空间。

通常情况下,地址总线寄存器或者RAM有相同的位数,因为这样更容易传输数据,但是也有不一致的情况,如x8632位寄存器宽度的物理地址可能有两种大小,分别是32位物理地址和36位物理地址,拥有36位物理地址的是Pentium Pro和更高型号。

不管是在Windows系统还是Linux系统下,我们要运行程序,都要向操作系统先申请内存地址。

通常操作系统管理内存的申请空间是按照进程来管理的,每个进程拥有一段独立的地址空间,每个进程之间不会相互重合,操作系统也会保证每个进程只能访问自己的内存空间。这主要是从程序的安全性来考虑的,也便于操作系统来管理物理内存。


其实上面所说的进程的内存空间的独立主要是指逻辑上独立,也就是这个独立是由操作系统来保证的,但是真正的物理空间是不是只能由一个进程来使用就不一定 了。因为随着程序越来越庞大和设计的多任务性,物理内存无法满足程序的需求,在这种情况下就有了虚拟内存的出现。

2.1.2 虚拟内存

虚拟内存的出现使得多个进程在同时运行时可以共享物理内存,这里的共享只是空间上共享,在逻辑上它们仍然是不能相互访问的。虚拟地址不但可以让进程共享物理内存、提高内存利用率,而且还能够扩展内存的地址空间,如一个虚拟地址可能被映射到一段物理内存、文件或者其他可以寻址的存储上。

一个进程在不活动的情况下,操作系统将这个物理内存中的数据移到一个磁盘文件中(也就是通常windows系统上的页面文件,或者Linux系统上的SWAP交换分区),而真正高效的物理内存留给正在活动的程序使用。在这种情况下,在我们重新唤醒一个很长时间没有使用的程序时,磁盘会吱吱作响,并且会有一个短暂的停顿得到印证,这时操作系统又会把磁盘上的数据重新交互到物理内存中。但是我们必须要避免这种情况的经常出现,如果操作系统频繁地交互物理内存的数据和磁盘数据,则效率将会非常低,尤其是在Linux服务器上,我们要关注Linux中swap的分区的活跃度。
如果swap分区被频繁使用,系统将会非常缓慢,很可能意味着物理内存已经严重不足或者某些程序没有及时释放内存。

点击了解Linux分区(目录中选择分区)

2.2 内存分页大小对性能提升原理

首先,我们需要回顾一小部分计算机组成原理,这对理解大内存分页至于JVM性能的提升是有好处的。

2.2.1 什么是内存分页

我们知道,CPU是通过寻址来访问内存的。32CPU的寻址宽度是 0~0xFFFFFFFF ,计算后得到的大小是4G,也就是说可支持的物理内存最大是4G。但在实践过程中,碰到了这样的问题,程序需要使用4G内存,而可用物理内存小于4G,导致程序不得不降低内存占用。
为了解决此类问题,现代CPU引入了 MMUMemory Management Unit 内存管理单元)

MMU的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由MMU负责将虚址映射为物理地址。MMU的引入,解决了对物理内存的限制,对程序来说,就像自己在使用4G内存一样。

内存分页(Paging)是在使用MMU的基础上,提出的一种内存管理机制。它将虚拟地址物理地址按固定大小(4K)分割成页(page)和页帧(page frame),并保证 页与页帧的大小相同,这种机制,从数据结构上,保证了访问内存的高效,并使OS能支持非连续性的内存分配。在程序内存不够用时,还可以将不常用的物理内存页转移到其他存储设备上,比如磁盘,这就是大家耳熟能详的虚拟内存。

页表其实是一个数组用于把虚拟页号物理帧号对应起来
其索引为VPN(虚拟地址) 索引值对应的项为PTE(页表项) 项中的值为PFN(物理页帧),每个PTE中还有很多别的内容,比如有很多不同的位:有效位、保护位、存在位、脏位、参考位

虚拟地址物理地址需要通过映射,才能使CPU正常工作。而映射就需要存储映射表。在现代CPU架构中,映射关系通常被存储在物理内存上一个被称之为页表(page table)的地方。
如下图:

从这张图中,可以清晰地看到CPU与页表,物理内存之间的交互关系。

由于页表是被存储在内存中的。我们知道CPU通过总线访问内存,肯定慢于直接访问寄存器的。为了进一步优化性能,现代CPU架构引入了TLBTranslation lookaside buffer页表寄存器缓冲),用来缓存一部分经常访问的页表内容。
如下图:

2.2.2 为什么要支持大内存分页

TLB是有限的,这点毫无疑问。当超出TLB的存储极限时,就会发生 TLB miss,之后,OS就会命令CPU去访问内存上的页表。如果频繁的出现TLB miss,程序的性能会下降地很快。
为了让TLB可以存储更多的页地址映射关系,我们的做法是调大内存分页大小。
如果一个页4M,对比一个页4K,前者可以让TLB多存储1000个页地址映射关系,性能的提升是比较可观的。

2.3 调整OS和JVM内存分页

Linuxwindows下要启用大内存页,有一些限制和设置步骤。

2.3.1 Linux

Linux限制:需要2.6内核以上或2.4内核已打大内存页补丁。
确认是否支持,请在终端敲如下命令:

### cat /proc/meminfo | grep HugeHugePages_Total: 0HugePages_Free: 0Hugepagesize: 2048 kB

如果有HugePage字样的输出内容,说明OS是支持大内存分页的。Hugepagesize就是默认的大内存页size
接下来,为了让JVM可以调整大内存页size,需要设置下OS共享内存段最大值和大内存页数量。

2.3.1.1 共享内存段最大值

建议这个值大于Java Heap size,这个例子里设置了4G内存。

### echo 4294967295 > /proc/sys/kernel/shmmax

2.3.1.2 大内存页数量

### echo 154 > /proc/sys/vm/nr_hugepages

这个值一般是 Java进程占用最大内存/单个页的大小 ,比如java设置 1.5G,单个页 10M,那么数量为 1536/10 = 154。
注意:因为proc是内存FS,为了不让设置在重启后被冲掉,建议写个脚本放到 init 阶段(rc.local)。

2.3.2 Windows

Windows限制:仅支持 windows server 2003 以上server版本
操作步骤:

  1. Control Panel -> Administrative Tools -> Local Security Policy
  2. Local Policies -> User Rights Assignment
  3. 双击Lock pages in memory, 添加用户和组
  4. 重启电脑

注意: 需要管理员操作。

2.3.3 单个页大小调整

JVM启用时加参数 -XX:LargePageSizeInBytes=10m
如果JDK是在1.5 update5以前的,还需要手动加 -XX:+UseLargePages,作用是启用大内存页支持。

2.4 大内存分页的副作用

因为每页size变大了,导致JVM在计算Heap内部分区(perm, new, old)内存占用比例时,会出现超出正常值的划分。最坏情况下是,某个区会多占用一个页的大小。不过后续jvm版本也在调整这个策略。
一般情况,不建议将页size调得太大,4-64M,是可以接受的(默认是4M)

3 内核空间与用户空间

3.1 定义

一个计算机通常有一定大小的内存空间,如使用的计算机是4GB的地址空间,但是程序并不能完全使用这些地址空间,因为这些地址空间被划分为内核空间用户空间。程序只能使用用户空间的内存,这里所说的使用是 指程序能够申请的内存空间 ,并不是程序真正访问的地址空间。

内核空间主要是指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。

3.2 为何需要内核空间和用户空间的划分

由于每个进程都独立使用属于自己的内存一样,为了保证操作系统的稳定性,运行在操作系统中的 用户程序不能访问操作系统所使用的内存空间。这也是从安全性上考虑的,如访问硬件资源只能由操作系统来发起,用户程序不允许直接访问硬件资源。如果用户程序需要访问硬件资源,如网络连接等,.可以调用操作系统提供的接口来实现,这个调用接口的过程也就是系统调用。

每一次系统调用都会存在两个内存空间的切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收到远程主机的数据,然后再从内核空间复制到用户空间,供用户程序使用。这种从内核空间到用户空间的数据复制很费时,虽然保住了程序运行的安全性和稳定性,但是也牺牲了一部分效率。但是现在已经出现了很多其他技术能够减少这种从内核空间到用户空间的数据复制的方式,如Linux系统提供了 sendflle文件传输方式。

内核空间和用户空间的大小如何分配也是一个问题,是更多地分配给用户空间供用户程序使用,还是首先保住内核有足够的空间来运行,这要平衡一下。如果是一台登录服务器,很显然,要分配更多的内核空间,因为每一个登录用户操作系统都会初始化一个用户进程,这个进程大部分都在内核空间里运行。在当前的 Windows 32位操作系统中默认内核空间和用户空间的比例是1:1 (2GB的内核空间,2GB的用户空间),而在32位Linux系统中默认的比例是1:3 (1GB的内核空间,3GB的用户空间)

3.3 内核用户空间切换的影响

java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态核心态之间切换,这种切换会消耗大量的系统资源,因为用户态内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间

展开阅读全文

页面更新:2024-03-20

标签:内核   主存   内存   用户   寄存器   空间   磁盘   模型   地址   程序   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top