您的位置:首页 > 数据库 > Oracle

2. Oracle的内存管理

2013-05-27 20:41 141 查看

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,需要有一个叫做交换磁盘(swapdisk)的特殊磁盘空间。交换磁盘的重要目的是保存程序的通过LRU算法换出的内存,这样可以使很多程序能够共享有限的RAM。一旦非活动程序的RAM页被写入交换磁盘(PageOut),操作系统可以使空出来的内存用于其他活动的程序。如果非活动程序稍后又继续执行,被pageout的RAM页又重新从交换磁盘中载入RAM(Pagein)。这种重新载入RAM页的动作就叫交换,而交换是非常消耗时间的,并且会降低相关程序的性能。
尽管交换磁盘确保并发的RAM使用量能大于实际的RAM总理,但是为了确保最佳性能,交换空间绝不要被活动程序所使用。这是因为从交换磁盘上读取pageout的RAM页要比直接从RAM中读取内存页要慢14000倍。磁盘访问都是以毫秒记的,而RAM访问是以10亿份之一秒记的。

2.1.1.Oracle的内存段类型

段(Segement)在OS上是对不同内存的使用目的和存放位置不同的区分。和一般的程序一样,Oracle使用以下几种段类型:
o程序文本(PragramText)
文本段包括了程序本身的可执行的机器代码(除动态链接库以外)。文本段一般标识为只读,因此它能被多个进程共享来跑同一个程序。
o初始化全局数据(InitializedGlobalData)
这一段包括了被编译器初始化的全局数据,比如用于跟踪数据的字符串。初始化数据能被修改,因此它不能被运行同一程序的多个进程共享。Oracle很少使用这个段。
o未初始化全局数据(UninitializedGlobalData)
未初始化全局数据一般称为BSS(BlockStartedbySymbol以符号开始的块)段。这一段包括了静态分配的全局数据,这些数据在进程运行时被进程初始化。Oracle也很少使用这个段。
o数据堆(DataHeap)
数据堆被用于进程在运行时,通过使用系统调用malloc()或sbrk()动态分配内存。Oracle将数据heap用于PGA。
o执行堆栈(ExecutionStack)
无论什么时候一个函数被调用,它的参数和返回上下文被push到一个执行堆栈中。返回上下文实际上是一组CPU注册值,这些注册值描述了进程在调用函数时那一刻的状态。当调用结束后,堆栈被POP而上下文被保留,以使执行能从函数调用时的结构状态立即执行下去。堆栈同时还保留了代码块的本地变量。堆栈大小依赖于函数嵌套或递归调用的深度、参数和本地变量所需的内存大小。
o共享库(SharedLibraries)
共享库是一个与位置无关的可执行代码集,这个集合实现了许多程序——特别是系统调用功能——所需要的功能。共享库段也是只读的,它被所有的进程(包括Oracle进程)共享。共享库无需保存一份在内存中。当调用了共享库中的一个函数后,进程需要打开共享库文件,然后通过系统调用mmap()将它映射到它的地址空间去。
使用共享库的另外一种方法是在程序文本段本身将需要的系统调用include进去。在那些不支持共享库的操作系统中或用上面方式有问题时就需要这样做。在大多数操作系统中,Oracle使用共享库作为来实现系统调用而不是实现Oracle代码本身。然而,Java类库都是编译好的,并且作为共享库动态链接的。
o共享内存段(SharedMemorySegment)
共享内存允许关联的进程共同读写内存中的同样数据。每个需要在共享内存段中寻址的进程都需要先将这段内存附到它自己的虚拟内存地址中去(一般通过shmat()系统调用实现)。OracleSGA就是使用的共享内存段。

2.1.2.Oracle的内存管理模块

