Automatic Memory Management简析
从11g开始引入了AMM(Automatic Memory Management)的概念,AMM管理了SGA+PGA的内存分配,它允许将内存在SGA和PGAs之间进行转移,你只需要指定MEMORY_TARGET一个参数即可,剩下的事情全部交给oracle自己来做。
这里首先解释几个名词:
System global area (SGA)
SGA是一组共享内存结构,作为SGA组件,包含了一个oracle实例中的数据和控制信息。所有server和后台进程都共享SGA,SGA的数据包括缓存数据块和共享SQL区。
Program global area (PGA)
PGA是一块非共享的内存区域,单独存放每个oracle进程的数据和控制信息。当进程开始运行时oracle就会自动为这个进程创建pga,每个服务进程和后台进程都有一个PGA,而所有PGA的集合就构成了整个实例的PGA,数据库的初始化参数设置了整个实例的PGA大小。
User global area (UGA)
UGA是每个用户会话的内存
Software code areas
Software code areas是用来存放要执行或可以执行的代码,Oracle数据库代码存储在软件区域中,该区域通常与用户程序位于不同的位置,即更为排他或受保护的位置。
AMM几个重要的参数如下:
- MEMORY_MAX_TARGET
- MEMORY_TARGET
- SGA_MAX_SIZE
- SGA_TARGET
- PGA_AGGREGATE_TARGET
AMM的功能由内存管理器进程实现,简称MMAN。对于进程的私有内存,可以被释放然后用于共享。这样就会产生很多重要的结果,在一个有很多复杂sql的系统中,含有很多并行,进程通常会进行大量的排序和表关联。这些操作十分消耗MEMORY_TARGET里的空闲内存。而越来越多这样的进程启动时,则需要更多的私有内存,MMAN则会转向SGA尝试将共享的内存重新分配到私有内存当中。
当PGA需要更多的内存时,MMAN会扫描SGA的空闲列去查找那些没有被使用的内存块,MMAN合并这些空闲的块,直到创建一个连续的块,其大小与granule的大小相同。然后这个granule会从SGA中解锁,然后重新交还给操作系统去用于作为PGA当中的私有内存。
这里就会有一个疑问,oracle到底是如何将SGA当中共享的内存转移到私有的的PGA当中去的呢?传统的sysv shm接口应该是无法从单个共享的内存段中回收和释放内存的,所以需要深入研究oracle是如何实现的。
检查实例的共享内存id
1 | [oracle@localhost ~]$ sysresv |
检查对应的sysv SHM段
1 | [oracle@localhost ~]$ ipcs -m |
这里有些很奇怪的地方,我设置的memory_target参数为2g,而这里sysv shm段每个却只有4k,那SGA包含的共享内存到哪去了?根据前面对SGA的定义,可以通过检查oracle实例的进程绑定的内存。
1 | [oracle@localhost ~]$ pmap `pgrep -f lgwr` |
通过pmap的输出可以看到,oracle采用/dev/shm来实现共享内存,会有多个16MB大小的文件来匹配oracle服务进程的地址空间,这里是Linux的面向POSIX的SHM实现,任何东西包括空闲内存都是一个文件。
Linux共享内存:
共享内存:
共享内存是进行程序之间的数据传输的一个非常有效率的方式,允许一个或多个进程来通过出现在其所有虚拟地址空间中的内存进行通信。POSIX,system-v提供了一个标准的API来使用共享内存。POSIX共享内存调用是根据UNIX原理,即如果你对一个对象执行输入/输出操作,则该对象必须是一个文件。所以当对一个POSIX共享内存对象进行读取和写入时,后者必须被当做一个文件。POSIX共享内存对象是一个内存映射文件,POSIX共享内存文件都是由/dev/shm挂载的tmpfs文件系统来提供。目前仅有两个专门的POSIX共享内存系统调用,即shm_open和shm_unlink,用来打开和unlink调用文件。
SYS-V共享内存段的大小在创建的时候就是固定的,相反,对于由文件或POSIX共享内存对象支持的映射,我们可以使用ftruncate()来调整基础对象的大小,然后使用munmap()和mmap()重新创建该映射。
为什么在AMM中使用POSIX共享内存?
为了用一个单独的memory_target参数来管理SGA和PGA的大小,并且对于单独的共享内存段来进行收缩和释放内存的操作来说,sysV SHM接口则并没有那么的具有弹性。所以oracle通过使用Linux的面向POSIX的SHM来实现,而要实现POSIX共享内存,我们需要ramfs或者tmpfs。在oracle实例启动以后,可以看到/dev/shm下面有一堆文件。
由于SGA的内存分配被分成了很多小块,oracle会更加容易去释放SGA的内存返还给操作系统,进而分配到那些系统进程当中。
1 | SQL> show parameter memory_target; |
这里已经分配了2048M的内存,当memory_target小于1g时,granule大小是4M,超过1g时,granule大小则为16M。这里可以检查一下已经分配了多少个granules
1 | [oracle@localhost ~]$ pmap `pgrep -f lgwr` | grep 16384K | wc -l |
而总大小为128*16M=2G。
也可以直接从/dev/shm系统目录下去查看
1 | [oracle@localhost ~]$ ls -l /dev/shm | grep test |
如果没有设置MEMORY_TARGET,/dev/shm下面的共享内存段则会消失,而MMAN会转向值使用正常的共享内存,也就是ipcs -ma命令可以看到的那些。
/dev/shm下面有很多内存块大小是为0的,这些内存块是由于需要空间或PGAs区域时被破坏掉的受害者。如果通过之前的pmap输出仍然可以看到这些内存映射到地址空间,但是这些并不会被使用因为oracle知道这些内存实际上是已经释放的。
如果修改增大pga_aggregate_target的参数,则会看到有很多原本为16M大小的文件会变成0。
这里就要提到一个关于MEMORY_TARGET的常见的错误,比如你没有设置/dev/shm为一个合适的值,则oracle会在分配新的POSIX共享内存失败,这样就会导致你无法使用MEMORY_TARGET参数,错误如下
1 | SQL> ORA-00845: MEMORY_TARGET not supported on this system |
也可以参考之前的一篇文章
https://www.xbdba.com/2019/04/11/ora-00845-memory_target-not-supported-on-this-system/