上篇分析了系统层面上看内存使用情况,这次谈的是页表,而它涉及到性能和数据进程。
进程内存
谈页表前先了解下进程的地址空间,也就是进程自己如何分配内存的,我们把进程的地址空间或者说是内存叫做逻辑的或者虚拟的内存。
这个图上的VIRT列表示该进程的虚拟内存,逻辑的,进程的地址空间。
我们在大学里学过很多语言,比如说汇编,C语言,C++语言,JAVA语言。
这些语言都有些特点,就是一个比一个高级。汇编语言,我们经常要MOV来MOV去,目的就是从数据段把数据调用到寄存器当中,这里的工作经常要算地址,跟地址打交道。而C语言则解放了编程者,把讨厌的地址给隐蔽了。可惜C语言必须要求编程人员自己去释放指针所指向的内存,否则造成内存泄漏。C++语言进一步地面向对象,而JAVA在C++基础上搞个内存回收机制,进一步解放编程人员,把讨厌的内存泄漏给解决掉了,集中精力搞业务。
所以说每个进程都有自己的内存空间,内存自然需要地址来寻址。32位进程空间是4GB,而64位进程空间是16PB。刁那么大!! 我们还是先拿32位的进程来谈。因此我们的进程的逻辑地址要进行转换成物理地址,物理内存的地址。很显然物理内存一定小于进程的内存空间。其逻辑地址的数量要远远多余时间的物理地址的数量。虽然现在内存高达几个TB,不过我们现在先谈32位的,也就是2000年的时候,把现在几TB内存暂时忘记吧!!
比如说目前服务器物理内存只有2GB,进程的内存是4GB。物理内存2GB按4KB大小进行分页。约有524288个页。一般情况下进程不会那么变态地全部用完4GB。当然一个服务器也不会只有一个进程在上面跑,而是多个进程在使用物理内存。而系统会把不活跃的内存页交换到SWAP区域中。
因此每个进程运行的时候,都需要CPU来参与地址转换的工作,如果进程需要的内存越大,转换工作就越多。如果系统同时运行的进程越多,CPU负荷也越重,也就是双重压力。为了提升性能,linux在cpu中申请 固定大小的buffer,被称为TLB,TLB中保存有“page table”的部分内容,这也遵循了,让数据尽可能的靠近cpu原则。 进程的内存分布长得如下的样子,大家看看就行,又不是搞编程的,无需了解细节
页表
假如我们把物理内存看作一本书,《2GB》 按4开来出版大约52,4288页 。52万页啊!
MY GOD 那是个什么鬼,十万个为什么吗?好吧! 天下就是有这样一本书。一本书前面几页讲的是,书名,出版细节,前言,作者简介,友人赞美,目录。
关键词 目录 目录对应我们讲的页表,这是个很形象地比喻! 我们目录把52万页,一般按照章和节两级来组织。
目录本身要占用几页纸,拿我手上的这本书来说《Oracle性能优化》需要10页纸。显然系统管理2GB物理空间,52万页。目录本身需要占用多少内存呢?为了减少数量,32位采用3级,64位采用4级。32位的如下图
图中的线性地址 也就是进程的 逻辑地址,虚拟地址!CR3 就是CPU的寄存器,把每个活跃进程的页目录放进CR3寄存器当中,开始地址转换工作。
页目录2^10项,页表2^10项,每页空间4KB,即4KB*1024*1024=4GB。
linux 系统本身抽象提供一个四级分页的,如下图
在4级当中64系统最大支持物理内存是1024*1024*1024*1024*64=64TB
寻址
操作系统将虚拟地址映射为物理地址时,将虚拟地址的31-22这10位用于从目录表中索引到1024个页表中的一个,将虚拟地址的12-21这10位用于从页表中索引到1024个页表entry中的一个。
从这个页表entry中获得物理内存页的起始地址,然后将虚拟地址的0-12位用作4KB内存页中的偏移量,那么物理内存页起始地址加上偏移量就是进出所需要访问的物理内存地址。
4K的内存页大小做出的计算:
1)目录表,用来存放页表的位置,共包含1024个目录entry,每个目录entry指向一个页表位置,每个目录entry,4b大小,目录表共4b*1024=4K大小
2)页表,用来存放物理地址页的起始地址,每个页表entry也是4b大小,每个页表共1024个页表entry,因此一个页表的大小也是4K,共1024个页表,因此页表的最大大小是1024*4K=4M大小
由于32位操作系统不会出现4M的页表,因为一个进程不能使用到4GB的内存空间,有些空间被保留使用,比如用来做操作系统内核的内存。而且页表entry的创建出现在进程访问到一块内存的时候,而不是一开始就创建。在32位系统下,一个进程访问1GB的内存,会产生1M的页表,64位按照三级分页的话就是2M页表大小。
64位系统
4级分页 大约内存是 页目录 1024*8=8K 而它可以指挥1024页上级目录,每个页上级目录装满了约8KB大小,而页中间目录也装满约8KB。反之页中间目录指挥1024个页,页上级目录也指挥1024个页中间目录,而页目录也指挥1024个页上级目录。
页目录+页上级目录大小:8KB+1024*8KB=8MB
页中间目录大小 1024*1024*8KB=8GB
8GB+8MB+8KB 就是64位系统下管理64TB所需要的页表占用的物理内存空间
我们现在基本上使用128GB,96GB,32GB内存来跑数据库。
然并卵的事情,系统只有32GB物理内存,只有一个页表的话,就没有必要写了! 实际上是每个进程都有自己的页表,每个进程页表都要占用内存。进程越多占用内存也越多!
下面是真实的数据库,系统大约有601个进程
2017年 04月 17日 星期一 07:00:01 HKT
All Process Number:
601
System Process Number:
195
Grid RAC Process Number:
0
Oracle User Procuess Number:
333
Oracle DB BackGround Process Number:
30
Oracle DB Client Process Link Number:
273
Oracle User NO DB Procuess Number:
28
Other User Process Number:
79
系统内存信息
MemTotal: 32880396 kB
MemFree: 574800 kB
Buffers: 168224 kB
Cached: 27034372 kB
SwapCached: 22708 kB
Active: 20031832 kB
Inactive: 8285476 kB
Active(anon): 7373560 kB
Inactive(anon): 616020 kB
Active(file): 12658272 kB
Inactive(file): 7669456 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 20971516 kB
SwapFree: 18951556 kB
Dirty: 84 kB
Writeback: 0 kB
AnonPages: 1096832 kB
Mapped: 6885044 kB
Shmem: 6874828 kB
Slab: 353244 kB
SReclaimable: 269016 kB
SUnreclaim: 84228 kB
KernelStack: 5176 kB
PageTables: 3376492 kB
32GB系统里面关页表就占用了3.22GB,你说CPU有多累啊!
页表为什么会变大?
1 进程数量变多了
2 进程访问的内存变多了
3 进程没有释放内存
第一个比较好理解,因为连接数据库的连接进程变多了,一般情况下这是个固定值,应用程序采用了连接池,当然也有达到连接池最大值,连接池本身可以动态调整连接数量的。
第二个,是这样的一个SQL语句本来是全表扫描,原来的表就1个G大小,现在表经过1年的时间累积到了8GB,自然来个全表扫描,进程地址空间就要分配8GB的内存使用。
第三个 进程使用的内存除了进程自己free ,delete释放掉的话,基本上不释放哦!虽然JAVA有个回收机制,然并卵的是,它需要JAVA虚拟机,这个LINUX没鸟关系,使不上劲!因为连接池采用的是LINUX本身进程,又不是JAVA虚拟机上的。除非你KILL掉进程,才能释放掉内存和进程的页表内存! 然而我们采用连接池,是不会主动地杀死进程,或者说进程自己退出。只有达到最大连接数后,空闲时段连接池才释放掉连接,回归正常水平数。如果是正常水平的连接数量,自然就没机会去释放。
那有什么办法呢? 要不搞个18级分页如何?十八层地狱!! 这可能日后系统开发人员的事情。不过系统提高了一个选项就是扩大页空间。原来的4KB页内存,扩大到2MB,其他系统还更大。反正嘛!原来4开的纸,老子现在用2平方米的纸来写书,超大的书。你看过西方的古代电影,哈利波特里面的,当他们翻阅古书。你说那么大的书携带不方便啊,可是老子用书又不是到处带来带去的!
大页内存
cat/proc/meminfo
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
hugepage特点
linux系统启动,hugepage就被分配并保留,不会pagein/pageout,除非人为干预,如改变hugepage的配置等;
根据linux内核的版本和HW的架构,hugepage的大小从2M到256M不等。因为采用大page,所以也减少TLB和page table的管理压力。而我用到的CENTOS和ORACLE LINUX 全是2MB一个选择!
注意的是 HUGEPAGE内存,系统一启动就分配出来了,别的进程不可以用。如果设置太大了,会导致系统起不来啊!系统自己要使用4KB的页,它使用不上2MB的页!
查看系统版本 uname -r 必须大于2.6的内核才能支持!其实没关系只要你CAT MEMINFO 就知道了。
2.6.18-128.el5
首先修改limits.conf
[root@db101 ~]# vi etc/security/limits.conf
## 等于SGA_MAX_SIZE 下面是KB 锁定15G内存
free - t 获取系统内存值
##zengmuansha add 0122
oracle soft memlock 15826672
oracle hard memlock 15826672
Oracle用户下使用 ulimit -t 复查下
[ORACLE 11G] 必须关闭AMM(自动内存管理)特性才能使用hugepage
设置如下初始化参数:
ALTER SYSTEM SET sga_max_size=15455M SCOPE=SPFILE;
ALTER SYSTEM SET sga_target=15455M SCOPE=SPFILE;
ALTER SYSTEM SET PGA_AGGREGATE_TARGET=2048M SCOPE=SPFILE;
ALTER SYSTEM SET memory_target=0 SCOPE=SPFILE;
ALTER SYSTEM SET memory_max_target=0 SCOPE=SPFILE;
11.2.0.1版本 MEMORY_TARGET=0 设置会无效 必须通过INIT.ORA来屏蔽掉 再生成SPFILE;
配置分配hugepage的数量
nr_hugepages的计算公式:
nr_hugepages>=sga(mb)/Hugepagesize(mb)
echo "vm.nr_hugepages=3872" >> etc/sysctl.conf
一般情况下使用ROOT帐号,而且值要大于SGA/2MB的数量,才能成功生效。
一般情况下SYSCTL -P 也可以生效,能重启系统的话,先重启系统!
sysctl -p 后 数据库需要关闭后再启动,才可以使用到HUGEPAGE!
(5) 重启系统
reboot
(6) 启动数据库
sqlplus as sysba
startup
(7) 检查是否生效
root@db101:[/root]grep HugePages proc/meminfo
HugePages_Total: 3890
HugePages_Free: 17
HugePages_Rsvd: 0
为了确保HugePages配置的有效性,HugePages_Free值应该小于HugePages_Total 的值,表示SGA已经使用到了HUGEPAGE。
11.2.0.2之前的版本,database的SGA只能选择全部使用hugepages或者完全不使用hugepages。 11.2.0.2 及以后的版本, oracle增加了一个新的参数“USE_LARGE_PAGES”来管理数据库如何使用 hugepages