Oracle中有两个内存管理模块。一个是内核服务内存管理模块(KernelServiceMemoryKSM);一个是内核通用堆管理模块(KernelGenericHeapKGH)
在X$表中,有两种用于这两个模块的表,它们就是以KSM和KGH开头的表。这两个模块相互非常紧密。内存管理模块是负责与操作系统进行接口以获取用于Oracle的内存,同时还负责静态内存的分配。这个模块中比较重要的X$表是X$ksmfs,它记录了固定sga、buffercache、logbuffer在内核服务内存中的分配。而堆管理模块则负责动态内存的管理。这也就是为什么SGA和PGA中堆又叫可变内存区了。Shared
Pool、Librarycache和PGA的堆都是由这个模块管理的。
一个堆(Heap)包括一个堆描述符和一个或多个内存扩展段(extent)。一个堆还可以包含子堆(Subheap)。这种情况下,堆描述符和子堆的扩展段可以被视为其父堆的大块(chunk).堆描述符的大小依赖于堆的类型和堆的空闲列表和LRU列表所包含的列表(Header)头的多少。一个扩展段又一个包含指向前一个和后一个扩展段指针(Pointer)的小的头部,扩展段的其他内存就是堆可用于动态分配的内存。
除了还包含一个保留列表的这一特性外,SharedPool中的子堆具有与SharedPool本身相同的结构。内存是以Chunk为单位分配的。空闲的chunk按照大小来组织在相应的空闲列表(FreeList)中。而未pin住的、可重建(unpinnedrecreatable)的chuck被维护在两个分别用于周期性chunk和短期chunk的LRU链表中。子堆还有一个包含少许空闲内存的主永久内存chunk。子堆也许还包含子堆,一共可以嵌套到四层。
子堆的概念非常重要,因为大多数被缓存在sharedpool中的对象实际上都被保存在子堆中,而不是保存在最上一层的堆中。在一个子堆中寻找空闲空间就相当于在sharedpool中寻找空闲空间。它们的不同处就在于子堆可以通过分配新的扩展段(extent)来增长,而sharedpool具有固定的扩展段数(10g引入SGA自动管理特性后,sharedpool的扩展段数也是可变的)。为子堆分配新的扩展段受到扩展段大小的限制,因此会存在这样的可能,因为没有任何一个父堆可以分配一个所需最小扩展段大小的chunk导致在子堆中查找一个小的chunk失败(抛4031错误)。
为了减少内存错误,在10g中,引入了多个隐含参数对扩展段进行控制,
_bct_buffer_allocation_min_extents-每次buffercache分配时的最小扩展段数(1)。
_compilation_call_heap_extent_size编译调用时分配堆的扩展段的大小(16384)10gR2引入。
_kgl_fixed_extentslibrarycache内存分配时是否使用固定的扩展段大小(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。
BufferCache、SharedPool、LargePool和JavaPool(在10g中,还包括StreamsPool、KEEPbuffercache、RECYCLEbuffercache、nKBufferCache、ASMBufferCache)的增长和收缩都是以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(包括redobuffer)一个granule
oBufferCache一个granule
oSharedPool一个granule
我们可以通过“ALTERSYSTEM”命令来修改分配给各个组件的granule数。当DBA想要给组件增加granule时,需要考虑实例中是否还有足够的granule来分配给新增加的组件大小(即增大后,各个组件之和小于SGA_MAX_SIZE)。有一点要注意,ALERTSYSTEM指定的是新的组件大小,是内存的大小,不是granule数。而Oracle在分配内存时,会以granule为单位,如果新分配的大小不是granule大小的倍数,则会使用最接近且大于分配数的granule的倍数值。例如,Unix中,当granule为16M,新分配组件大小为120M(不是16的倍数),Oracle则会实际分配128M(16×8);32位windows下,SGA小于1G时granule是4M,大于1G时granule是8M。
执行ALERTSYSTEM扩展组件大小后,前台进程(即执行ALTERSYSTEM命令)的进程会将SGA中的可用的granule(按照扩展新增大小计算)先保留下来。当这些granule被保留后,前台进程将后续处理交给后台进程。后台进程负责将这些保留的granule加到指定的组件的granule列表中去。这就完成了一次对SGA组件的扩展。
Granule的默认大小是根据以上规则规定的,但也可以通过隐含参数_ksm_granule_size来修改(强烈不建议修改)。另外,隐含参数_ksmg_granule_locking_status可以设置内存分配是否强制按照granule为单位进行分配。

2.1.4.SGA内存

当一个Oracle实例启动后,主要的SGA区的大小一开始基于初始化参数计算得出。这些大小可以通过showsga显示(实例启动后也会显示出这些信息)。但是,在共享内存段分配之前,每个区(Area)的大小都只有大概一个内存页大小。当需要时,这些区被分为一些子区(sub-area),因此没有一个子区会大于操作系统所限制的共享内存段(UNIX下受SHMMAX限制)的大小。对于可变区,有一个操作系统规定的最小子区大小,因此可变区的大小是最小子区大小的倍数。
如果可能,Oracle会为整个SGA分配一个单独的共享内存段。然而,如果SGA大于操作系统限制的单个共享内存段的大小时,Oracle会使用最佳的算法来将所有子区组织在多个共享段中,并且不超过SGA最大大小的限制。
严重的页入/页出会导致很严重的系统性能问题。然而,大内存消耗导致的间断的页入/页出是没有影响的。大多数系统都有大量的非活动内存被pageout而没有什么性能影响。但少量的页出也是有问题的,因为这会导致SGA中那些中度活性的页会经常pageout。大多数操作系统提供了一个让Oracle锁住SGA(设置lock_sga参数为TRUE)到物理内存中的机制以防止pageout。在某些操作系统中,oracle需要有特定的系统权限来使用这一特性。
当共享池分配了一个chunk,代码会返回给执行分配的函数一个注释。这些注释可以通过表X$KSMSP的字段KSMCHCOM查到。它们同时描述了这块内存分配的目的。如以下语句:
selectksmchcom,ksmchcls,ksmchsizfromx$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>selectstatistic#,name,value

2fromv$sysstat

3wherenamelike'%gamemory%';


STATISTIC#NAMEVALUE

------------------------------------------------------------------

20sessionugamemory8650004156

21sessionugamemorymax778811244

25sessionpgamemory50609488

26sessionpgamemorymax58007200

查询所有会话的堆大小可以用以下语句实现:
selectvalue,n.name||'('||s.statistic#||')',sid

fromv$sesstats,v$statnamen

wheres.statistic#=n.statistic#

andn.namelike'%gamemory%'

orderbyvalue;

但是,查询出来大的PGA或UGA并不一定说明有问题。它们的大小受到以下参数影响:
oSORT_AREA_SIZE
oSORT_AREA_RETAINED_SIZE
oHASH_AREA_SIZE
另外,过多的使用PL/SQL结构体(如PL/SQLTABLE、ARRAY)也会导致会话内存增大。

2.2.Oracle的内存的分配、回收
Oracle中的共享内存区的分配都是以chunk为最小单位的。Chunk不是一个固定值,它是一个扩展段(extent)中一块连续的内存。而Oracle的内存(其他存储,如磁盘也是)的增长是以扩展段为基础的。

2.2.1.空闲列表和LRU链表

空闲的chunk按照大小来组织在相应的空闲列表(FreeList)中。而未pin住的、可重建(unpinnedrecreatable)的chuck被维护在两个分别用于周期性chunk和短期chunk的LRU链表中。子堆还有一个包含少许空闲内存的主永久内存chunk。

2.2.2.空闲内存分配和回收

空闲内存都是由空闲列表(freelist)统一管理、分配的。每个空闲的chunk(大块)都会属于也只属于一个空闲列表。空闲列表上的chunk的大小范围是由bucket来划分的。Bucket直译为“桶”,在西方,往往用桶来盛装一定品质范围的物品,以便查找。比如,在采矿时,用不同的桶来装不同纯度的矿石,在桶上标明矿石的纯度范围,以便在提炼时可以采用不同工艺。在这里,我们也可以把bucket视为一种索引,使Oracle在查找空闲块时,先定位所需的空闲块在哪个bucket的范围内,然后在相应的空闲列表中查找。
一次内存的分配过程如下:当一个进程需要一个内存的大块(chunk)时,它会先扫描目标空闲列表(每个空闲列表对应有一个bucket,bucket是这个空闲列表的中chunk的大小范围)以查找最适合大小的chunk。如果找不到一个大小正好合适的chunk,则继续扫描空闲列表中更大的chunk。如果找到的可用chunk比所需的大小大24字节或者更多,则这个chunk就会被分裂,剩余的空闲chunk又被加到空闲列表的合适位置。如果空闲列表中没有一个chunk能满足要求的大小,则会从非空的相邻bucket的空闲列表中取最小的chunk。如果所有空闲列表都是空的,就需要扫描LRU链表释放最近最少使用的内存。当chunk空闲时,如果相邻的chunk也是空闲的,它们可能会结合(coalesce)起来。
当所有空闲列表都没有合适的空闲chunk可用时,就开始扫描LRU链表,将最近最少使用的chunk释放掉(如有空闲的相邻chunk,则结合),放到相应的空闲列表种去,直到找到合适的chunk或者达到最大查找数限制。如果在释放了LRU链表中最近最少使用的chunk后没没有找到合适空闲chunk,系统就抛4031错误。
如果找到了可用空闲chunk,就将它从空闲列表中移出,放到LRU链表中去。
下面介绍一下SharedPool的分配和回收。

2.2.3.SharedPool的分配和回收

在SharedPool中,空闲列表扫描、管理和chunk分配的操作都是受到sharedpoollatch保护的。显然,如果sharedpool含有大量的非常小的空闲chunk,则扫描空闲列表时间将很长,而sharedpoollatch则会要保持很久。这就是导致sharedpoollatch争用的主要原因了。有些DBA通过增加sharedpool来减少sharedpoollatch争用,这种方法是没有用的,可能反倒会加剧争用(作为短期解决方法,可以flush
sharedpool;而要真正解决问题,可以采取在实例启动时keep住那些可能会断断续续使用的对象【这种对象最容易导致sharedpool碎片】)。
只有执行了ALTERSYSTEMFLUSHSHARED_POOL才会使sharedpool的空闲chunk全结合起来。因此,即使sharedpool空闲内存之和足够大,也可能出现内存请求失败(空闲内存都是一些很小的碎片chunk)。
实际上,oracle实例启动时,会保留大概一半的SharedPool,当有内存压力时逐渐释放它们。Oracle通过这种方法限制碎片的产生。Oracle的少量空袭内存(sparefreememory)和X$表即其他持久内存结构一起,隐含在sharedpool的主要持久内存chunk中。这些内存不在sharedpool的空闲列表中,因此能够立即被分配。但是,却包含在视图V$SGASTAT的free
memory的统计数据中。
SQL>select*fromv$sgastat

2wherepool='sharedpool'

3andname='freememory';


POOLNAMEBYTES

------------------------------------------------

sharedpoolfreememory18334468


SQL>

而sparefreememory可以用以下方式查出:
selectavg(v.value)shared_pool_size,greatest(avg(s.ksmsslen)-sum(p.ksmchsiz),0)spare_free,to_char(100*greatest(avg(s.ksmsslen)-sum(p.ksmchsiz),0)/avg(v.value),'99999')||'%'wastagefromsys.x$ksmsss,sys.x$ksmspp,sys.v$parametervwheres.inst_id=userenv('Instance')andp.inst_id=userenv('Instance')andp.ksmchcom='freememory'ands.ksmssnam='freememory'andv.name='shared_pool_size';

注意:如果10g中用了SGA内存自动管理。以上语句可能无法查出。
当需要时,少量的空闲内存chunk会被释放到sharedpool中。除非所有这些少量空闲内存倍耗尽了,否则不会报4031错误。如果实例在负载高峰运行了一段时期之后还有大量的少量空闲内存,这就说明sharedpool太大了。
而未pin住的、可重建(unpinnedrecreatable)的chuck被维护在两个分别用于周期性chunk和短期chunk的LRU链表中。这两个LRU链表的长度可以通过表X$KGHLU查到,同时还能查到被flush的chunk数、由于pin和unpin而加到和从LRU链表中移出的chunk数。X$KGHLU还能显示LRU链表没有被成功flush的次数,以及最近一次这样的请求失败的请求大小。
SQL>column
kghlurcrheading"RECURRENT|CHUNKS"

SQL>columnkghlutrnheading"TRANSIENT|CHUNKS"

SQL>columnkghlufshheading"FLUSHED|CHUNKS"

SQL>columnkghluopsheading"PINSAND|RELEASES"

SQL>columnkghlunfuheading"ORA-4031|ERRORS"

SQL>columnkghlunfsheading"LASTERROR|SIZE"

SQL>select

2kghlurcr"RECURRENT_CHUNKS",

3kghlutrn"TRANSIENT_CHUNKS",

4kghlufsh"FLUSHED_CHUNKS",

5kghluops"PINSAND_RELEASES",

6kghlunfu"ORA-4031_ERRORS",

7kghlunfs"LASTERROR_SIZE"

8from

9sys.x$kghlu

10where

11inst_id=userenv('Instance');


RECURRENT_CHUNKSTRANSIENT_CHUNKSFLUSHED_CHUNKSPINSAND_RELEASESORA-4031_ERRORSLASTERROR_SIZE

---------------------------------------------------------------------------------------------

3273680796500

865963296010213800

1473565796205461540

000000


如果短期链表的长度大于周期链表的长度3倍以上,说明SharedPool太大,如果chunkflash对LRU操作的比例大于1/20,则说明SharedPool可能太小。

2.3.Oracle在UNIX下的内存管理

在UNIX下,Oracle是以多进程的方式运行的。除了几个后台进程外,Oracle为每个连接起一个进程(进程名称中,LOCAL=NO)。而UNIX下的内存分为两类,一种叫共享内存,即可以被多个进程共享;还有一种就是私有进程,只能被所分配的进程访问。更加Oracle内存区的不同特性,SGA内存可以被所有会话进程共享,因此是从共享内存中分配的;而PGA是进程私有的,因而是从私有内存中分配的。

2.3.1.共享内存和信号量

在Unix下,Oracle的SGA是保存在共享内存中的(因为共享内存是可以被多个进程共享的,而SGA正是需要能被所有Oracle进程所共享)。因此,系统中必须要有足够的共享内存用于分配SGA。而信号量则是在共享内存被多个进程共享时,防止发生内存冲突的一种锁机制。
UNIX下,对于内存的管理配置,是由许多内核参数控制,在安装使用Oracle时,这些参数一定要配置正确,否则可能导致严重的性能问题,甚至Oracle无法安装、启动。涉及共享内存段和信号量的参数:

参数名称

建议大小(各个平台的Oracle建议值可以去metalink上找)

参数描述

SHMMAX

可以得到的物理内存

(0.5*物理内存)

定义了单个共享内存段能分配的最大数.

SHMMAX必须足够大,以在一个共享内存段中能足够分配Oracle的SGA空间.这个参数设置过低会导致创建多个共享内存段,这会导致Oracle性能降低.

SHMMIN

定义了单个共享内存段能分配的最小数.

SHMMNI

512

定义了整个系统共享内存段的最大数。

NPROC

4096

系统的进程总数

SHMSEG

32

定义了一个进程能获取的共享内存段的最大数.

SEMMNS

(NPROC*2)*2

设置系统中的信号数.

SEMMNS的默认值是128

SEMMNI

(NPROC*2)

定义了系统中信号集的最大数

SEMMSL

与Oracle中参数processes大小相同

一个信号集中的信号最大数

SEMMAP

((NPROC*2)+2)

定义了信号映射入口最大数

SEMMNU

(NPROC-4)

定义了信号回滚结构数

SEMVMX

32768

定义了信号的最大值

信号量(Semaphore):对于每个相关的process都給予一个信号來表示其目前的狀态。主要的目地在于确保process间能同步,避免process存取shareddata时产生碰撞(collisions)的情況。可以把信号量视为操作系统级的用于管理共享内存的钥匙,每个进程需要有一把,一个信号量集就是一组钥匙,这组钥匙可以打开共同一个共享内存段上的所。当一个进程需要从共享内存段中获取共享数据时,使用它自己的钥匙打开锁,进入后,再反锁以防止其他进程进来。使用完毕后,将锁释放,这样其他进程就可以存取共享数据了。
共享内存(SharedMemory)是指同一块记内存段被一个以上的进程所共享。这是我们所知速度最快的进程间通讯方式。使用共享内存在使用多CPU的机器上,会使机器发挥较佳的效能。
可以通过以下命令检查你当前的共享内存和信号量的设置:
$sysdef|more

当Oracle异常中止,如果怀疑共享内存没有被释放,可以用以下命令查看:
$ipcs-mop

IPCstatusfrom/dev/kmemasofThuJul614:41:432006

TIDKEYMODEOWNERGROUPNATTCHCPIDLPID

SharedMemory:

m00x411c29d6--rw-rw-rw-rootroot0899899

m10x4e0c0002--rw-rw-rw-rootroot2899901

m20x4120007a--rw-rw-rw-rootroot2899901

m4587550x0c6629c9--rw-r-----rootsys2911317065

m40x06347849--rw-rw-rw-rootroot116619150

m655410xffffffff--rw-r--r--rootroot016591659

m5242940x5e100011--rw-------rootroot118111811

m8519750x5fe48aa4--rw-r-----oracleoinstall66201725076

然后它ID号清除共享内存段:
$ipcrm–m851975

对于信号量,可以用以下命令查看:
$ipcs-sop

IPCstatusfrom/dev/kmemasofThuJul614:44:162006

TIDKEYMODEOWNERGROUP

Semaphores:

s00x4f1c0139--ra-------rootroot

......

s140x6c200ad8--ra-ra-ra-rootroot

s150x6d200ad8--ra-ra-ra-rootroot

s160x6f200ad8--ra-ra-ra-rootroot

s170xffffffff--ra-r--r--rootroot

s180x410c05c7--ra-ra-ra-rootroot

s190x00446f6e--ra-r--r--rootroot

s200x00446f6d--ra-r--r--rootroot

s210x00000001--ra-ra-ra-rootroot

s450780x67e72b58--ra-r-----oracleoinstall

当Oracle异常中止,可以根据信号量ID,用以下命令清除信号量:
$ipcrm-s45078

一个共享内存段可以可以分别由以下两个属性来定位:
oKey:一个32位的整数
o共享内存ID:系统分配的一个内存
$ipcs-mop

IPCstatusfrom/dev/kmemasofThuJul610:32:122006

TIDKEYMODEOWNERGROUPNATTCHCPIDLPID

SharedMemory:

m00x411c29d6--rw-rw-rw-rootroot0899899

m10x4e0c0002--rw-rw-rw-rootroot2899901

m20x4120007a--rw-rw-rw-rootroot2899901

m4587550x0c6629c9--rw-r-----rootsys2911317065

m40x06347849--rw-rw-rw-rootroot116619150

m655410xffffffff--rw-r--r--rootroot016591659

m5242940x5e100011--rw-------rootroot118111811

m8519750x5fe48aa4--rw-r-----oracleoinstall66201725076

2.3.2.私有内存

对于PGA,由于是分配的私有内存,不存在争用问题,因而OS也没有相应的信号量来控制(同理,PGA中也没有latch)。在10g之前,PGA的私有内存是通过函数malloc()和sbrk()进行分配扩展的,10g之后,私有内存是通过函mmap()进行初始化的。
另外,还有一点要注意的是,和SGA不同,PGA内存的大小不是固定的,是可以扩展的(前者的大小是固定的,无法增长的)。因而进程的私有内存是会增长的。因此,一个规划好的系统发生内存不足的情况通常是由于进程的私有内存或进程数量突然增长造成的(PGA_AGGREGATE_TARGET参数能尽量控制但不保证PGA内存总量被控制在一定数值范围内)。
隐含参数_pga_large_extent_size(1048576)和_uga_cga_large_extent_size(262144)就控制了当初始化时,PGA、UGA和CGA扩张段的最大大小。

2.3.3.SWAP的保留区

swap(交换)区,是UNIX中用来分配虚拟内存的一块特殊的磁盘分区。UNIX启动每一个进程,都需要在swap区预留一块和内存一样大小的区域,以防内存不够时作数据交换。当预留的swap区用完时,系统就不能再启动新的进程。比如,系统物理内存是4G,而设置的交换区只有1G,那么可以计算得出大概3G的内存会浪费(BufferCache除外,可能有2G浪费)。

在HP-UX中,参数swapmen_on可以让系统创建一个pseudo-swap(伪交换区),大小为系统物理内存的3/4,但是这个伪交换区并不占用任何内存和硬盘资源。只是说,让系统认为,交换区的大小是1+4*3/4=4G,而不是1G,就是说可以启动更多的进程,避免内存的浪费。
一般系统物理内存不大的时候,设置交换区是物理内存的2-4倍,swapmen_on设置为1或0都没什么影响,但是当系统内存很大如8G时,因为swap一般不设为16G-32G,这时开启swapmen_on就很必要了。
hp建议,即使设置了swapmen_on,也将你的swap为物理内存的1-1.5倍。
swap大小设置不当,也同样会造成系统的性能问题。因为,swap中首先会为各个进程留出一个保留区,这部分区去掉后,swap的可用大小就比较小了(这就是为什么用swapinfo可能看到TotalPCTUSED为100%而devPCTUSED为0%)。当swap可用区不足,而由内存需要被pageout到swap区中,就需要先将swap区中一些页被pagein到物理内存中去,因而导致发生交换,产生性能问题。
swap的使用情况可以通过swapinfo查看:
>swapinfo-mt

MbMbMbPCTSTART/Mb

TYPEAVAILUSEDFREEUSEDLIMITRESERVEPRINAME

dev4096040960%0-1/dev/vg00/lvol2

dev8000080000%0-1/dev/vg00/swap2

reserve-12026-12026

memory2046813387708165%

total3256425413715178%-0-

2.4.Oracle在windows下的内存管理

2.4.1.Windows内存系统概述

WindowsNT使用一个以页为基础的虚拟内存系统,该系统使用32位线性地址。在内部,系统管理被称为页的4096字节段中的所有内存。每页的物理内存都被备份。对于临时的内存页使用页文件(pagefile),而对于只读的内存页,则使用磁盘文件。在同一时刻,最多可以有16个不同的页文件。代码、资源和其它只读数据都是通过它们创建的文件直接备份。
WindowsNT为系统中的每一个应用程序(进程)提供一个独立的、2GB的用户地址空间。对于应用程序来说,好象是有2GB的可用内存,而不用考虑实际可用的物理内存的量。如果某个应用程序要求的内存比可用的内存更多时,WindowsNT是这样满足这种要求的,它从这个和/或其他的进程把非关键内存分页(paging)到一个页文件,并且释放这些物理内存页。结果,在WindowsNT中,全局堆不再存在。相反,每一个进程都有其自己的32位地址空间,在其中,该进程的所有内存被分配,
包括代码、资源、数据、DLL(动态链接库),和动态内存。实际上,系统仍然要受到可用的硬件资源的限制,但是实现了与系统中应用程序无关的、对于可用资源的管理。
WindowsNT在内存和地址空间之间作出了区分。每个进程分配到2GB的用户地址空间,而不管对于该进程的实际可用物理内存有多少。而且,所有进程都使用相同范围的线性32位地址,范围从0000000016-7FFFFFFF16,而不考虑可用内存的地址。WindowsNT负责在适当的时间把内存页映射(paging)到磁盘以及从磁盘页映射回内存,使得每个进程都确保能够寻址到它所需要的内存。尽管有可能出现两个进程试图同时访问同一虚拟地址上的内存,但是,实际上Windows
NT虚拟内存管理程序是在不同的物理位置描述这两个内存的位置。而且这两个地址都不见得与原始的虚拟地址一致。这就是虚拟内存。
Win32环境下,32位的地址空间转化为4GB的虚拟内存。默认情况下,将一半(2GB)分配给用户进程(因而一个进程的最大可用虚拟内存为2G,oracle进程同样受此限制),另一半(2GB)分配给操作系统。
因为虚拟内存的存在,一个应用程序能够管理它自己的地址空间,而不必考虑在系统中对于其它进程的影响。在WindowsNT中的内存管理程序负责查看在任何给定的时间里,所有的应用程序是否有足够的物理内存进行有效的操作。WindowsNT操作系统下的应用程序不必考虑和其它应用程序共享系统内存这个问题。并且,即使在应用程序自己的地址空间内,它们仍能够与其它的应用程序共享内存。
区分内存和地址空间的一个好处是,为应用程序提供了将非常大的文件加载到内存的能力。不必将一个大的文件读进内存中,WindowsNT为应用程序保留该文件所需的地址范围提供了支持。然后,在需要的时候,该文件部分就可以被浏览了(物理性地读进内存)。通过虚拟内存的支持,对于大段的动态内存的分配同样可以做到这一点。
在任意给定的时间,进程中每个地址都可以被当作是空闲的(free)、保留的(reserved)或已提交的(committed)。进程开始时,所有地址的都是空闲的,意味着它们都是自由空间并且可以被提交到内存,或者为将来使用而保留起来。在任何空闲的地址能够被使用前,它必须首先被分配为保留的或已提交的。试图访问一个保留的或已提交的地址都将产生一个访问冲突异常(accessviolationexception)。
一个进程中的所有2GB的地址要么为了使用而是空闲的、要么为了将来的使用而是保留的、要么已提交到特定的内存(在使用的)。
一旦地址被以保留的或者已提交的形式分配,VirtualFree是唯一可以释放它们的方法棗那就是,将它们返回到自由的地址。VirtualFree还可以用来对已提交的页解除提交,同时,返回这些地址到保留状态。当解除地址的提交时,所有与该地址相关的物理内存和页文件空间都被释放。
在WindowsNT中的进程有一个被称为工作组(workingset)的最小页,是为了进程能够顺利地运行,在运行时在内存中必须被提供。WindowsNT在启动时为一个进程分配了默认数量的页数,并且逐渐地调整该数,使得系统中所有激活的进程的性能达到一种平衡的最优。当一个进程正在运行时(实际上是,是一个进程的线程正在运行时),WindowsNT“尽量”确保该进程的工作组页总是驻留在物理内存中。工作集即在物理内存中保持的虚拟页面的子集,分进程工作集和系统工作集。

2.4.2.Windows下Oracle的内存配置

在windows下,Oracle实例作为一个单独的进程运行,这个进程是一个标准的Win32应用程序,它能够申请最大为2G的虚拟内存地址空间,所有用户连接后后台线程的分配内存(包括像buffercache那些全局内存)必须小于2G(64位平台中无此限制,32位平台中可以通过设置参数use_indirect_data_buffers来突破这一限制,不详述)。
Oracle可以运行于windowsNT下的任何版本,但是Oracle一般不推荐将Oracle数据库运行在PDC(主域控服务器)或BDC(备域控服务器)下。这是因为域控服务器会需要大量的文件缓存(这会消耗大量内存,影响到Oracle)和网络资源。
而文件缓存带来的另外一个问题就是,这个机器到底是作为一个专用的数据库服务器还是一个混合服务器。因为Oracle数据库不会用到文件缓存(logbuffer就相当于Oracle自己的文件缓存),它通过直接写磁盘来避免文件缓冲。
在专用数据库服务器系统上,用户一定要确保没有使用到页文件(pagefile即虚拟内存文件)。否则,可以通过修改Oracle参数或者增大物理内存来避免。如果大量额页被持续的移入、移出到虚拟内存中,会严重影响到性能。
如果是专用服务器系统,有以下建议:
o如果分配给Oracle的总内存能保证不会超过物理内存,则虚拟内存页可以被设置位物理内存的50%,并且可以增长到物理内存的100%大小(在mycomputer=>properties=>Advanced=>Performance=>Settings=>Advanced=>VirtualMemory=>Change中设置,设置Initialsize为物理内存的50%,Maximum
Size和物理内存大小相同);
o对于主要是运行纯Oracle数据库的系统(但不是专用),一般推荐虚拟内存页大小在1倍到1.5倍于物理内存大小之间;
o对于物理内存大于2G的机器,要求虚拟内存页最少为2G。
一个机器上能被用于分配的总内存等于物理内存加上扩展前的虚拟内存页大小。你一定要避免设置参数如buffer_cache_size或其他相关参数导致Oracle要求分配内存大于物理内存。尽管Oracle的分配内存大小是限制在总内存(物理内存+最小虚拟内存)之内,但是,对虚拟内存页的访问是非常慢的,会直接影响到系统性能,因此Oracle分配内存要小于物理内存大小以避免发生内存交换。
如果系统是混合应用,除了Oracle数据库外还运行了其他程序,这时就需要考虑设置虚拟内存页大于物理内存了。那些当前不活动的进程可以减少它们的工作区(workingset即物理内存)以使活动进程能增加工作区。如果Oracle数据库运行在这样的机器上,则推荐虚拟内页最少1.5到2倍的物理内存大小,特别是在内存大于2G时。
在Oracle8.1.x之前,启动Oracle服务时不会启动Oracle实例。此时(即只启动了Oracle服务,没有启动实例),分配给Oracle.EXE的主要内存是给相关DLL的,大概20M(各个版本的DLL不同,因此占用内存情况也不同)。9i之后,Oracle实例会随着服务启动,不过我们可以在服务启动后再关闭实例,这时就可以观察出Oracle服务所占用的内存大小了:
这是windows下一个Oracle10g的实例关闭后内存占用情况。我们看到此时Oracle服务占用的内存是32M。
Windows下,可以使用以下语句来计算Oracle占用的虚拟内存大小:
selectsum(bytes)/1024/1024+22/*DLL占用内存*/Mbfrom(selectbytesfromv$sgastat–SGA内存unionselectvaluebytesfrom–会话内存v$sesstats,v$statnamenwheren.STATISTIC#=s.STATISTIC#andn.name='sessionpgamemory'unionselect1024*1024*count(*)bytes–线程堆栈fromv$process);

在实例启动时,所有全局内存页都被保留和提交(所有共享全局区、BufferCache和RedoBuffer)——可以通过TopShow观察到实例启动后,所需要的Page
File都已经被分配。但只有一小部分内存页(如果没有设置PRE_PAGE_SGA的话;这小部分内存以granule为单位,固定SGA【包括redobuffer】一个、BufferCache一个、SharedPool一个)被触及(touch)而已经分配到工作组(workingset)中,而其他更多的页需要使用时才分配到工作组中。
通过设置注册表可以设置Oracle进程的最小工作组大小和最大工作组大小:
oORA_WORKINGSETMIN或ORA_%SID%_WORKINGSETMIN:Oracle.EXE进程的最小工作组大小(M为单位)
oORA_WORKINGSETMAX或ORA_%SID%_WORKINGSETMAX:Oracle.EXE进程的最大工作组大小(M为单位)
这些注册项需要加在HKEY_LOCAL_MACHINE->SOFTWARE->ORACLE或者HKEY_LOCAL_MACHINE->SOFTWARE->ORACLE->HOMEn下。
在混合服务器下,这种设置能防止Oracle进程的内存被其他进程争用。设置这些注册项时,需要考虑PRE_PAGE_SGA的设置。如前所述,PRE_PAGE_SGA使Oracle实例启动时“触及”所有的SGA内存页,使它们都置入工作组中,但同时会增长实例启动时间。
ORA_WORKINGSETMIN是一个非常有用的参数,它能防止Oracle进程的工作组被缩小到这一限制值之下:
o如果设置了PRE_PAGE_SGA,实例启动后,工作组就大于这个限制值。在实例关闭之前,就不会低于这个值;
o如果没有PRE_PAGE_SGA,当实例的工作组一旦达到这个值后,就不会再低于这个值。
另外,在10g之前,存在一个Bug(642267),导致在windows系统中设置了LOCK_SGA后,实例启动报ORA-27102错误。可以通过设置ORA_WORKINGSETMIN最小为2M来解决这个问题。但是,windows中,LOCK_SGA只影响Oracle进程中的SGA部分,而分配给用户会话的内存不会受影响,这部分内存还是可能会产生内存交换。

2.4.3.SGA的分配

当实例启动时,oracle在虚拟内存地址空间中创建一段连续的内存区,这个内存区的大小与SGA所有区相关参数有关。通过调用Win32API接口“VirtualAlloc”,在接口函数的参数中指定MEM_RESERVE|MEM_COMMIT内存分配标识和PAGE_READWRITE保护标识,这部分内存始终会被保留和提交。这就保证所有线程都能访问这块内存(SGA是被所有线程共享的),并且这块内存区始终有物理存储(内存或磁盘)所支持。
VirtualAlloc函数不会触及这块区域内的内存页,这就是说分配给SGA组件(如buffercache)的内存在没有触及之前是不会到工作组中去的。
VirtualAlloc
VirtualAlloc函数保留或提交调用此函数的进程的虚拟内存地址空间中的一段内存页。如果没有指定MEM_RESET,被这个函数分配的内存自动初始化为0,
BufferCache通常被创建为一个单一的连续内存区。但这并不是必须的,特别是当使用了非常大的BufferCache时。因为dll内存和线程分配的内存会导致虚拟地址空间产生碎片。
当一个进程创建后,windowsNT会在进程的地址空间中创建一个堆(heap),这个堆被称为进程的默认堆。许多Win32API调用接口和C运行调用接口(如malloc、localalloc)都会使用这个默认堆。当需要时,进程能在虚拟内存地址空间中创建另外的命名堆。默认堆创建为1M大小(被保留和提交的)的内存区,当执行分配或释放这个堆的操作时,堆管理器提交或撤销这个区。而访问这个区是通过临界区来串行访问的,所以多个线程不能同时访问这个区。
当进程创建了一个线程后,windowsNT会为线程堆栈(每个现场都有自己的堆栈)保留一块地址空间区域,并提交一部分这些保留区。当一个进程连接到标准区时,系统为堆栈保留一个1M大小的虚拟地址空间并提交这个区顶部的两个页。当一个线程分配了一个静态或者全局变量时,多个线程可以同时访问这个变量,因此存在变量内容被破坏的潜在可能。本地和自动变量被创建在线程的堆栈中,因而变量被破坏的可能性很小。堆栈的分配是从上至下的。例如,一个地址空间从0x08000000到0x080FF000的堆栈的分配是从0x080FF000到0x08001000来提交内存页,如果访问在0x08001000的页就会导致堆栈溢出异常。堆栈不能增长,任何试图访问堆栈以外的地址的操作都可能会导致进程被中止的致命错误。

2.4.4.会话内存的分配

当监听创建了一个用户会话(线程和堆栈)时,Oracle服务进程就通过调用Win32API函数创建用户连接所必须的内存结构,并且指定MEM_RESERVE|MEM_COMMIT标识,以保持和提交给线程私有的地址空间区域。
当调用了VirtualAlloc来在指定地址保留内存,它会返回一个虚拟地址空间到下一个64K大块(chunk,windows内存分配最小单位)的地址。Oracle调用VirtualAlloc时不指定地址,只传一个NULL在相应参数中,这会返回到下一个64K大块的地址。因此用户会话在分配PGA、UGA和CGA时同时也遵循64K的最小粒度,来提交倍数于这个粒度值的内存页。许多用户会话经常分配许多小于64K的内存,这就导致地址空间出现碎片,因为许多64K区只有两个页被提交。
一旦地址空间被用户会话占满了后,如果要再创建一个新会话,就会存在无法分配到地址空间的危险。将可能报以下错误:
ORA-12500/TNS-12500
TNS:listenerfailedtostartadedicatedserverprocess
也可能报这些错误:
oORA-12540/TNS-12540TNS:internallimitrestrictionexceeded
oNT-8Notenoughstorageisavailabletoprocessthiscommand
oskgpspawnfailed:category=....
oORA-27142couldnotcreatenewprocess
oORA-27143OSsystemcallfailure
oORA-4030outofprocessmemorywhentryingtoallocate....
因为地址空间碎片问题和DLL被载入了oracle服务进程的地址空间,这些错误很可能发生再当Oracle进程占用大概1.6G~1.7G(可以通过任务管理器或TopShow查看)时。

2.4.4.1.会话内存大小设置

我们前面说了,一个进程的全部内存大小被限制在2G以内。因此,对于一个有许多用户同时访问的系统,要考虑这些会话总内存小于2G–SGA的大小。
下列参数会影响每个会话的内存大小(这些参数前面都有介绍):
obitmap_merge_area_size
ocreate_bitmap_area_size
ohash_area_size
oopen_cursors
osort_area_size(sort_area_retained_size)
在没有设置PGA_AGGREGATE_TARGE(这个参数能尽量但不一定使所有会话PGA之和在指定范围内)参数时,需要调整这些参数,以使所有会话占用内存与SGA之和小于2G。过多的使用PL/SQL结构体(如PL/SQLTABLE、ARRAY)也会导致会话内存增大。

2.4.4.2.ORASTACK修改线程堆栈大小

Oracle提供了ORASTACK工具让用户内修改Oracle执行程序创建会话、线程时的默认堆栈大小。当ORASTACK应用于一个可执行程序时,它会修改程序头部的、定义使用创建线程API函数所指定默认堆栈大小的二进制区,以修改默认堆栈的大小。没有必要区修改线程提交页数的默认值,因为它们时从堆栈中请求到的。当用户非常多时,通过减少每个创建在Oracle中会话堆栈大小,可以节省大量内存。比如,一个1000用户的系统,将堆栈从1M降为500K后,能节省出1000
*500K=500M的地址空间。
在需要使用ORASTACK来降低现场堆栈大小时,你需要测试你的系统以保证新的堆栈大小能确保系统正常运行。如果堆栈大小被缩小到Oracle服务端所必须的堆栈大小以下,就会产生堆栈溢出错误,用户进程就失败(通常报ORA-3113错误),并且在alertlog中不会有报错而且页不产生trace文件。Oracle一般不推荐将堆栈该到500K以下(尽管不少系统在300K时也能正常运行)。
ORASTACK必须修改所有能在oracle中创建线程的进程,使用语法如下:
orastackexecutable_namenew_stack_size_in_bytes

下面的例子将堆栈改为500K:
orastackoracle.exe500000

orastacktnslsnr.exe500000

orastacksvrmgrl.exe500000

orastacksqlplus.exe500000

在使用ORASTACK之前必须保证没有任何oracle进程正在运行(可以通任务管理器或者TopShow)。
此外,如果有程序在本地连接(没有通过SQL*NET)了Oracle,也要先停止(如在本地运行sqlplus连接了实例)。

2.4.4.3.会话内存如何释放、线程如何结束

当会话成功结束后,它会按用Win32API函数VirtualFree来释放它的内存,调用此函数时,需要指定MEM_DECOMMIT|MEM_RELEASE标识。当所有内存被释放后,堆栈也被释放,将Oracle进程中指向完成的会话的地址空间空闲出来。
如果一个用户会话被异常结束,它将不会释放它所分配的内存,这些内存页会被继续保留在Oracle进程的地址空间中,知道进程结束。会话的异常结束可能由以下原因导致的:
oShutdownabort.
oAltersessionkillsession.
oorakill杀掉的会话.
oOracle管理助手forWindows:killsession.
o其他杀线程的工具(TopShow工具提供了杀线程的功能)
Oracle建议尽量少使用以上方式(命令),特别是shutdownabort(这种停止实例的方法所带来的问题还不止这个)。当调用shutdownabort时,Oracle会调用Win32API函数TerminateThread来中止每个用户会话,这个命令将直接杀掉线程而不释放它们的内存。如果系统已经接近2G地址空间的限制,Oracle实例再次启动要分配内存时就会产生问题。唯一释放Oracle全部内存的方法是停止和启动Oracle服务(服务OracleService<SID>)。
如果windowsNT下的系统需要能被许多用户访问,可以通过以下措施来优化内存的使用:
o降低相关的PGA、UGA内存参数(如SORT_AREA_SIZE);
o降低SGA的参数;
o使数据库作业队列数(参数job_queue_processes控制)和并行查询slave(参数parallel_max_servers控制)最小,因为它们也会导致Oracle进程创建线程;
o使用ORASTACK降低会话、线程堆栈大小到500K;
o考虑使用MTS模式;
o考虑将WindowsNT升级到WindowsNT企业版;
o考虑升级硬件以支持IntelESMA(Extended.ServerMemoryArchitecture,扩展服务内存架构)

3.内存错误处理

Oracle中最常见的内存错误就是4030和4031错误。这两个错误分别是在分配PGA和SGA时,没有足够内存分配导致的。经过我们以上对Oracle内存的了解以及对内存管理机制的浅析,可以总结在这两种错误发生时,我们该如何分析和处理。

3.1.分析、定位ORA-4030

4030错误是由于oracle进程在内存扩展时,无法从OS获取到所需的内存而产生的报错。在专有服务模式下,Oracle进程的内存包括堆栈、PGA、UGA(从PGA中分配)和有些进程信息;而MTS下,UGA是从SGA中分配,不包括在进程的内存范围内。

3.1.1.4030错误产生的原因

PGA的大小不是固定的,是可以扩展的。PGA通过系统调用扩展堆数据段时,操作系统分配新的虚拟内存给进程作为PGA扩展段。这些扩展段一般是几个KB。只要需要,oracle会分配几千个扩展段。然而,操作系统限制了一个进程的堆数据段的增长。在UNIX中,这个限制一般受到OS内核参数MAXDSIZ限制,这是限制单个进程的。还有一个堆所有进程的虚拟内存的总的大小的限制。这个限制和swap交换空间(虚拟内存)大小有关。如果在扩展PGA内存时达到这些限制,就会抛4030错误。

3.1.2.4030错误分析

既然知道了4030错误产生的可能原因,我们在分析4030错误时,就可以从这几个方面分别收集信息进行分析,并结合Oracle进程内存的使用情况来解决问题。

3.1.2.1.操作系统是否由足够的内存

在不同的操作系统下,我们可以使用相应的工具来收集系统的内存使用情况,以判断内存是否足够:
·OpenVMSsystems
可以使用showmemory查看物理内存和虚拟内存页的使用情况
PhysicalMemoryUsage(pages):TotalFreeInUseModifiedMainMemory(256.00Mb)32768248497500419
.....
PagingFileUsage(blocks):FreeReservableTotal

DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]SWAPFILE.SYS307203072039936DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]PAGEFILE.SYS226160201088249984DISK$BOBBIE_USER3:[SYS0.PAGEFILE]PAGEFILE.SYS462224405296499968

