避免Java堆空間錯誤的5個步驟

ImportNew - 光光頭去打醬油發表於2014-12-12

牢記以下五個步驟可以為你減少很多頭痛的問題並且避免Java堆空間錯誤。

  1. 通過計算預期的記憶體消耗。
  2. 檢查JVM是否有足夠的可用空間。
  3. 檢查JVM的設定是否正確。
  4. 限制節點使用交換空間和記憶體分頁。
  5. 設定例項slot數量小於JobTracker web GUI計算的數值。

譯者注:slot :slot不是CPU的Core,也不是memory chip,它是一個邏輯概念,一個節點的slot的數量用來表示某個節點的資源的容量或者說是能力的大小,因而slot是 Hadoop的資源單位。詳見這裡

在這篇博文裡,我將詳細講解每個步驟,幫助大家更好地理解並正確管理例項(task attempt)記憶體。

譯者注:例項(task attempt) :這個詞在官方文件中找到了解釋: “Each task attempt is one particular instance of a Map or Reduce Task identified by its TaskID”。

理解怎樣管理例項記憶體是很重要的,這樣可以避免Java堆空間錯誤。當執行 map/reduce 作業(Job)時,你可能會看到例項出現這樣的錯誤:

13/09/20 08:50:56 INFO mapred.JobClient: Task Id : attempt_201309200652_0003_m_000000_0, Status : FAILED on node node1
Error: Java heap space

當試圖申請一個超過Java虛擬機器(JVM)設定的最大記憶體限制時就會發生這個錯誤。

避免Java堆空間錯誤的第一步是瞭解你的map和reduce任務的記憶體需求,以便於你啟動一個JVM時設定了適當記憶體限制。

例如,hadoop-0.20.2-dev-examples.jar中的wordcount 功能。 不管處理什麼資料,map 任務都不需要很多記憶體。唯一需要很多記憶體的就是在載入執行所需的函式庫的時候。當使用預設附帶MapR包的wordcount功能時,512MB的記憶體對於例項JVM是綽綽有餘了。如果你打算執行我們提供的Hadoop示例,可以嘗試將map例項JVM的記憶體限制設為512MB。

如果你知道自己的map例項需要多少記憶體(在本例中是512MB), 那麼下一步啟動設定好JVM記憶體。該例項在JVM中的記憶體是由TaskTracker為Map/Reduce作業處理資料而設定的。 TaskTracker設定的限制可能有兩個來源:要麼是使用者提交作業時指定了記憶體大小作為該作業配置物件的一部分,或者是TaskTracker產生了 預設記憶體大小的JVM。

mapred.map.child.java.opts屬性被用來為TaskTracker 啟動JVM和執行map任務的引數(在reduce任務中也有個類似的屬性)。如果mapred.map.child.java.opts屬性被設定成“-Xmx512m”,那麼map例項JVMs會有512MB的記憶體限制。相反的,如果-Xmx沒有通過配置屬性去指定一個數值的話,那麼 每個TaskTracker將會為啟動JVM計算一個預設的記憶體限制。該限制是基於TaskTracker為map/reduce task slot分配的數量所決定的,並且TaskTracker分配給Map/Reduce總記憶體不能超過系統限制。

TaskTracker為map/reduce例項分配的slot數量在TaskTracker啟動時就設定好了。通過每個節點上mapred-site.xml檔案中兩個引數進行控制的:

mapred.tasktracker.map.tasks.maximum
mapred.tasktracker.reduce.tasks.maximum

設定這些預設值的規則是基於節點上CPU核心的數量。不過你可以下面兩個方法來過載引數:

  1. 修改mapred-site.xml檔案設定一個固定的slots數值。
  2. 使用自定義規則。

在系統中,TaskTracker map/reduce例項記憶體限制是在TaskTracker程式啟動時設定的。有兩個地方可以設定記憶體限制。首先在Hadoop conf目錄下的hadoop-env.sh指令碼中可以顯式的設定,你可以新增下面這行來指定記憶體限制:

export HADOOP_HEAPSIZE=2000

這行命令限制了節點上的所有例項JVM總共可以使用2000MB的記憶體。如果沒有在hadoop-env.sh檔案中指定 HADOOP_HEAPSIZE這個引數,那麼當MapR warden service啟動TaskTracker時會對記憶體進行限制。 warden service會基於節點上實體記憶體的數量減去服務執行中已經佔用的記憶體數量得出限制的大小。如果你去看看warden.conf你會看到像這樣的一些屬性:

service.command.mfs.heapsize.percent=20
service.command.mfs.heapsize.min=512

這個例子表示,warden佔用分配給MFS服務節點的20%實體記憶體或最低512MB(512MB<20%的實體記憶體的情況下)。如果你考慮所有服務都配置在一個節點上執行的話,你要考慮下在 warden.conf中指定下記憶體分配。你應該能明確多少記憶體用於服務配置(還要為系統正常執行預留記憶體)。剩下的記憶體就是TaskTracker為併發執行例項設定的記憶體限制了。

