JVM虛擬機器和Oracle資料庫記憶體管理的學習

济南小老虎發表於2024-04-23

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的要小一些.
建議不同架構不同作業系統 上面還是進行定製化處理好一些. 

相關文章