一般情况下,空闲pagefile的之和不能小于它的总数之和的一半。而且SWAPFILE必须是始终没有被使用的,它的空闲页必须和总页数相同。
·Windows
Windows下可以通过任务管理器的性能页来查看内存的使用情况,也可以使用TopShow来观察系统进程的内存使用情况
·UNIX
不同厂商的UNIX下,各种工具的使用和统计结果可能有所不同。常用的对内存的查看工具主要有:
oTOP——查看物理内存和交换空间
ovmstat——查看物理内存和交换空间状况,以及观察是否有pageout/pagein
oswapon–s——查看交换空间情况
oswapinfo–mt——查看交换空间使用情况
下面是swapinfo的一个输出:
>swapinfo-mt

MbMbMbPCTSTART/Mb

TYPEAVAILUSEDFREEUSEDLIMITRESERVEPRINAME

dev4096040960%0-1/dev/vg00/lvol2

dev8000080000%0-1/dev/vg00/swap2

reserve-12026-12026

memory2046813387708165%

total3256425413715178%-0-

此外,在一些操作系统中,还可以通过Oracle自己提供的工具maxmem来检查一个进程能够分配的最大堆数据段的大小。
>maxmem

Memorystartsat:6917529027641212928(6000000000020000)

Memoryendsat:6917529031936049152(6000000100000000)

