【第169期 October 5, 2011】
 

研發新視界

論令人困擾的Out Of Memory問題

作者/莊育明

[發表日期:2011/10/3]


前言

程式運作方面的效能不外乎執行速率、資料準確度以及程式穩定性等等,而就程式穩定性方面而言,多半著重於可用記憶體方面的狀態。程式裡的一些小地方若未注意到,或程式寫法未能因應處理資料量,則會出現令人困擾的Out Of Memory問題,乃至於部分程式無法正常運作/整支服務停止運作,以下為相關處理經驗。

Return之前

以Java開發為例,可採用wrapper的方式定義作業系統在程式初始時期配置給Java的記憶體空間 (可用wrapper.java.initmemory設定)、以及作業系統最多能配置給Java使用的記憶體空間 (可用wrapper.java.maxmemory設定)。每當你/妳新增一條thread (執行緒)要進行專案相關程式之運算時,該thread便被配置一些記憶體空間。但若遇到隨時間遞增的thread數時,則會使得總thread所使用記憶體空間已經到達作業系統所配置給整個專案之記憶體空間。當再有新增thread需求時,則會引發OutOfMemoryError,如下圖所示:


《圖一》


採用Thread.currentThread().getThreadGroup().activeCount()來抓整個專案所用thread數時發現,同樣的程式在一些環境下,是不會出現此問題的;有些環境則會有thread不斷地增加之狀況;有些環境下,專案總thread數量則只有在特定時段遞增。原因究竟為何?

以自身專案為例,該專案之特定程式於每個thread裡會對網路設備發出SNMP query之需求,當SNMP timeout時,我們會判定對方設備未能連通,進行完相關動作後,不待此特定程式執行完,便先return掉。後來發現,問題就出在於return之前,未能注意到將該thread裡所宣告的SNMP物件設為null,以利Java本身的Garbage Collection (垃圾收集)機制進行處理。

例如: 當數名消費者在逛大賣場時,因故將手推車留置在賣場當中,會造成之後欲入場的消費者無手推車可用。若手推車 (無論車上有無商品)沒有被標示無人使用,便要待賣場人員確認此手推車可讓他人使用後,才會將手推車移往出入口處,這期間便需取決於賣場人員的巡查頻率以及自身判斷。

Java本身的Garbage Collection機制會不定時地將程式中執行過後,已不使用到的資源釋放出來,用System.gc()或System.runFinalization()函式並未能有效地立即進行專案相關程式的Garbage Collection。上述所提之問題成因源自於Java本身的Garbage Collection機制並不清楚我們的自定物件是否使用中,而未釋放相關資源。又依環境不同,會使得之後總在新增thread,有一定比例的thread未被回收。 (總thread數一直增加的原因,是該環境有設備不通,固定時間間隔下的SNMP query皆產生timeout情況,進而要進行上述的return、特定時段遞增則是因為該環境有某些設備於下班時刻關機,造成SNMP query不通)。

可用記憶體遞減

除了thread數遞增之外;還有另外一種thread數呈現穩定,卻同樣會引發Out Of Memory問題的情況 (java.lang.OutOfMemoryError: Java heap space),此時就需使用Java內建擷取Free Memory的函數來協助觀測,例如:


《圖二》


經觀察發現,雖然專案的總thread數呈現穩定,但Free Memory部分有隨時間遞減的情況產生。會造成可用記憶體遞減之可能成因有: 未適時將資源釋放出來、程式無法適時將資料處理完成等。個人兩度遇上後者,狀況1: 單一thread處理大量資料,狀況2: 未能在排程中的各回合內將應完成項目完成。

當以單一thread從數分鐘處理一個封包,到一分鐘處理多個封包,乃至於需要一秒鐘處理數個封包時不僅會有資料處理不及的問題 (e.g. A時刻仍在處理數分鐘甚至數小時前的封包),更嚴重地會造成程式效能上的嚴重問題—上述所提的”可用記憶體遞減”。

此情形下,需帶入ThreadPool的觀念,以Multi-Thread來處理此問題,如下:


《圖三》


而上述所提到的情況2,指的是我們若採ScheduledFuture的方式,以排程 (類似回合)的方式進行所要完成的工作。如下:


《圖四》


其0L指的是首度執行Work(),需延遲多久時間,此例為0毫秒。Interval參數代表的是之後每一輪間隔時間。

若Interval設定值與實際能處理完一輪所需時間出現較大差異時,則會開始對於Free Memory造成影響。例如,環境A有50台設備,對環境A做完一輪需2分鐘;環境B有500台設備,對環境B做完一輪需8分鐘,若將Interval同樣都設為1分鐘的話,對A來說,每一輪或許只有之前幾台未處理完;對B來說,處理速率遠低於待處理項目的產生,而那些待處理項目則會持續佔用記憶體空間。故相關的Interval參數便需要設計成變數,因環境現況進行調整。

結論

解決問題的”眉眉角角”常藏在細微之處。當遇上Out Of Memory問題時,一開始可先試著配置多一些記憶體空間供程式使用;若發現這並非治本的方法後,需立刻準備重新審視相關程式。先獨立測試專案各功能,嘗試找出問題之所在;但若現況不允許此類測試方法的話,需重複進行code review的動作,以期找出相關資訊,進而推測出可能的原因。