[English]
作者:
fuyuncat
来源:
www.HelloDBA.com
2. Oracle的内存管理
在这部分章节中,我们将从更底层的角度来了解Oracle的内存管理。当然,涉及到内存管理,就不可避免的要了解OS对内存的管理,在这以章节中,将谈到不少关于OS内存的管理知识。如果概念记得不清除了,可以找一本《操作系统》的书看看先。
2.1. Oracle内存管理基础
要了解内存管理,首先需要知道虚拟内存。而要了解虚拟内存,就先要知道CPU寻址。我们知道,目前主流的CPU都是32位或者64位的。那么这个位数指的是什么呢?就是指CPU的最大寻址能力。在CPU中,所有一切都是二进制表示的,那么一个32位CPU的寻址范围就是2^32=4G。但有可能一台机器的实际物理内存没有这么大,比如说只有2G。而程序员在编程的时候根本不用考虑实际物理内存多大,还是按照4G的内存来分配。这时,OS就提出了一个虚拟内存的概念,如果所寻址的数据实际上不在物理内存中,那就从“虚拟内存”中来获取。这个虚拟内存可以是一个专门文件格式的磁盘分区(比如UNIX下的swap分区),也可以是硬盘上的某个足够大的文件(比如win下的那个i386文件,好像是这个名字)。物理内存中长期不用的数据,也可以转移到虚拟内存中。这样的交换由OS来控制,用户看起来就好像物理内存大了一样。
虚拟内存寻址的一个好处就是可以时进程使用很大的虚拟内存地址,而无需考虑实际的物理内存的大小。这使得进程内存可以基于使用需要,从逻辑上分为几个不同段。这些段可能映射到不连续的虚拟内存地址上,以使内存能够够增加。
为了共享RAM,需要有一个叫做交换磁盘(swap disk)的特殊磁盘空间。交换磁盘的重要目的是保存程序的通过LRU算法换出的内存,这样可以使很多程序能够共享有限的RAM。一旦非活动程序的RAM页被写入交换磁盘(Page Out),操作系统可以使空出来的内存用于其他活动的程序。如果非活动程序稍后又继续执行,被page out的RAM页又重新从交换磁盘中载入RAM(Page in)。这种重新载入RAM页的动作就叫交换,而交换是非常消耗时间的,并且会降低相关程序的性能。
尽管交换磁盘确保并发的RAM使用量能大于实际的RAM总理,但是为了确保最佳性能,交换空间绝不要被活动程序所使用。这是因为从交换磁盘上读取page out的RAM页要比直接从RAM中读取内存页要慢14000倍。磁盘访问都是以毫秒记的,而RAM访问是以10亿份之一秒记的。
2.1.1. Oracle的内存段类型
段(Segement)在OS上是对不同内存的使用目的和存放位置不同的区分。和一般的程序一样,Oracle使用以下几种段类型:
o 程序文本(Pragram Text)
文本段包括了程序本身的可执行的机器代码(除动态链接库以外)。文本段一般标识为只读,因此它能被多个进程共享来跑同一个程序。
o 初始化全局数据(Initialized Global Data)
这一段包括了被编译器初始化的全局数据,比如用于跟踪数据的字符串。初始化数据能被修改,因此它不能被运行同一程序的多个进程共享。Oracle很少使用这个段。
o 未初始化全局数据(Uninitialized Global Data)
未初始化全局数据一般称为BSS(Block Started by Symbol 以符号开始的块)段。这一段包括了静态分配的全局数据,这些数据在进程运行时被进程初始化。Oracle也很少使用这个段。
o 数据堆(Data Heap)
数据堆被用于进程在运行时,通过使用系统调用malloc()或sbrk()动态分配内存。Oracle将数据heap用于PGA。
o 执行堆栈(Execution Stack)
无论什么时候一个函数被调用,它的参数和返回上下文被push到一个执行堆栈中。返回上下文实际上是一组CPU注册值,这些注册值描述了进程在调用函数时那一刻的状态。当调用结束后,堆栈被POP而上下文被保留,以使执行能从函数调用时的结构状态立即执行下去。堆栈同时还保留了代码块的本地变量。堆栈大小依赖于函数嵌套或递归调用的深度、参数和本地变量所需的内存大小。
o 共享库(Shared Libraries)
共享库是一个与位置无关的可执行代码集,这个集合实现了许多程序——特别是系统调用功能——所需要的功能。共享库段也是只读的,它被所有的进程(包括Oracle进程)共享。共享库无需保存一份在内存中。当调用了共享库中的一个函数后,进程需要打开共享库文件,然后通过系统调用mmap()将它映射到它的地址空间去。
使用共享库的另外一种方法是在程序文本段本身将需要的系统调用include进去。在那些不支持共享库的操作系统中或用上面方式有问题时就需要这样做。在大多数操作系统中,Oracle使用共享库作为来实现系统调用而不是实现Oracle代码本身。然而,Java类库都是编译好的,并且作为共享库动态链接的。
o 共享内存段(Shared Memory Segment)
共享内存允许关联的进程共同读写内存中的同样数据。每个需要在共享内存段中寻址的进程都需要先将这段内存附到它自己的虚拟内存地址中去(一般通过shmat()系统调用实现)。Oracle SGA就是使用的共享内存段。
2.1.2. Oracle的内存管理模块
Oracle中有两个内存管理模块。一个是内核服务内存管理模块(Kernel Service Memory KSM);一个是内核通用堆管理模块(Kernel Generic Heap KGH)
在X$表中,有两种用于这两个模块的表,它们就是以KSM和KGH开头的表。这两个模块相互非常紧密。内存管理模块是负责与操作系统进行接口以获取用于Oracle的内存,同时还负责静态内存的分配。这个模块中比较重要的X$表是X$ksmfs,它记录了固定 sga、buffer cache、log buffer在内核服务内存中的分配。而堆管理模块则负责动态内存的管理。这也就是为什么SGA和PGA中堆又叫可变内存区了。Shared Pool、Library cache和PGA的堆都是由这个模块管理的。
除了还包含一个保留列表的这一特性外,Shared Pool中的子堆具有与Shared Pool本身相同的结构。内存是以Chunk为单位分配的。空闲的chunk按照大小来组织在相应的空闲列表(Free List)中。而未pin住的、可重建(unpinned recreatable)的chuck被维护在两个分别用于周期性chunk和短期chunk的LRU链表中。子堆还有一个包含少许空闲内存的主永久内存chunk。子堆也许还包含子堆,一共可以嵌套到四层。
子堆的概念非常重要,因为大多数被缓存在shared pool中的对象实际上都被保存在子堆中,而不是保存在最上一层的堆中。在一个子堆中寻找空闲空间就相当于在shared pool中寻找空闲空间。它们的不同处就在于子堆可以通过分配新的扩展段(extent)来增长,而shared pool具有固定的扩展段数(10g引入SGA自动管理特性后,shared pool的扩展段数也是可变的)。为子堆分配新的扩展段受到扩展段大小的限制,因此会存在这样的可能,因为没有任何一个父堆可以分配一个所需最小扩展段大小的chunk导致在子堆中查找一个小的chunk失败(抛4031错误)。
为了减少内存错误,在10g中,引入了多个隐含参数对扩展段进行控制,
_bct_buffer_allocation_min_extents - 每次buffer cache分配时的最小扩展段数(1)。
_compilation_call_heap_extent_size 编译调用时分配堆的扩展段的大小(16384)10gR2引入。
_kgl_fixed_extents library cache内存分配时是否使用固定的扩展段大小(TRUE),10gR2引入。
_mem_std_extent_size 固定扩展段大小的堆的标准扩展段大小(4096),10gR2引入。
_minimum_extents_to_shrink 当内存收缩时,收缩的最少扩展段数(1)
_total_large_extent_memory 分配大扩展段的内存数(0),10gR1引入,10gR2中已经废除。
_pga_large_extent_size(1048576)和_uga_cga_large_extent_size(262144)控制了当使用系统函数mmap()初始化时,PGA、UGA和CGA扩张段的最大大小。
2.1.3. 内存分配颗粒Granule
在Oracle的内存分配和管理中,有一个重要的单位:Granule(颗粒)。granule是连续虚拟内存分配的单位。Granule的大小依赖于SGA的总的大小(即SGA_MAX_SIZE大小)。当SGA小于128M时,granule为4M,SGA大于128M时,granule为16M。
Buffer Cache、Shared Pool、Large Pool和Java Pool(在10g中,还包括Streams Pool、KEEP buffer cache、RECYCLE buffer cache、nK Buffer Cache、ASM Buffer Cache)的增长和收缩都是以granule为单位的。SGA的各个组件的大小增长、收缩情况可以通过视图v$sga_dynamic_components(这个视图在1.1.2中有介绍)来观察。
在实例启动时, Oracle先分配granule条目(Entry),使所有granule能支持到SGA_MAX_SIZE的空间大小。如果没有设置PRE_PAGE_SGA和LOCK_SGA,在实例启动时,每个组件请求它所需要的最少granule。
因此最小SGA(占用物理内存)是3个granule,包括:
o 固定SGA(包括redo buffer)一个granule
o Buffer Cache一个granule
o Shared Pool一个granule
我们可以通过“ALTER SYSTEM”命令来修改分配给各个组件的granule数。当DBA想要给组件增加granule时,需要考虑实例中是否还有足够的granule来分配给新增加的组件大小(即增大后,各个组件之和小于SGA_MAX_SIZE)。有一点要注意,ALERT SYSTEM指定的是新的组件大小,是内存的大小,不是granule数。而Oracle在分配内存时,会以granule为单位,如果新分配的大小不是granule大小的倍数,则会使用最接近且大于分配数的granule的倍数值。例如,Unix中,当granule为16M,新分配组件大小为120M(不是16的倍数),Oracle则会实际分配128M(16×8);32位windows下,SGA小于1G时granule是4M,大于1G时granule是8M。
执行ALERT SYSTEM扩展组件大小后,前台进程(即执行ALTER SYSTEM命令)的进程会将SGA中的可用的granule(按照扩展新增大小计算)先保留下来。当这些granule被保留后,前台进程将后续处理交给后台进程。后台进程负责将这些保留的granule加到指定的组件的granule列表中去。这就完成了一次对SGA组件的扩展。
Granule的默认大小是根据以上规则规定的,但也可以通过隐含参数_ksm_granule_size来修改(强烈不建议修改)。另外,隐含参数_ksmg_granule_locking_status可以设置内存分配是否强制按照granule为单位进行分配。
2.1.4. SGA内存
当一个Oracle实例启动后,主要的SGA区的大小一开始基于初始化参数计算得出。这些大小可以通过show sga显示(实例启动后也会显示出这些信息)。但是,在共享内存段分配之前,每个区(Area)的大小都只有大概一个内存页大小。当需要时,这些区被分为一些子区(sub-area),因此没有一个子区会大于操作系统所限制的共享内存段(UNIX下受SHMMAX限制)的大小。对于可变区,有一个操作系统规定的最小子区大小,因此可变区的大小是最小子区大小的倍数。
如果可能,Oracle会为整个SGA分配一个单独的共享内存段。然而,如果SGA大于操作系统限制的单个共享内存段的大小时,Oracle会使用最佳的算法来将所有子区组织在多个共享段中,并且不超过SGA最大大小的限制。
严重的页入/页出会导致很严重的系统性能问题。然而,大内存消耗导致的间断的页入/页出是没有影响的。大多数系统都有大量的非活动内存被page out而没有什么性能影响。但少量的页出也是有问题的,因为这会导致SGA中那些中度活性的页会经常page out。大多数操作系统提供了一个让Oracle锁住SGA(设置lock_sga参数为TRUE)到物理内存中的机制以防止page out。在某些操作系统中,oracle需要有特定的系统权限来使用这一特性。
当共享池分配了一个chunk,代码会返回给执行分配的函数一个注释。这些注释可以通过表X$KSMSP的字段KSMCHCOM查到。它们同时描述了这块内存分配的目的。如以下语句:
select ksmchcom, ksmchcls, ksmchsiz from x$ksmsp;
2.1.5. 进程内存
进程包括程序代码(文本)、本地数据域(进程堆栈、进程堆【主要是PGA】和进程BSS【未初始化的全局数据】)和SGA。程序代码的大小由基本内核、内核、联机的网络情况以及所使用的操作系统决定的。SGA大小是由Oracle初始化参数决定的。而这两部分是共享。随着用户的增加,它们与单个Oracle服务进程的关系越来越小。它们是可以通过修改Oracle配置参数来改变的。
本地数据域中的堆栈是根据需要来增大、缩小的。然而,堆就不会释放内存了。堆的主要组成部分是PGA。而影响PGA的主要因素是sort_area_size(如果没有配置PGA_AGGREGATE_TARGET的话)。因此,非自动PGA内存管理模式下,可以通过控制sort_area_size来控制PGA的大小。
因此,总的来说,可以有以下方法来限制进程内存大小:
1、 降低相关的内核参数;
2、 通过Oracle参数来降低SGA;
3、 通过减小sort_area_size来降低PGA。
在UNIX平台中,一般通过操作系统命令如“ps”或“top”来定位一个经常的内存大小。这些工具可以预警那些大量占用内存的进程。但是,这些工具统计出来的Oracle进程的内存情况往往是是实际PGA内存是有出入的。
这是为什么呢?有两种原因会导致错误的进程内存报告错误:将那些非进程私有的(如共享内存)计算进去了;操作系统没有回收空闲内存。下面详细解释它们。
· 统计了非私有内存
一个内存中的进程包含以下几个部分:
o 共享内存(SGA)
o 共享库(包括公用和私有的)
o 私有数据(指数据段【DATA】或堆)
o 可执行部分(指文本【TEXT】段)
而SGA和TEXT部分是被所有Oracle进程共享的。它们只会被映射到内存中一次,而不会未每个进程做映射。因此,这些内存不是一个新的Oracle进程导致的内存增加部分。
· 操作系统没有回收空闲内存
通常,一部分内存被一个进程释放了后,并没有立即返回到操作系统的空闲池中,而是继续保持与进程的关联,直到操作系统内存不足时,才会回收这些空闲页。所以操作系统工具报告的进程内存大小可能会被实际大。
从Oracle的角度来看,一个服务进程的私有内存包括多个Oracle“堆(heap)”。在Oracle的术语中,堆就是一个受管理的内存区。而对于操作系统来说,这仅仅时分配给一个应用程序的另外一块内存而已。PGA和UGA中都关联到堆。
Oracle当前或者曾经在这些堆中拥有的内存总数可以通过以下语句统计出来:
SQL> select statistic#, name, value
2 from v$sysstat
3 where name like '%ga memory%';
STATISTIC# NAME VALUE
---------- -------------------------- ------------------------------
20 session uga memory 8650004156
21 session uga memory max 778811244
25 session pga memory 50609488
26 session pga memory max 58007200
查询所有会话的堆大小可以用以下语句实现:
select value, n.name|| '('||s.statistic#||')' , sid
from v$sesstat s , v$statname n
where s.statistic# = n.statistic#
and n.name like '%ga memory%'
order by value;
但是,查询出来大的PGA或UGA并不一定说明有问题。它们的大小受到以下参数影响:
o SORT_AREA_SIZE
o SORT_AREA_RETAINED_SIZE
o HASH_AREA_SIZE
另外,过多的使用PL/SQL结构体(如PL/SQL TABLE、ARRAY)也会导致会话内存增大。
上一篇:Oracle内存全面分析(9) | 下一篇:Oracle内存全面分析(7) |
本类中的所有文章 |