Memoryavailable:4294836224(fffe0000)

3.1.2.2.是否受到系统限制

在操作系统,往往会有对单个进程或者所有进程能够分配的内存大小做了限制。当Oracle分配进程内存时,如果达到这些限制,也会导致4030错误。在不同操作系统中,可以用不同方式检查系统是否有做限制。
·OpenVMSsystems:
showprocess/id=<processid>/quota可以显示一个进程的可用配额是多少。
·Windows
如前所述,在window32位系统中,进程的可用内存限制为2G(可以通过其他方式突破此限制)。而windows下,oracle是以一个单独进程方式运行的,它的内存包括了堆栈、SGA、PGA。我们可以通过任务管理器或TopShow来检查Oracle进程是否达到此限制。
·UNIX
可以使用命令ulimit来查看unix下的限制:
>ulimit-a

time(seconds)unlimited

file(blocks)unlimited

data(kbytes)1048576

stack(kbytes)131072

memory(kbytes)unlimited

coredump(blocks)4194303

3.1.2.3.哪个Oracle进程请求了过多的内存

有些进程会做某些操作时会需要分配大量内存,如使用了PL/SQLTABLE或者做排序时。如果这样的进程在系统中运行一段时间后,就可能导致4030错误的产生。我们可以用以下语句来查看各个进程的内存使用情况:
selectsid,name,valuefromv$statnamen,v$sesstatswheren.STATISTIC#=s.STATISTIC#andnamelike'%ga%'orderby3asc;