例如,假設你在一個節點上安裝執行ZooKeeper、CLDB、MFS、JobTracker、TaskTracker、NFS、the GUI、HBase Master 和HBase RegionServer。這麼多的服務執行在一個節點上,而且每個服務都需要記憶體,所以warden會將記憶體按照百分比分配給每個服務,剩下的將會分配 給節點上的map/reduce 例項。如果你分配給這些服務總共60%還有5%為系統預留,那麼就還有35%分給節點上的map/reduce例項。如果這個節點有10G的記憶體,將會有3.5G分給 map/reduce 任務。如果你有 6個map slot和4個reduce slot。如果記憶體是平均分配的,最終每個JVM的記憶體限制為350MB。如果你需要512MB記憶體來執行你的map任務,那麼預設設定的情況下是不會執行的,你會遇到Java堆空間錯誤。

當管理例項記憶體的時候會意識到還有其它問題。不要強制節點去使用大量的交換空間(swap space)或者觸發頻繁記憶體分頁讀寫磁碟。如果你通過顯式的在mapred.map.child.java.opts設定“-Xmx500m”來改變提交的作業,將會重寫安全的記憶體限制。但實際上你並沒有額外的實體記憶體。雖然 map/reduce 例項仍能啟動,但是會強制使用大量的交換空間,而且無法依賴核心的OOM killer或者其他的方法來防止這種情況發生。如果真的發生這種情況,無法指望節點啟動大量分頁來迅速恢復。如果只是增加了例項的JVM記憶體,同時繼續在節點上啟動相同數量的例項。你會申請更多的記憶體,需要注意不要超額申請。如果超額申請太多的話,會導致大量的分頁,這樣節點可能會被掛 起再也無法恢復。除非重啟電源。

所以如果你給每個例項JVM增加記憶體的話,需要通過TaskTrackers來減少分配給map/reduce task slot數量。

這是一個很複雜的情況,因為如果你在叢集上併發執行不同的作業,可能來自一個作業(JobA)的例項需要大量的記憶體,來自另外一個作業(JobB)的例項只需要很少的記憶體。因此,如果你減少map/reduce slot的數量,會發現會有足夠的記憶體來執行來自JobB任務(task)。但是卻沒有足夠的記憶體提供給JobA。所以關鍵就是找到一個平衡點,一個可以允許進行一些超額申請卻不會導致節點被掛起的平衡點。

為了協助這個任務,TaskTracker 將會著眼於當前所有在執行的 map/reduce tasks 所使用的記憶體數量。不是隻看這些任務的最大記憶體限制,而是所有執行中的例項實際利用的記憶體總數。當消耗的記憶體達到一定級別,TaskTracker 會殺死一些執行的例項來釋放記憶體,以便其他的例項能正常執行完並且不會造成節點上的分頁過多。

舉個例子,如果你想在一個小型的叢集或者單一節點上執行wordcount示例,碰到“Java堆空間”錯誤,最簡單最快的解決方法就是通過編輯/opt/mapr/hadoop/hadoop-0.20.2/conf/mapred-site.xml中的設定來減少 map/reduce 例項 slot的數量:

mapred.tasktracker.map.tasks.maximum 
mapred.tasktracker.reduce.tasks.maximum

將例項的slot的數量設定為小於當前計算結果是非常重要的。當前計算的數量可以通過進入JobTracker web介面來確定。例如,如果你有一個TaskTracker ,顯示它有6個mpa slot和4個 reduce slot,那麼你應該設定 3個map slot、2個 reduce slot。然後通過下面這行命令重啟節點上的TaskTracker程式:

maprcli node services -nodes -tasktracker restart

減少slot的數量重新啟動後,重新提交wordcount作業。如果沒有額外記憶體申請,每個例項、JVM都會分配到更多的記憶體。這是一個安全的解決方法,節點不會產生大量分頁。這是一種簡單的解決方案,不需要大量計算記憶體。這也是快速的方法,只需要編輯下配置檔案並重啟下服務就好了。

為了避免Java堆空間錯誤,記住下面這些步驟:

  1. 估算你的例項需要消耗多少記憶體。
  2. 確保TaskTracker 啟動你的例項時,JVM記憶體的限制要大於等於你預計的記憶體需求。
  3. 記住,啟動這些JVM是有預設設定的,除非你顯式的重寫過這些設定。在CPU核心數和實體記憶體已經平衡並執行服務的節點上,預設設定並不適用。
  4. 不要迫使節點大量的使用交換空間或者頻繁的將記憶體分頁讀寫到磁碟上。
  5. 將例項slot數量設定為小於JobTracker web GUI計算值。

相關文章