JVM虚拟机和Oracle数据库内存管理的学习

JVM虚拟机和Oracle数据库内存管理的学习


背景

周四陪孩子吃早饭时看到了DB宝推送的 Oracle shared_pool内存的文章.
然后一路看到公司, 发现里面写的跟最近自己在看的jvm内存有很多相通的地方.

所以想尝试总结一下两者内存管理的一些可以用来提高性能的地方

Oracle的内存分布

Oracle的管理,其实与操作系统也非常相似.
主要也是分为: 内存管理, 线程管理, 存储管理. 
存储管理就是 物理文件存储和逻辑存储.  (其实还有ASM)
线程管理 主要分为 用户的线程和数据库本身的线程.

内存管理就是今天想学习的核心
分为PGA和SGA
PGA 线程私有 SGA 是线程共享区域

其实JVM的内存管理, 也是分为 堆区和非堆区
堆区都是线程共享的.
非堆区里面的 线程栈区域是线程私有的.
两者很相似. 

关于线程共享内存的查看

SELECT
	pool,
	round( sum( bytes / 1024 / 1024 ), 0 ) bytes 
FROM
	v$sgastat 
GROUP BY
	pool 
ORDER BY
	bytes DESC;

我这边一个测试环境为:

池类型 池大小(单位MB)
未命名的池 280064
shared pool 8971
java pool 3584
streams pool 1003
large pool 717

OracleSGA区域JVM区域的对照

最大的一部分: 未命名的内存池
包含:
块缓冲区(缓存的数据库块)、
重做日志缓冲区
“固定SGA”区专用的内存

fixed area 应该都有, 是一个自启,并且进行链接其他区域使用.
Redo buffer 就是重做日志的区域, 这个区域其实不能太大,
 事务提交,必须要落盘的. 写入buffer 并不安全.

block buffer 这里面是解决物理读写, 改成逻辑读写的核心部分. 
他又分为: 
默认池(default pool):所有段块一般都在这个池中缓存。这就是原先的缓冲区池
    (原来也只有一个缓冲区池)。
保持池(keep pool):按惯例,访问相当频繁的段会放在这个候选的缓冲区池中,
    如果把这些段放在默认缓冲区池中,尽管会频繁访问,
    但仍有可能因为其他段需要空间而老化(aging)。
回收池(recycle pool):按惯例,访问很随机的大段可以放在这个候选的缓冲区池中,
    这些块会导致过量的缓冲区刷新输出,而且不会带来任何好处,因为等你想要再用这个块时,
    它可能已经老化退出了缓存。要把这些段与默认池和保持池中的段分开,
    这样就不会导致默认池和保持池中的块老化而退出缓存。

其实 未命名的这一块就很想jvm 里面的堆区
fixed 类似于 类加载器加载的那一块区域. 是bootstrap用的部分. 
redo buffer 其实应该对比 直接内存, 但是jvm 一般没有写数据库的动作,可以暂时不这样对比.
block buffer 就是跟最大的堆区对照了. 

他的保持区 就类似于老年代. 
默认池的话就类似于 咱们的青年代 
回收池 其实jvm没有这个设置, 他更类似于 大对象直接进入老年代的概念了. 

OracleSGA区域JVM区域的对照

shared pool(共享池)
共享池里面 会缓存 SQL 的代码执行计划等内容.
类似于 JVM里面的code cache.

这个区域太小了 肯定存在问题, 会导致执行计划的换入换出, 
捯饬重新进行SQL解析和执行计划的绑定. 
但是太大了也不好, 会导致遍历执行计划的效率降低. 速度会变慢. 

JVM的code cache也一样. 太小了, 会导致C2编译进程效率降低
如果flush了 会重新编译, 导致浪费应用的CPU
如果Code cache太大了, 可能也会影响获取编译后方法的效率.
以及产生大量的内存碎片进而影响性能. 

OracleSGA区域JVM区域的对照

large pool 大池

    大池(large pool)并不是因为它是一个“大”结构才这样取名(不过,它可能确实很大)。