同时,我们还可以从操作系统的角度来确认这些大量消耗内存的进程。
·OpenVMSsystems:
showprocess/continious可以查看各个进程的物理和虚拟内存的使用情况。
·Windows
在windows中,由于Oracle是以一个单独进程运行的,而由线程来服务于会话的。因此无法查看单个会话的内存占用情况。
·UNIX
UNIX中,可以通过ps–lef|grepora来查看oracle进程占用的内存情况。

3.1.2.4.收集进程正在进行的操作

在解决4030问题时,有一点很重要,抛出4030错误的进程并不一定是导致内存不足的进程。只不过在它请求分配内存时,内存已经不足了。很有可能在此之前就已经有大量消耗内存的进程导致内存不足。你需要找出内存消耗不断增长的进程,观察它锁进行的操作。这条语句可以查出会话进程正在执行的语句:
selectsql_text

fromv$sqlareaa,v$sessions

wherea.address=s.sql_addressands.sid=<SID>;

另外,可以做一个heapdump,将结果发给Oracle进行分析,
SQL>oradebugunlimitSQL>oradebugsetorapid<PID>(通过v$process查到的pid,用setospid来设置OS中的PID【或者v$process中的spid】)SQL>oradebugdumpheapdump7(1-PGA;2-SharedPool;4-UGA;8-CGA;16-topCGA;32-largepool)

