2.4.Oracle在windows下的内存管理
2.4.1.Windows内存系统概述
Windows
NT使用一个以页为基础的虚拟内存系
统,该系统使用32位线性地址。在内部,系统管理被称为页的4096字
节段中的所有内存。每页的物理内存都被备份。
对于临时的内存页使用页文件(pagefile),而对于只读的内存页,则使用磁盘文件。在同
一时刻,最多可以有16个不同的页文件。代码、资源和其它只读数据都是通过它们创建的文件直接备
份。
Windows
NT为系统中的每一个应用程序(进
程)提供一个独立的、2
GB的用户地址空间。对于应用程序来说,好象是有2 GB的可用内存,而不用考
虑实际可用的物理内存的量。如果某个应用程序要求的内存比可用的内存更多时,Windows
NT是这样满足这种要求的,它从这个和/或其他的进程把非关键内存分页(paging)到一个页文件,并且释放这些物理内存页。结果,在Windows
NT中,全局堆不再存在。相反,每一个进程都有其自己的32位地址空间,在其
中,该进程的所有内存被分配,包括代码、资源、数据、DLL(动态链接库),和动态内存。实际上,系统仍然要
受到可用的硬件资源的限制,但是实现了与系统中应用程序无关的、对于可用资源的管理。
Windows
NT在内存和地址空间之间作出了区
分。每个进程分配到2
GB的用户地址空间,而不管对于该进程的实际可用物理内存有多少。而且,所有进程都使用相同范围的线性32位地址,范围从0000000016-7FFFFFFF16,
而不考虑可用内存的地址。Windows
NT负责在适当的时间把内存页映射(paging)到磁盘以及从磁盘页映射回内
存,使得每个进程都确保能够寻址到它所需要的内存。尽管有可能出现两个进程试图同时访问同一虚拟地址上的内存,但是,实际上Windows
NT虚拟内存管理程序是在不同的物理位置描述这两个内存的位置。而且这两个地址都不见得与原始的虚拟地址一致。这就是虚拟内存。
Win32环境下,32位
的地址空间转化为4GB的虚拟内存。默认情况下,将一半(2GB)
分配给用户进程(因而一个进程的最大可用虚拟内存为2G,oracle进
程同样受此限制),另一半(2GB)分配给操作系统。
因为虚拟内存的存在,一个应用程序能够管理它自
己的地址空间,而不必考虑在系统中对于其它进程的影响。在Windows
NT中的内存管理程序负责查看在任何给定的时间里,所有的应用程序是否有足够的物理内存进行有效的操作。Windows
NT操作系统下的应用程序不必考虑和其它应用程序共享系统内存这个问题。并且,即使在应用程序自己的地址空间内,它们仍能够与其它的应
用程序共享内存。
区分内存和地址空间的一个好处是,为应用程序提
供了将非常大的文件加载到内存的能力。不必将一个大的文件读进内存中,Windows
NT为应用程序保留该文件所需的地址范围提供了支持。然后,在需要的时候,该文件部分就可以被浏览了(物理性地读进内存)。通过虚拟内
存的支持,对于大段的动态内存的分配同样可以做到这一点。
在任意给定的时间,进程中每个地址都可以被当作
是空闲的(free)、保留的(reserved)
或已提交的(committed)。进程开始时,所有地址的都是空闲的,意味着它们都是自由空间并
且可以被提交到内存,或者为将来使用而保留起来。在任何空闲的地址能够被使用前,它必须首先被分配为保留的或已提交的。试图访问一个保留的或已提交的地址
都将产生一个访问冲突异常(access
violation exception)。
一个进程中的所有2 GB的地址要么为了使用而是空闲的、要么为了将来的使用而是保留的、要么已提交到特定的内存(在使用的)。
一旦地址被以保留的或者已提交的形式分配,VirtualFree是唯一可以释放它们的方法棗那就是,将它们返回到自由的地址。VirtualFree还可以用来对已提交的页解除提交,同时,返回这些地址到保留状态。当解除地址的提交时,
所有与该地址相关的物理内存和页文件空间都被释放。
在Windows
NT中的进程有一个被称为工作组(working
set)的最小页,是为了进程能够顺利地运行,在运行时在内存中必须被提供。Windows
NT在启动时为一个进程分配了默认数量的页数,并且逐渐地调整该数,使得系统中所有激活的进程的性能达到一种平衡的最优。当一个进程正在运
行时(实际上是,是一个进程的线程正在运行时),Windows
NT“尽量”确保该进程的工作组页总是驻留在物理内存中。工作集即在物理内存中保持的虚拟页面的子集,分进程工作集和系统工作集。
2.4.2.Windows下Oracle的内存配置
在windows下,Oracle实例作为一个单独的进程运行,这个进程是一个标准的Win32应
用程序,它能够申请最大为2G的虚拟内存地址空间,所有用户连接后后台线程的分配内存(包括像buffer
cache那些全局内存)必须小于2G(64位
平台中无此限制,32位平台中可以通过设置参数use_indirect_data_buffers来突破这一限制,不详述)。
Oracle可以运行于windows
NT下的任何版本,但是Oracle一般不推荐将Oracle数据库运行在PDC(主域控服务器)或BDC(备域控服务器)下。这是因为域控服务器会需要大量的文件缓存(这会消耗大量内存,影响到Oracle)和网络资源。
而文件缓存带来的另外一个问题就是,这个机器到
底是作为一个专用的数据库服务器还是一个混合服务器。因为Oracle数据库不会用到文件缓存(log
buffer就相当于Oracle自己的文件缓存),它通过直接写磁盘来避免文
件缓冲。
在专用数据库服务器系统上,用户一定要确保没有
使用到页文件(pagefile即虚拟内存文件)。否则,可以通过修改Oracle参数或者增大物理内存来避免。如果大量额页被持续的移入、移出到虚拟内存中,会严重影响到性能。
如果是专用服务器系统,有以下建议:
o如果分配给Oracle的总内存能保证不会超过物理内存,则虚拟内存页可以被设置位物理内存的50%,并且可以增长到物理内存的100%大小(在my
computer => properties => Advanced => Performance =>
Settings =>
Advanced => Virtual Memory => Change中设置,设置Initial
size为物理内存的50%,Maximum
Size和物理内存大小相同);
o对于主要是运行纯Oracle数据库的系统(但不是专用),一般推荐虚拟内存页大小在1倍
到1.5倍于物理内存大小之间;
o对于物理内存大于2G的机器,要求虚拟内存页最少为2G。
一个机器上能被用于分配的总内存等于物理内存加
上扩展前的虚拟内存页大小。你一定要避免设置参数如buffer_cache_size或其他相关
参数导致Oracle要求分配内存大于物理内存。尽管Oracle的
分配内存大小是限制在总内存(物理内存+最小虚拟内存)之内,但是,对虚拟内存页的访问是非常慢的,会直接影响到系统性能,因此Oracle分配内存要小于物理内存大小以避免发生内存交换。
如果系统是混合应用,除了Oracle数据库外还运行了其他程序,这时就需要考虑设置虚拟内存页大于物理内存了。那些当前不活动的进程可
以减少它们的工作区(working
set即物理内存)以使活动进程能增加工作区。如果Oracle数据库运行在
这样的机器上,则推荐虚拟内页最少1.5到2倍
的物理内存大小,特别是在内存大于2G时。
在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)——可以通过Page
File都已经被分配。但只有一小部分内存页(如果没有设置PRE_PAGE_SGA的
话;这小部分内存以granule为单位,固定SGA【包
括redo
buffer】一个、Buffer Cache一个、Shared
Pool一个)被触及(touch)而已经分配到工作组(working
set)中,而其他更多的页需要使用时才分配到工作组中。
通过设置注册表可以设置Oracle进程的最小工作组大小和最大工作组大小:
o
ORA_WORKINGSETMIN或ORA_%SID%_WORKINGSETMIN:Oracle.EXE进程的最小工作组大小(M为单
位)
o
ORA_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时同时也遵循64K的最小粒度,来提交倍数于这
个粒度值的内存页。许多用户会话经常分配许多小于64K的内存,这就导致地址空间出现碎片,因为许
多64K区只有两个页被提交。
一旦地址空间被用户会话占满了后,如果要再创建
一个新会话,就会存在无法分配到地址空间的危险。将可能报以下错误:
ORA-12500
/
TNS-12500
TNS:listener
failed to start a dedicated server process
也可能报这些错误:
o
ORA-12540
/ TNS-12540 TNS:internal limit restriction exceeded
o
NT-8 Not
enough storage is available to process this command
o
skgpspawn
failed:category = ....
o
ORA-27142
could not create new process
o
ORA-27143
OS system call failure
o
ORA-4030
out of process memory when trying to allocate ....
因为地址空间碎片问题和DLL被载入了oracle服务进程的地址空间,这些错
误很可能发生再当Oracle进程占用大概1.6G~1.7G(可以通过任务管理器或
2.4.4.1.会话内存大小设置
我们前面说了,一个进程的全部内存大小被限制在2G以内。因此,对于一个有许多用户同时访问的系统,要考虑这些会话总内存小于2G
– SGA的大小。
下列参数会影响每个会话的内存大小(这些参数前
面都有介绍):
o
bitmap_merge_area_size
o
create_bitmap_area_size
o
hash_area_size
o
open_cursors
o
sort_area_size
(sort_area_retained_size)
在没有设置PGA_AGGREGATE_TARGE(这个参数能尽量但不一定使所有会话PGA之和在指定范围内)参数时,需要调整这些参数,以使所有会话占用内存与SGA之和小于2G。过多的使用PL/SQL结构体(如PL/SQL
TABLE、ARRAY)也会导致会话内存增大。
2.4.4.2.ORASTACK修改线程堆栈大小
Oracle提供了ORASTACK工
具让用户内修改Oracle执行程序创建会话、线程时的默认堆栈大小。当ORASTACK应用于一个可执行程序时,它会修改程序头部的、定义使用创建线程API函数所指定默认堆栈大小的二进制区,以修改默认堆栈的大小。没有必要区修改线程提交页数的默认值,因为它
们时从堆栈中请求到的。当用户非常多时,通过减少每个创建在Oracle中会话堆栈大小,可以节省
大量内存。比如,一个1000用户的系统,将堆栈从1M降
为500K后,能节省出1000
* 500K = 500M的地址空间。
在需要使用ORASTACK来降低现场堆栈大小时,你需要测试你的系统以保证新的堆栈大小能确保系统正常运行。如果堆栈大
小被缩小到Oracle服务端所必须的堆栈大小以下,就会产生堆栈溢出错误,用户进程就失败(通常
报ORA-3113错误),并且在alert
log中不会有报错而且页不产生trace文件。Oracle一般不推荐将堆栈该到500K以下(尽管不
少系统在300K时也能正常运行)。
ORASTACK必须修改所有能在oracle中
创建线程的进程,使用语法如下:
orastack executable_name new_stack_size_in_bytes
下面的例子将堆栈改为500K:
orastack oracle.exe 500000
orastack tnslsnr.exe 500000
orastack svrmgrl.exe 500000
orastack sqlplus.exe 500000
在使用ORASTACK之
前必须保证没有任何oracle进程正在运行(可以通任务管理器或者
此外,如果有程序在本地连接(没有通过SQL*NET)了Oracle,也要先停止(如在本
地运行sqlplus连接了实例)。
2.4.4.3.会话内存如何释放、线程如何结束
当会话成功结束后,它会按用Win32 API函数VirtualFree来释放它
的内存,调用此函数时,需要指定MEM_DECOMMIT
| MEM_RELEASE标识。当所有内存被释放后,堆栈也被释放,将Oracle进
程中指向完成的会话的地址空间空闲出来。
如果一个用户会话被异常结束,它将不会释放它所
分配的内存,这些内存页会被继续保留在Oracle进程的地址空间中,知道进程结束。会话的异常结
束可能由以下原因导致的:
o
Shutdown
abort.
o
Alter
session kill session.
o
orakill杀掉的会话.
o
Oracle管理助手for
Windows:kill session.
o其他杀线程的工具
(
Oracle建议尽量少使用以上方式(命令),特别是shutdown abort(这种停止实例的方法所带来的问题还不止这个)。当调用shutdown
abort时,Oracle会调用Win32
API函数TerminateThread来中止每个用户会话,这个命令将直接
杀掉线程而不释放它们的内存。如果系统已经接近2G地址空间的限制,Oracle实例再次启动要分配内存时就会产生问题。唯一释放Oracle全
部内存的方法是停止和启动Oracle服务(服务OracleService)。
如果windows
NT下的系统需要能被许多用户访问,可以通过以下措施来优化内存的使用:
o降低相关的PGA、UGA内存参数(如SORT_AREA_SIZE);
o降低SGA的参数;
o使数据库作业队列数(参数job_queue_processes控制)和并行查询slave(参数parallel_max_servers控
制)最小,因为它们也会导致Oracle进程创建线程;
o使用ORASTACK降低会话、线程堆栈大小到500K;
o考虑使用MTS模式;
o考虑将Windows
NT升级到Windows NT企业版;
o考虑升级硬
件以支持Intel
ESMA(Extended. Server Memory Architecture,
扩展服务内存架构)