1. 程式人生 > >JVM無法向虛擬機器申請記憶體

JVM無法向虛擬機器申請記憶體

一、問題背景

       在偽分散式的Hadoop安裝中,hadoop-env.sh配置檔案關於記憶體分配的項都是預設值,sbin/hadoop-daemon.sh start namenode可以,但是當sbin/hadoop-daemon.sh start datanode或者jps時失敗。提示:

Error occurred during initialization of VM
Could not reserve enough space for the card marking array
或者
Error occurred during initialization of VM
Could not reserve enough space for object heap
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

 

 二、問題解決

   (1)嘗試改變記憶體大小。但是hadoop都是使用的預設大小,按理說不應該需要改變。

     hadoop配置檔案,hadoop-env.sh中有個選項HADOOP_NAMENODE_OPTS,此JVM選項是用來設定記憶體大小的。比如:HADOOP_NAMENODE_OPTS=-Xmx2000m。那麼就是給namenode分配了2000MB的空間。

     如果改變了namenode的記憶體大小,那麼secondarynamenode的記憶體的大小同樣也要改變,其選項是HADOOP_SECONDARYNAMENODE_OPTS。

  (2)虛擬機器記憶體分配策略

      Memory Overcommit的意思是作業系統承諾給程序的記憶體大小超過了實際可用的記憶體。一個保守的作業系統不會允許memory overcommit,有多少就分配多少,再申請就沒有了,這其實有些浪費記憶體,因為程序實際使用到的記憶體往往比申請的記憶體要少,比如某個程序malloc()了200MB記憶體,但實際上只用到了100MB,按照UNIX/Linux的演算法,實體記憶體頁的分配發生在使用的瞬間,而不是在申請的瞬間,也就是說未用到的100MB記憶體根本就沒有分配,這100MB記憶體就閒置了。下面這個概念很重要,是理解memory overcommit的關鍵:
     commit(或overcommit)針對的是記憶體申請,記憶體申請不等於記憶體分配,記憶體只在實際用到的時候才分配。

      Linux是允許memory overcommit的,只要你來申請記憶體我就給你,寄希望於程序實際上用不到那麼多記憶體,但萬一用到那麼多了呢?那就會發生類似“銀行擠兌”的危機,現金(記憶體)不足了。Linux設計了一個OOM killer機制(OOM = out-of-memory)來處理這種危機:挑選一個程序出來殺死,以騰出部分記憶體,如果還不夠就繼續殺…也可通過設定核心引數 vm.panic_on_oom 使得發生OOM時自動重啟系統。這都是有風險的機制,重啟有可能造成業務中斷,殺死程序也有可能導致業務中斷,我自己的這個小網站就碰到過這種問題,參見前文。所以Linux 2.6之後允許通過核心引數 vm.overcommit_memory 禁止memory overcommit。

     核心引數 vm.overcommit_memory 接受三種取值:

0 – Heuristic overcommit handling. 這是預設值,它允許overcommit,但過於明目張膽
的 overcommit會被拒絕,比如 malloc一次性申請的記憶體大小就超過了系統總記憶體。Heuristic的意思是“試探
式的”,核心利用某種演算法(對該演算法的詳細解釋請看文末)猜測你的記憶體申請是否合理,它認為不合理就會拒絕
overcommit。
1 – Always overcommit. 允許overcommit,對記憶體申請來者不拒。
2 – Don’t overcommit. 禁止overcommit。
關於禁止overcommit (vm.overcommit_memory=2) ,需要知道的是,怎樣才算是overcommit呢?kernel
設有一個閾值,申請的記憶體總數超過這個閾值就算overcommit,在/proc/meminfo中可以看到這個閾值的大
小:
     # grep -i commit /proc/meminfo
     # grep的-i選項:忽略字元大小寫的差別。
     CommitLimit:     5967744 kB
     Committed_AS:    5363236 kB

     CommitLimit 就是overcommit的閾值,申請的記憶體總數超過CommitLimit的話就算是overcommit。這個閾值是如何計算出來的呢?它既不是實體記憶體的大小,也不是free memory的大小,它是通過核心引數 vm.overcommit_ratio或vm.overcommit_kbytes間接設定的,公式如下:
           CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap

     注:
     vm.overcommit_ratio 是核心引數,預設值是50,表示實體記憶體的50%。可以通過cat /proc/sys/vm/overcommit_ratio檢視。如果你不想使用比率,也可以直接指定記憶體的位元組數大小,通過另一個核心引數 vm.overcommit_kbytes 即可;如果使用了hugepages,那麼需要從實體記憶體中減去,公式變成:
     CommitLimit = ([total RAM] – [total huge TLB RAM]) * vm.overcommit_ratio / 100 + swap
     參見https://access.redhat.com/solutions/665023

     /proc/meminfo中的 Committed_AS 表示所有程序已經申請的記憶體總大小,(注意是已經申請的,不是已經分配的),如果Committed_AS 超過 CommitLimit 就表示發生了 overcommit,超出越多表示 overcommit 越嚴重。Committed_AS 的含義換一種說法就是,如果要絕對保證不發生OOM (out of memory) 需要多少實體記憶體。

     當oom-killer發生時,linux會選擇殺死哪些程序 選擇程序的函式是oom_badness函式(在mm/oom_kill.c中),該函式會計算每個程序的點數(0~1000)。 點數越高,這個程序越有可能被殺死。 每個程序的點數跟oom_score_adj有關,而且oom_score_adj可以被設定(-1000最低,1000最高)。
 