SQL>altersessionsetevents'4030tracenameheapdumplevel25';

3.1.3.解决4030错误的建议

如果问题是由于swap空间不足造成的,并且由中度或者严重的pagein/pageout(可以用vmstat查看),你就需要尝试降低系统整体的虚拟内存的使用(如调整SGA大小),或者降低单个进程内存的使用(如调整sort_area_size),或者减少进程数量(如限制processes参数,使用MTS)。而如果pagein/pageout很少或者根本没有,就可以考虑增大swap空间。某些系统中,可以考虑使用伪交换区(如hp-ux中,可以考虑设置swapmen_on)。
如果问题和PLSQL操作有关,可以,1、检查PLSQL中的TABLE,看看其中的数据是否全都必要,是否可以减少数据放入TABLE中;2、优化相关语句(比如通过调整优化器策略,使查询计划走sort比较少的访问路径),减少sort操作,或者减少sort_area_size(代价就是一部分sort操作会放在磁盘上进行,降低性能)。
9i以后可以考虑设置PGA内存自动管理。即设置PGA_AGGREGATE_TARGET在一定数值范围内,WORKAREA_SIZE_POLICY设置为AUTO。但是注意,9i在OpenVMS系统上、或者在MTS模式下不支持PGA内存自动关联。
如果是因为进程数过多导致的内存大量消耗,首先可以考虑调整客户端,减少不必要的会话连接,或者采用连接池等方式,以保持系统有稳定的连接数。如果会话非常多,且无法降低的话,可以考虑采用MTS,以减少Oracle进程数。
检查SGA中的内存区是否分配过多(如sharedpool、largepool、javapool)等,尝试减少SGA的内存大小。
在windows下,可以尝试使用ORASTACK来减少线程的堆栈大小,以释放更多的内存。
考虑增加物理内存。

3.2.分析、定位ORA-4031

4031错误是Oracle在没有足够的连续空闲空间分配给SharedPool或者LargePool时抛出的错误。

3.2.1.4031错误产生的原因

前面我们描述SharedPool的空闲空间的请求、分配过程。在受到空闲空间请求时,内存管理模块会先查找空闲列表,看是否有合适的空闲chunk,如果没有,则尝试从LRU链表中寻找可释放的chunk,最终还未找到合适的空闲chunk就会抛出4031错误。
在讨论4031问题之前,可以先到第一章中找到与sharedpool(shared_pool_size、shared_pool_reserved_size、shared_pool_reserved_min_alloc)和largepool(large_pool_size)的参数描述,再了解一下这些参数的作用。这对于理解和分析4031错误会很有帮助。此外,还需要再回顾以下10g以后的SGA内存自动关联部分(相关参数是SGA_TARGET),因为使用这一特性,能大大减少4031错误产生的几率。

3.2.2.4031错误分析

通常,大多数的4031错误都是和sharedpool相关的。因此,4031错误的分析,主要是对sharedpool的分析。

3.2.2.1.对sharedpool的分析

当4031错误提示是sharedpool无足够连续内存可分配时,有可能是由于sharedpool不足或者sharedpool中严重的碎片导致的。
·Sharedpool不足分析
视图V$SHARED_POOL_RESERVED中可以查询到产生4031的一些统计数据、以及sharedpool中保留区(前面说了,保留区是用来缓存超过一定大小的对象的sharedpool区)的统计信息。
如果字段REQUEST_FAILURES>=0并且字段LAST_FAILURE_SIZE<_SHARED_POOL_RESERVED_MIN_ALLOC,可以考虑减小_SHARED_POOL_RESERVED_MIN_ALLOC,以使更多的对象能放到保留区中区(当然,你还需要观察字段MAX_USED_SPACE以确保保留区足够大)。如果还没有效果,就需要考虑增加shared_pool_size了。
·碎片问题分析
Librarycache和sharedpool保留区的碎片也会导致4031错误的产生。
还是观察上面的视图,如果字段REQUEST_FAILURES>0并且字段LAST_FAILURE_SIZE>_SHARED_POOL_RESERVED_MIN_ALLOC,就可以考虑增加_SHARED_POOL_RESERVED_MIN_ALLOC大小以减少放入保留区的对象,或者增加SHARED_POOL_RESERVED_SIZE和shared_pool_size(因为保留区是从shared
pool中分配的)的大小。
此外,要注意有一个bug导致REQUEST_FAILURES在9.2.0.7之前所有版本和10.1.0.4之前的10g版本中统计的数据是错误的,这时可以观察最后一次4031报错信息中提示的无法分配的内存大小。

3.2.2.2.对largepool的分析

Largepool是在MTS、或并行查询、或备份恢复中存放某些大对象的。可以通过视图v$sgastat来观察largepool的使用情况和空闲情况。
而在MTS模式中,sort_area_retained_size是从largepool中分配的。因此也要检查和调整这个参数的大小,并找出产生大量sort的会话,调整语句,减少其中的sort操作。
MTS中,UGA也是从largepool中分配的,因此还需要观察UGA的使用情况。不过要注意一点的是,如果UGA无法从largepool获取到足够内存,会尝试从sharedpool中去分配。

3.2.3.解决4031错误

根据4031产生的不同原因,采取相应办法解决问题。

3.2.3.1.bug导致的错误

有很多4031错误都是由于oraclebug引起的。因此,发生4031错误后,先检查是否你的系统的4031错误是否是由bug引起的。下面是已经发现的会引起4031错误的bug。相关信息可以根据bug号或note号到metalink上查找。

BUG
说明
修正版本
Bug1397603

ORA-4031由于缓存句柄导致的SGA永久内存泄漏

8172,901

Bug1640583

ORA-4031duetoleak/由于查询计划中AND-EQUAL访问路径导致缓冲内存链争用,从而发生内存泄漏。

8171,901

Bug:1318267

(未公布)

如果设置了TIMED_STATISTICS可能导致INSERTASSELECT无法被共享。

8171,8200

Bug:1193003