之所以称之为大池,是因为它用于大块内存的分配,共享池不会处理这么大的内存块。
    在Oracle 8.0引入大池之前,所有内存分配都在共享池中进行。如果你使用的特性要利用“大块
的”内存分配(如共享服务器UGA内存分配),倘若都在共享池中分配就 不太好。
另外,与共享池管理内存的方式相比,处理(需要大量内存分配)会以不同的方式使用内存,
所以这个问题变得更加复杂。共享池根据LRU来管理内存, 这对于缓存和重用数据很合适。
不过,大块内存分配则是得到一块内存后加以使用,然后就到此为止,没有必要缓存这个内存。
我的理解是:其实是把原来属于共享 池里面的一些特殊的内存拿出来进行不同的处理。
因为这些内存用完之后就可以立即释放,而共享池的内存不存在释放问题,因为是大家共享的。

     大池专门用于以下情况:
共享服务器连接,用于在SGA中分配UGA区,因为一个用户断开之后,UGA就可以立即释放!
语句的并行执行,允许分配进程间的消息缓冲区,这些缓冲区用于协调并行查询服务器。
    一旦发送了缓冲消息就可以立即释放!
备份,在某些情况下用于RMAN磁盘I/O 缓冲区。因为写入磁盘之后,这些缓存可以立即释放!

Java pool Java池

    在数据库中运行Java代码时用到这部分内存。例如:编写Java存储过程在服务器内运行。
需要注意的是,该内存与常见的Java编写的B/S系统并没关系。
用JAVA语言代替PL/SQL语言在数据库中写存储过程才会用到这部分内存。

Stream pool 流池

    9iR2以上增加了“流”技术,10g以上在SGA中增加了流池。流是用来共享和复制数据的工具。

关于内存的理解

1. jvm的堆区和Oracle的block buffer 有着相同的作用. 
加速系统的使用效率. 
如果数据库的block buffer 太小, 会导致数据块被换出,可能产生大量的物理读.导致性能衰退
JVM的堆区如果太小, 会导致经常fullGC,导致卡顿和性能衰退. 

大页内存以及快速内存都可能对性能有正向的作用. 

2. 关于快速CPU的影响.
数据库和JVM都应该使用高主频的CPU. 高主频的CPU 会减少栓锁,自旋的时间. 
高主频的CPU 能够快速的完成锁的占用和使用, 能够在相同时间内完成更多的事务
提高吞吐量. 

一个简单的猜想, 理论上主频的提升相对于性能应该是平方倍数, 或者是更高的效率提升. 

3. 关于Oracle的shared pool 和 jvm的方法区和类压缩区

shared pool 会存储 经过语法语义解析以及CBO 产生执行计划的SQL. 
SQL通过hash值进行存放, 还有一个问题是会产生多版本. 
内存太多了会碎片化, 内存太小了会导致换入换出.
还有很严重的问题是 shared_pool 其实是基于统计信息进行生产执行计划
按理说进行了统计分析,需要重新进行执行计划的创建. 
如果表变化比较快 或者是更改过索引, 重新收集过统计信息
可以需要重新进行一下设置. 
此时. 绑定变量的SQL和不绑定变量的SQL执行时间可能是不一样的
必须通过执行计划 进行分析. 

跟jvm的方法区类似.
不能太小, 如果不刷新方法区,会导致产品变慢, 代码无法进行深度优化. 
也不建议太大. 可能会导致方法区出现碎片化. 

此时合理的进行方法区的清理机制就很重要了. 
JVM 会根据执行次数 进行不同深度的优化. 

执行1000次和执行一千万次需要的优化层级是不一样的. 
而且jvm内部还有一些内联, 会将代码组合, 加快性能.

此时这里 不同架构需要的方法区的大小就不一样了. 
理论上 CISC的native code代码可能会比 RISC的要小一些.
建议不同架构不同操作系统 上面还是进行定制化处理好一些. 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章