Oracle内存全面分析(10)

作者: fuyuncat
来源: www.HelloDBA.com

 

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 )分配给用户进程(因而一个进程的最大可用虚拟内存为2Goracle 进程同样受此限制),另一半(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 那些全局内存)必须小于2G64 位平台中无此限制,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.52 倍的物理内存大小,特别是在内存大于2G 时。

Oracle 8.1.x 之前,启动Oracle 服务时不会启动Oracle 实例。此时(即只启动了Oracle 服务,没有启动实例),分配给Oracle.EXE 的主要内存是给相关DLL 的,大概20M (各个版本的DLL 不同,因此占用内存情况也不同)。9i 之后,Oracle 实例会随着服务启动,不过我们可以在服务启动后再关闭实例,这时就可以观察出Oracle 服务所占用的内存大小了:

Oracle内存全面分析(10)_第1张图片

 

 

这是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 CacheRedo Buffer )——可以通过TopShow 观察到实例启动后,所需要的Page File 都已经被分配。但只有一小部分内存页(如果没有设置PRE_PAGE_SGA 的话;这小部分内存以granule 为单位,固定SGA 【包括redo buffer 】一个、Buffer Cache 一个、Shared Pool 一个)被触及(touch )而已经分配到工作组(working set) 中,而其他更多的页需要使用时才分配到工作组中。

通过设置注册表可以设置Oracle 进程的最小工作组大小和最大工作组大小:

o        ORA_WORKINGSETMIN ORA_%SID%_WORKINGSETMINOracle.EXE 进程的最小工作组大小(M 为单位)

o        ORA_WORKINGSETMAX ORA_%SID%_WORKINGSETMAXOracle.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 运行调用接口(如malloclocalalloc )都会使用这个默认堆。当需要时,进程能在虚拟内存地址空间中创建另外的命名堆。默认堆创建为1M 大小(被保留和提交的)的内存区,当执行分配或释放这个堆的操作时,堆管理器提交或撤销这个区。而访问这个区是通过临界区来串行访问的,所以多个线程不能同时访问这个区。

当进程创建了一个线程后,windows NT 会为线程堆栈(每个现场都有自己的堆栈)保留一块地址空间区域,并提交一部分这些保留区。当一个进程连接到标准区时,系统为堆栈保留一个1M 大小的虚拟地址空间并提交这个区顶部的两个页。当一个线程分配了一个静态或者全局变量时,多个线程可以同时访问这个变量,因此存在变量内容被破坏的潜在可能。本地和自动变量被创建在线程的堆栈中,因而变量被破坏的可能性很小。堆栈的分配是从上至下的。例如,一个地址空间从0x080000000x080FF000 的堆栈的分配是从0x080FF0000x08001000 来提交内存页,如果访问在0x08001000 的页就会导致堆栈溢出异常。堆栈不能增长,任何试图访问堆栈以外的地址的操作都可能会导致进程被中止的致命错误。

2 .4.4.  会话内存的分配

当监听创建了一个用户会话(线程和堆栈)时,Oracle 服务进程就通过调用Win32 API 函数创建用户连接所必须的内存结构,并且指定MEM_RESERVE | MEM_COMMIT 标识,以保持和提交给线程私有的地址空间区域。

当调用了VirtualAlloc 来在指定地址保留内存,它会返回一个虚拟地址空间到下一个64K 大块(chunkwindows 内存分配最小单位)的地址。Oracle 调用VirtualAlloc 时不指定地址,只传一个NULL 在相应参数中,这会返回到下一个64K 大块的地址。因此用户会话在分配PGAUGACGA 时同时也遵循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.6G1.7G (可以通过任务管理器或TopShow 查看)时。

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 TABLEARRAY )也会导致会话内存增大。

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 进程正在运行(可以通任务管理器或者TopShow )。

此外,如果有程序在本地连接(没有通过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 Windowskill session.

o        其他杀线程的工具(TopShow 工具提供了杀线程的功能)

Oracle 建议尽量少使用以上方式(命令),特别是shutdown abort (这种停止实例的方法所带来的问题还不止这个)。当调用shutdown abort 时,Oracle 会调用Win32 API 函数TerminateThread 来中止每个用户会话,这个命令将直接杀掉线程而不释放它们的内存。如果系统已经接近2G 地址空间的限制,Oracle 实例再次启动要分配内存时就会产生问题。唯一释放Oracle 全部内存的方法是停止和启动Oracle 服务(服务 OracleService<SID> )。

 

如果windows NT 下的系统需要能被许多用户访问,可以通过以下措施来优化内存的使用:

o        降低相关的PGAUGA 内存参数(如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 ESMAExtended. Server Memory Architecture ,扩 展服务内存架构)

 

 

你可能感兴趣的:(oracle,windows,工作,数据库,buffer,数据库服务器)