(未公布)

Oracle8.1中,某些游标不共享。

8162,8170,901

Bug2104071

ORA-4031太多PIN导致sharedpool消耗过大。

8174,9013,9201

Note263791.1

许多与4031相关的错误在9205补丁集中修正。

9205

3.2.3.2.Sharedpool太小

大多数4031错误都是由sharedpool不足导致的。可以从以下几个方面来考虑是否调整sharedpool大小:
·Librarycache命中率
通过以下语句可以查出系统的librarycache命中率:
SELECTSUM(PINS)"EXECUTIONS",SUM(RELOADS)"CACHEMISSESWHILEEXECUTING",1-SUM(RELOADS)/SUM(PINS)FROMV$LIBRARYCACHE;

如果命中率小于99%,就可以考虑增加sharedpool以提高librarycache的命中率。
·计算sharedpool的大小
以下语句可以查看sharedpool的使用情况
selectsum(bytes)fromv$sgastat

wherepool='sharedpool'

andname!='freememory';

专用服务模式下,以下语句查看cache在内存中的对象的大小,
selectsum(sharable_mem)fromv$db_object_cache;

专用服务模式下,以下语句查看SQL占用的内存大小,
selectsum(sharable_mem)fromv$sqlarea;

Oracle需要为保存每个打开的游标分配大概250字节的内存,以下语句可以计算这部分内存的占用情况,
selectsum(250*users_opening)fromv$sqlarea;

此外,在我们文章的前面部分有多处提到了如何分析sharedpool是否过大或过小,这里就不在赘述。

3.2.3.3.Sharedpool碎片

每当需要执行一个SQL或者PLSQL语句时,都需要从librarycache中分配一块连续的空闲空间来解析语句。Oracle首先扫描sharedpool查找空闲内存,如果没有发现大小正好合适的空闲chunk,就查找更大的chunk,如果找到比请求的大小更大的空闲chunk,则将它分裂,多余部分继续放到空闲列表中。这样就产生了碎片问题。系统经过长时间运行后,就会产生大量小的内存碎片。当请求分配一个较大的内存块时,尽管shared
pool总空闲空间还很大,但是没有一个单独的连续空闲块能满足需要。这时,就可能产生4031错误。
如果检查发现shared_pool_size足够大,那4031错误一般就是由于碎片太多引起的。
如果4031是由碎片问题导致的,就需要弄清楚导致碎片的原因,采取措施,减少碎片的产生。以下是可能产生碎片的一些潜在因素:
o没有使用共享SQL;
o过多的没有必要的解析调用(软解析);
o没有使用绑定变量。
以下表/视图、语句可以查询sharedpool中没有共享的SQL
·通过V$SQLAREA视图
前面我们介绍过这个视图,它可以查看到每一个SQL语句的相关信息。以下语句可以查出没有共享的语句,
SELECTsubstr(sql_text,1,40)"SQL",count(*),sum(executions)"TotExecs"FROMv$sqlareaWHEREexecutions<5–-语句执行次数GROUPBYsubstr(sql_text,1,40)HAVINGcount(*)>30–-所有未共享的语句的总的执行次数ORDERBY2;

·X$KSMLRU表
这张表保存了对sharedpool的分配所导致的sharedpool中的对象被清出的记录。可以通过它来查找是什么导致了大的sharedpool分配请求。
如果有许多对象定期会被从sharedpool中被清出,会导致响应时间太长和librarycachelatch争用问题。
不过要注意一点,每当查询过表X$KSMLRU后,它的内容就会被删除。因此,最好将查出的数据保存在一个临时的表中。以下语句查询X$KSMLRU中的内容,
SELECT*FROMX$KSMLRUWHEREksmlrsiz>0;

·X$KSMSP表
从这张表中可以查到当前分配了多少空闲空间,这对于分析碎片问题很有帮助。一些语句可以查询sharedpool的空闲列表中chunk的统计信息,
select'0(<140)'BUCKET,KSMCHCLS,KSMCHIDX,10*trunc(KSMCHSIZ/10)"From",count(*)"Count",max(KSMCHSIZ)"Biggest",trunc(avg(KSMCHSIZ))"AvgSize",trunc(sum(KSMCHSIZ))"Total"fromx$ksmspwhereKSMCHSIZ<140andKSMCHCLS='free'groupbyKSMCHCLS,KSMCHIDX,10*trunc(KSMCHSIZ/10)UNIONALLselect'1(140-267)'BUCKET,KSMCHCLS,KSMCHIDX,20*trunc(KSMCHSIZ/20),count(*),max(KSMCHSIZ),trunc(avg(KSMCHSIZ))"AvgSize",trunc(sum(KSMCHSIZ))"Total"fromx$ksmspwhereKSMCHSIZbetween140and267andKSMCHCLS='free'groupbyKSMCHCLS,KSMCHIDX,20*trunc(KSMCHSIZ/20)UNIONALLselect'2(268-523)'BUCKET,KSMCHCLS,KSMCHIDX,50*trunc(KSMCHSIZ/50),count(*),max(KSMCHSIZ),trunc(avg(KSMCHSIZ))"AvgSize",trunc(sum(KSMCHSIZ))"Total"fromx$ksmspwhereKSMCHSIZbetween268and523andKSMCHCLS='free'groupbyKSMCHCLS,KSMCHIDX,50*trunc(KSMCHSIZ/50)UNIONALLselect'3-5(524-4107)'BUCKET,KSMCHCLS,KSMCHIDX,500*trunc(KSMCHSIZ/500),count(*),max(KSMCHSIZ),trunc(avg(KSMCHSIZ))"AvgSize",trunc(sum(KSMCHSIZ))"Total"fromx$ksmspwhereKSMCHSIZbetween524and4107andKSMCHCLS='free'groupbyKSMCHCLS,KSMCHIDX,500*trunc(KSMCHSIZ/500)UNIONALLselect'6+(4108+)'BUCKET,KSMCHCLS,KSMCHIDX,1000*trunc(KSMCHSIZ/1000),count(*),max(KSMCHSIZ),trunc(avg(KSMCHSIZ))"AvgSize",trunc(sum(KSMCHSIZ))"Total"fromx$ksmspwhereKSMCHSIZ>=4108andKSMCHCLS='free'groupbyKSMCHCLS,KSMCHIDX,1000*trunc(KSMCHSIZ/1000);

如果使用ORADEBUG将sharedpool信息dump出来,就会发现这个查询结果和trace文件中空闲列表信息一直。
如果以上查询结果显示大多数空闲chunk都在bucket比较小的空闲列表中,则说明系统存在碎片问题。

3.2.3.4.编译java代码导致的错误

当编译java(用loadjava或deployjb)代码时产生了4031错误,错误信息一般如下:
ASQLexceptionoccurredwhilecompiling::

ORA-04031:unabletoallocatebytesofsharedmemory("sharedpool","unknownobject","joxlod:inith","JOX:ioc_allocate_pal")

这里提示时sharedpool不足,其实是错误,实际应该是javapool不足导致的。解决方法将JAVA_POOL_SIZE加大,然后重启实例。

3.2.3.5.Largepool导致的错误

Largepool是在MTS、或并行查询、或备份恢复中存放某些大对象的。但和sharedpool中的保留区(用于存放sharedpool的大对象)不同,largepool是没有LRU链表的,而后者使用的是sharedpool的LRU链表。
在largepool中的对象永远不会被清出的,因此不存在碎片问题。当由于largepool不足导致4031错误时,可以先通过v$sgastat查看largepool的使用情况,
SELECTpool,name,bytesFROMv$sgastatwherepool='largepool';

或者做一个dump,看看largepool中空闲chunk的大小情况。
进入largepool的大小条件是由参数LARGE_POOL_MIN_ALLOC决定的,根据以上信息,可以适当调整LARGE_POOL_MIN_ALLOC的大小。
Largepool的大小是由LARGE_POOL_SIZE控制的,因此当largepool空间不足时,可以调整这个参数。

3.2.4.SGA内存自动管理

10g以后,Oracle提供了一个非常有用的特性,即SGA内存自动管理。通过设置SGA_TARGET可以指定总的SGA大小,而无需固定每个区的大小。这就是说,当分配sharedpool或largepool时,只要SGA区足够大,就能获取到足够内存,因而可以大大减少4031错误发生的几率。

3.2.5.FLUSHSHAREDPOOL

使用绑定变量是解决sharedpool碎片的最好方法。此外,9i以后,可以设置CURSOR_SHARING为FORCE,强行将没有使用绑定变量的语句使用绑定变量,从而共享SQL游标。当采用以上措施后,碎片问题并不会马上消失,并可能还会长时间存在。这时,可以考虑flushsharedpool,将内存碎片结合起来。但是,在做flush之前,要考虑以下问题。
·Flush会将所有没有使用的游标从librarycache中清除出去。因此,这些语句在被再次调用时会被重新硬解析,从而提高CPU的占用率和latch争用;
·如果应用没有使用绑定变量,即使flush了sharedpool以后,经过一段时间运行,仍然会出现大量碎片。因此,这种情况下,flush是没有必要的,需要先考虑优化应用系统;
·如果sharedpool非常大,flush操作可能会导致系统被hung住。
因此,如果要flushsharedpool,需要在系统不忙的时候去做。Flush的语法为,
altersystemflushshared_pool;

3.2.6.TRACE4031错误

