在Oracle 8.1.x之前,启动Oracle服务时不会启动Oracle实例。此时(即只启动了Oracle服务,没有启动实例),分配给Oracle.EXE的主要内存是给相关DLL的,大概20M(各个版本的DLL不同,因此占用内存情况也不同)。9i之后,Oracle实例会随着服务启动,不过我们可以在服务启动后再关闭实例,这时就可以观察出Oracle服务所占用的内存大小了:
这是windows下一个Oracle 10g的实例关闭后内存占用情况。我们看到此时Oracle服务占用的内存是32M。
Windows下,可以使用以下语句来计算Oracle占用的虚拟内存大小:
select sum(bytes)/1024/1024 + 22/*DLL占用内存*/ Mb
from (select bytes from v$sgastat -- SGA内存
union
select value bytes from -- 会话内存
v$sesstat s,
v$statname n
where
n.STATISTIC# = s.STATISTIC# and
n.name = 'session pga memory'
union
select 1024*1024*count(*) bytes -- 线程堆栈
from v$process
);
在实例启动时,所有全局内存页都被保留和提交(所有共享全局区、Buffer Cache和Redo Buffer)――可以通过TopShow观察到实例启动后,所需要的Page File都已经被分配。但只有一小部分内存页(如果没有设置PRE_PAGE_SGA的话;这小部分内存以granule为单位,固定SGA【包括redo buffer】一个、Buffer Cache一个、Shared Pool一个)被触及(touch)而已经分配到工作组(working set)中,而其他更多的页需要使用时才分配到工作组中。
通过设置注册表可以设置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所有区相关参数有关。通过调用Win32 API接口“VirtualAlloc”,在接口函数的参数中指定MEM_RESERVE | MEM_COMMIT内存分配标识和PAGE_READWRITE保护标识,这部分内存始终会被保留和提交。这就保证所有线程都能访问这块内存(SGA是被所有线程共享的),并且这块内存区始终有物理存储(内存或磁盘)所支持。
VirtualAlloc函数不会触及这块区域内的内存页,这就是说分配给SGA组件(如buffer cache)的内存在没有触及之前是不会到工作组中去的。
VirtualAlloc
VirtualAlloc函数保留或提交调用此函数的进程的虚拟内存地址空间中的一段内存页。如果没有指定MEM_RESET,被这个函数分配的内存自动初始化为0,
Buffer Cache通常被创建为一个单一的连续内存区。但这并不是必须的,特别是当使用了非常大的Buffer Cache时。因为dll内存和线程分配的内存会导致虚拟地址空间产生碎片。
当一个进程创建后,windows NT会在进程的地址空间中创建一个堆(heap),这个堆被称为进程的默认堆。许多Win32 API调用接口和C运行调用接口(如malloc、localalloc)都会使用这个默认堆。当需要时,进程能在虚拟内存地址空间中创建另外的命名堆。默认堆创建为1M大小(被保留和提交的)的内存区,当执行分配或释放这个堆的操作时,堆管理器提交或撤销这个区。而访问这个区是通过临界区来串行访问的,所以多个线程不能同时访问这个区。
当进程创建了一个线程后,windows NT会为线程堆栈(每个现场都有自己的堆栈)保留一块地址空间区域,并提交一部分这些保留区。当一个进程连接到标准区时,系统为堆栈保留一个1M大小的虚拟地址空间并提交这个区顶部的两个页。当一个线程分配了一个静态或者全局变量时,多个线程可以同时访问这个变量,因此存在变量内容被破坏的潜在可能。本地和自动变量被创建在线程的堆栈中,因而变量被破坏的可能性很小。堆栈的分配是从上至下的。例如,一个地址空间从0x08000000到0x080FF000的堆栈的分配是从0x080FF000到0x08001000来提交内存页,如果访问在0x08001000的页就会导致堆栈溢出异常。堆栈不能增长,任何试图访问堆栈以外的地址的操作都可能会导致进程被中止的致命错误。
2.4.4.会话内存的分配
当监听创建了一个用户会话(线程和堆栈)时,Oracle服务进程就通过调用Win32 API函数创建用户连接所必须的内存结构,并且指定MEM_RESERVE | MEM_COMMIT标识,以保持和提交给线程私有的地址空间区域。
当调用了VirtualAlloc来在指定地址保留内存,它会返回一个虚拟地址空间到下一个64K大块(chunk,windows内存分配最小单位)的地址。Oracle调用VirtualAlloc时不指定地址,只传一个NULL在相应参数中,这会返回到下一个64K大块的地址。因此用户会话在分配PGA、UGA和CGA