修改核心引數的三種方式

有三種方式修改核心引數,但要有root許可權:
 (1)永久有效:編輯/etc/sysctl.conf ,改vm.overcommit_memory=1,然後sysctl -p使配置檔案生效
 (2)暫時有效:sysctl vm.overcommit_memory=1 注意=前後沒有空格
 (3)暫時有效echo 1 > /proc/sys/vm/overcommit_memory。注意1後面有空格,沒有空格會導致修改失敗

(3)/proc講解

      proc是Linux系統下一個很重要的目錄。它跟/etc, /home等這些系統目錄不同,它不是一個真正的檔案系統,而是一個虛擬的檔案系統。它不存在於磁碟,而是存在於系統記憶體中,難怪資料夾的名字叫做proc程序。所以當你使用 ls -al /proc這條命令來檢視proc目錄時,會看到其下面的所有檔案的大小都為0位元組。 proc以檔案系統的方式為訪問系統核心的操作提供介面。很多系統的資訊,如記憶體使用情況, cpu使用情況,程序資訊等等這些資訊,都可以通過檢視/proc下的對應檔案來獲得。proc檔案系統是動態從系統核心讀出所需資訊的。

/proc 目錄下的檔案很多,舉幾個例子
檢視proc目錄下的檔案的內容,如   #cat /proc/devices
/proc/cpuinifo CPU的資訊(型號、家族、快取大小等)
/proc/meminfo實體記憶體、交換空間
/proc/mounts      已載入的檔案系統的列表
/proc/devices 可用裝置的列表,這個檔案列出字元和塊裝置的主裝置號,以及分配到這些裝置號的裝置

(4)區別

     sudo : 暫時切換到超級使用者模式以執行超級使用者許可權,提示輸入密碼時該密碼為當前使用者的密碼,而不是超級賬戶的密碼。不過有時間限制,Ubuntu預設為一次時長15分鐘。
     su : 切換到某某使用者模式,提示輸入密碼時該密碼為切換後賬戶的密碼,用法為“su - 賬戶名稱”,-後有空格,如果後面不加賬戶時系統預設為root賬戶,密碼也為超級賬戶的密碼。沒有時間限制。
     sudo -i: 為了頻繁的執行某些只有超級使用者才能執行的許可權,而不用每次輸入密碼,可以使用該命令。提示輸入密碼時該密碼為當前賬戶的密碼。沒有時間限制。執行該命令後提示符變為“#”而不是“$”。想退回普通賬戶時可以執行“exit”或“logout” 。

     Linux的#和$區別
       【#】代表 root許可權
       【$】代表普通使用者
     如果更改了/etc/profile , 或~/.bashrc等文件,可以用任何符號來代替它們。linux視窗下的【[email protected]~】其中的【~】代表代表使用者的家目錄(root為/root,一般user則為/home/username);【./】和【.】代表當前目錄;【../】代表上級目錄