如果问题比较复杂(比如由于内存泄漏导致),或者你不幸遇上了oracle的bug,这时就需要考虑设置4031事件来trace并dump出相关内存信息。
以下语句在整个系统设置4031事件,
SQL>altersystemsetevents'4031tracenameerrorstacklevel3';SQL>altersystemsetevents'4031tracenameHEAPDUMPlevel3';

这个事件也可以在会话中设置,只要将以上语句中的“system”改为“session”就行了。
然后将dump出来的trace文件发给oracle吧。
不过注意一点,9205以后就无需设置这个事件了,因为一旦发生4031错误时,oracle会自动dump出trace文件。

4.Dump内存解析

下面以sharedpool为例,解释一下dump出来的内存结构。

SQL>connsys/sysassysdba

Connected.

SQL>oradebugsetmypid

Statementprocessed.

SQL>oradebugdumpheapdump2

Statementprocessed.

SQL>

以下时trace文件的内容,我们分别解释各个部分:

Dumpfilec:"oracle"product"10.2.0"admin"fuyuncat"udump"fuyuncat_ora_4032.trc

TueJul1116:03:262006

ORACLEV10.2.0.1.0-Productionvsnsta=0

vsnsql=14vsnxtr=3

OracleDatabase10gEnterpriseEditionRelease10.2.0.1.0-Production

WiththePartitioning,OLAPandDataMiningoptions

WindowsXPVersionV5.1ServicePack2

CPU:2-type586

ProcessAffinity:0x00000000

Memory(Avail/Total):Ph:885M/2039M,Ph+PgF:2702M/3890M,VA:1590M/2047M

Instancename:fuyuncat

Redothreadmountedbythisinstance:1

Oracleprocessnumber:18

Windowsthreadid:4032,image:ORACLE.EXE(SHAD)

***SERVICENAME:(SYS$USERS)2006-07-1116:03:26.322

***SESSIONID:(159.7)2006-07-1116:03:26.322

这部分是关于trace文件的基本信息,oracle版本、资源情况、用户和会话等。

KGHLatchDirectoryInformation

ldirstate:2nextslot:75

Slot[1]Latch:03C3D280Index:1Flags:3State:2next:00000000

Slot[2]Latch:1EC9D4B0Index:1Flags:3State:2next:00000000

Slot[3]Latch:1EC9D540Index:1Flags:3State:2next:00000000

Slot[4]Latch:03C3E100Index:1Flags:3State:2next:00000001

Slot[5]Latch:1ED65CE4Index:1Flags:3State:2next:00000000

Slot[6]Latch:1ED65F14Index:1Flags:3State:2next:00000000

......

这部分记录的是sharedpool中的latch信息。每个latch的具体信息可以通过视图V$LATCH、V$LATCH_PARENT、V$LATCH_CHILDREN或者表x$ksllt查出

******************************************************

HEAPDUMPheapname="sgaheap"desc=03C38510

extentsz=0x32c8alt=108het=32767rec=9flg=-126opc=0

parent=00000000owner=00000000nex=00000000xsz=0x10

******************************************************

这是堆dump信息的头部,heapname说明了内存所述的堆,sharedpool是属于SGA区的,因此,这里是"sga
heap";

extentsz记录的是所有扩展段的大小。

HEAPDUMPheapname="sgaheap(1,0)"desc=04EC131C

extentsz=0xfc4alt=108het=32767rec=9flg=-126opc=0

parent=00000000owner=00000000nex=00000000xsz=0x400000

EXTENT0addr=1CC00000

Chunk1cc00038sz=24R-freeable"reservedstoppe"

Chunk1cc00050sz=212888R-free""

Chunk1cc33fe8sz=24R-freeable"reservedstoppe"

Chunk1cc34000sz=3977544perm"perm"alo=3977544

Chunk1cfff148sz=3768free""

EXTENT1addr=1D000000

Chunk1d000038sz=24R-freeable"reservedstoppe"

Chunk1d000050sz=212888R-free""

Chunk1d033fe8sz=24R-freeable"reservedstoppe"

Chunk1d034000sz=2097168perm"perm"alo=2097168

这部分信息是trace文件中的主要部分,它详细记录了sharedpool中各个chunk的信息。

首先看它的头部信息,注意到这里heapname是"sgaheap(1,0)"。这是什么意思呢?我们前面提到,oracle10g会将shared
pool分为几个区来管理,这里就是其中的一个区。共有4个区。通过表X$KGHLU可以看到对应的LRU链表。

EXTENT0addr=1CC00000

这一行说明下面的chunk都属于这个扩展段(extent),0是它的编号,addr是它的起始地址。

Chunk1cc00038sz=24R-freeable"reservedstoppe"

这是一个chunk的信息,sz是这个chunk的大小(24字节)。R-freeable是这个chunk的状态,"reserved
stoppe"是这个chunk的用途。Chunk有4种可能状态,以下是这四种状态的含义:

free:即空闲chunk,可以随时分配给适合大小的请求;

freeable:这种状态的chunk表示它当前正在被使用,但是这种使用是短期的,比如在一次调用中或者一个会话中,会话或者调用解释就可以被释放出来。这种状态的chunk是不放在LRU链表中的,一旦使用结束,自动成为free状态,放到空闲列表中;

recreatable:这种状态的chunk正在被使用,但是它所包含的对象是可以被暂时移走、重建,比如解析过的语句。它是被LRU链表管理的。

permanent:顾名思义,这种状态的chunk所包含的对象是永远不会被释放的。即使flushsharedpool也不会释放。

我们注意到,这里还有一些状态是有前缀“R-”的。带有这种前缀的chunk说明是sharedpool中的保留区的chunk。

Totalheapsize=41942480

最后是这一sharedpool区的总的大小。

FREELISTS:

Bucket0size=16

Bucket1size=20

Chunk166ed050sz=20free""

Chunk167de068sz=20free""

Chunk164b9c10sz=20free""

Chunk1f2776f8sz=20free""

接下来便是这个sharedpool区的空闲列表。Bucket是一个空闲列表的范围,例如Bucket1,它的最小值是上一个Bucket的最大值,即16,最大值为20。Bucket下面是空闲列表中chunk,后面的信息和前面解释chunk的信息一样,8位的16进制数字是它的地址;sz是chunk的大小;free是chunk的状态,因为是空闲列表中的chunk,这里只有一个状态;最后是chunk的用途,因为都是free,所以肯定为空。

Totalfreespace=1787936

最后是这块sharedpool区中空闲chunk的总的大小。

RESERVEDFREELISTS:

Reservedbucket0size=16

Reservedbucket1size=4400

Reservedbucket2size=8204

Reservedbucket3size=8460

Reservedbucket4size=8464

Reservedbucket5size=8468

Reservedbucket6size=8472

Reservedbucket7size=9296

Reservedbucket8size=9300

Reservedbucket9size=12320

Reservedbucket10size=12324

Reservedbucket11size=16396

Reservedbucket12size=32780

Reservedbucket13size=65548

Chunk1b800050sz=212888R-free""

Chunk16c00050sz=212888R-free""

Chunk1ac00050sz=212888R-free""

Totalreservedfreespace=638664

Sharedpool的普通区的空闲列表下面就是关于这块sharedpool区中保留区的空闲列表的描述,其中除了在名字上bucket前面都有一个Reserved标识,和状态前面有“R-”前缀外,含义和普通空闲列表相同。

UNPINNEDRECREATABLECHUNKS(lrufirst):

Chunk1aee99c0sz=4096recreate"sqlarea"latch=1D8BDD48

Chunk1ae4aeecsz=4096recreate"sqlarea"latch=1D8BDDB0

......

SEPARATOR

Chunk166e8384sz=540recreate"KQRPO"latch=1DD7F138

Chunk1f333a5csz=284recreate"KQRPO"latch=1DC7DFC8

Chunk166e9340sz=540recreate"KQRPO"latch=1DE00A70

Chunk1f0fe058sz=284recreate"KQRPO"latch=1DC7DFC8

Chunk1f2116b4sz=540recreate"KQRPO"latch=1DE81910

Chunk1f21127csz=540recreate"KQRPO"latch=1DE81910

......

Unpinnedspace=1611488rcr=645trn=864

空闲列表后面就是LRU链表了。LRU链表不是按照大小分的,因而没有Bucket。它的chunk是按照最近最少使用的顺序排列。其中chunk的信息和前面解释的一样。但是要注意一点,因为LRU链表中的chunk都是使用的,因为每个chunk根据用途不同,都会有一个latch来保护,Chunk信息最后便是latch的地址。

注意,我们前面提到,sharedpool中是有两种LRU链表的,一种循环LRU链表;另外一种是暂时LRU链表。在这里LRU信息中前面部分是循环LRU链表,SEPARATOR后面部分是暂时LRU链表信息。

最后是LRU链表中chunk的总的大小,rcr是循环LRU链表中的chunk数,trn是暂时LRU链表中的chunk数。

此外,有一点提示,如果是有多个sharedpool区,第一个区是不含LRU链表信息的。

PERMANENTCHUNKS:

Chunk1d234010sz=1884144perm"perm"alo=1728440

Chunk1cc34000sz=3977544perm"perm"alo=3977544

Chunk1d034000sz=2097168perm"perm"alo=2097168

Chunk1d434000sz=3117112perm"perm"alo=3117112

......

Chunk1f434000sz=3917704perm"perm"alo=3917704

Permanentspace=38937696

最后是永久chunk的信息。Chunk部分解释和前面一致。alo表示已经分配的大小。

如果有多个sharedpool区,永久chunk信息则只存在于第一个sharedpool区。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: