Oracle内存全面分析(8)

本文深入探讨了Oracle数据库的内存管理机制,介绍了虚拟内存、进程内存、SGA和PGA等关键概念,以及Oracle如何利用这些概念来优化内存使用。

  作者: 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 的堆都是由这个模块管理的。

一个堆( Heap )包括一个堆描述符和一个或多个内存扩展段( extent )。一个堆还可以包含子堆( Subheap )。这种情况下,堆描述符和子堆的扩展段可以被视为其父堆的大块( chunk . 堆描述符的大小依赖于堆的类型和堆的空闲列表和 LRU 列表所包含的列表( Header )头的多少。一个扩展段又一个包含指向前一个和后一个扩展段指针( Pointer )的小的头部,扩展段的其他内存就是堆可用于动态分配的内存。

除了还包含一个保留列表的这一特性外, 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 )也会导致会话内存增大。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值