**黃金法則**:在嘗試分配大塊記憶體 (圖片解碼、網路緩衝、大型數組) **之前**,主動檢查可用記憶體,不足時優雅降級或提示用戶,而非讓 JVM 暴力崩潰。
// 1. 建議 GC 釋放無用物件 (僅建議,不保證)
System.gc();
// 2. 取得當前可用堆疊記憶體 (bytes)
long freeMemory = Runtime.getRuntime().freeMemory();
// 3. 定義操作所需記憶體閾值 (含安全邊際)
int requiredMemory = 102400; // 例如:解碼 100KB 圖片需 100KB 緩衝區
// 4. 安全邊際:要求可用記憶體 >= 所需 * 1.5 倍 (應對 GC 未跑完、碎片化等)
if (freeMemory < requiredMemory * 3 / 2) {
// 記憶體不足:優雅處理
showAlert("記憶體不足", "無法完成操作,請關閉其他應用後重試");
return; // 放棄操作,保持 MIDlet 存活
}
// 5. 記憶體充足:安全分配
byte[] buffer = new byte[requiredMemory];
// 繼續操作:下載圖片、解碼、處理...
public class MemoryGuard {
private static final float SAFETY_FACTOR = 1.5f; // 50% 安全邊際
/** 檢查記憶體是否足夠執行操作
* @param requiredBytes 操作所需位元組數
* @return true=可安全分配, false=記憶體不足
*/
public static boolean checkMemory(int requiredBytes) {
System.gc(); // 建議 GC
long free = Runtime.getRuntime().freeMemory();
long needed = (long) (requiredBytes * SAFETY_FACTOR);
return free >= needed;
}
/** 執行需大量記憶體的操作,自動處理不足情況
* @param requiredBytes 所需記憶體
* @param action 實際操作 (Runnable 匿名類)
* @param alertMsg 不足時提示訊息
* @return true=操作已啟動, false=記憶體不足已提示
*/
public static boolean executeWithMemoryCheck(int requiredBytes,
Runnable action,
String alertMsg) {
if (checkMemory(requiredBytes)) {
action.run();
return true;
} else {
showAlert("記憶體不足", alertMsg);
return false;
}
}
// 簡易 Alert 顯示 (需在 MIDlet/Displayable 環境中實作)
private static void showAlert(String title, String msg) {
// 實作依賴具體 UI 架構 (Canvas/GameCanvas 或 High-level UI)
// 例如: new Alert(title, msg, null, AlertType.WARNING).show();
}
}
// 下載 50KB 圖片,解碼需約 150KB 緩衝區 (原始資料 + 解碼後像素陣列)
final int IMAGE_BUFFER_SIZE = 150 * 1024;
MemoryGuard.executeWithMemoryCheck(IMAGE_BUFFER_SIZE, new Runnable() {
public void run() {
try {
HttpConnection conn = (HttpConnection) Connector.open(imageUrl);
InputStream is = conn.openInputStream();
// 讀取圖片資料
byte[] imageData = new byte[IMAGE_BUFFER_SIZE];
int totalRead = 0, read;
while ((read = is.read(imageData, totalRead, imageData.length - totalRead)) > 0) {
totalRead += read;
}
is.close();
conn.close();
// 解碼建立 Image (需在事件執行緒或獨立執行緒)
final Image img = Image.createImage(imageData, 0, totalRead);
// UI 更新需在事件執行緒
Display.getDisplay(midlet).callSerially(new Runnable() {
public void run() {
currentScreen.showImage(img);
}
});
} catch (IOException e) {
showAlert("下載失敗", "網路錯誤: " + e.getMessage());
}
}
}, "圖片太大,記憶體不足無法顯示");
當單次操作所需記憶體**超過總堆疊上限**時 (如下載 500KB 圖片但堆疊僅 300KB),必須**分塊流式處理**:
// 邊下載邊寫入 RMS/檔案系統,不一次性載入記憶體
HttpConnection conn = (HttpConnection) Connector.open(url);
InputStream is = conn.openInputStream();
RecordStore rs = RecordStore.openRecordStore("img_cache", true);
byte[] chunk = new byte[4096]; // 4KB 緩衝區
int read;
while ((read = is.read(chunk)) > 0) {
rs.addRecord(chunk, 0, read); // 直接寫入持久化存儲
}
is.close();
conn.close();
rs.closeRecordStore();
// 後續從 RMS 分塊讀取解碼 (需平台支援流式解碼)
// 或分段載入、拼接顯示 (大圖分塊渲染)
J2ME 開發中,**`OutOfMemoryError` 不是 Bug,是必然會發生的環境事實**。成熟開發者的標誌不是「不發生 OOM」,而是:**每一處大記憶體分配前都有檢查、每一處檢查失敗都有優雅降級、每一個資源釋放點都經過驗證**。將 `MemoryGuard` 納入專案基礎庫,強制團隊遵守「先檢查後分配」紀律,是從「能跑」邁向「商用級穩定」的關鍵一步。