早期功能機堆疊記憶體僅 200KB~500KB,單一 MIDlet 可用往往不足 200KB。OutOfMemoryError 是開發者最常見的噩夢。本文從代碼習慣、資源管理、架構設計三個維度,給出經過實戰驗證的優化策略。
// ❌ 錯誤:新舊物件短暫共存,峰值記憶體雙倍
Image img = Image.createImage("/new.png");
img = Image.createImage("/another.png"); // 舊 img 變垃圾,但未即時回收
// ✅ 正確:先釋放再分配
img = null; // 明確斷開引用
System.gc(); // 建議觸發(非強制)
img = Image.createImage("/another.png");
原理:J2ME GC 非即時、非世代、非並行。手動置空 + System.gc() 能最大程度降低峰值記憶體。
// ❌ 危險:new 瞬間舊物件仍存活
obj = new HeavyObject();
// ✅ 安全:三步驟
obj = null;
System.gc();
obj = new HeavyObject();
// 關鍵點插入監控
long free = Runtime.getRuntime().freeMemory();
System.out.println("Free mem: " + free + " bytes");
// 輸出範例:Free mem: 184320 bytes
在場景切換、大圖載入前後、遊戲關卡跳轉處埋點,建立記憶體基線與峰值曲線。
// ❌ 大量字串拼接產生大量臨時物件
String s = "";
for (int i = 0; i < 100; i++) s += "item" + i;
// ✅ 使用 StringBuffer(非執行緒安全,效能優於 StringBuilder)
StringBuffer sb = new StringBuffer(500);
for (int i = 0; i < 100; i++) sb.append("item").append(i);
String result = sb.toString();
Image.getWidth/Height() 檢查後再決定是否縮放// ❌ 啟動一次性載入所有圖 → OOM
Image[] allFrames = new Image[50];
for (int i=0;i<50;i++) allFrames[i]=Image.createImage("/f"+i+".png");
// ✅ 分組延遲載入 + 釋放
// 只載入當前關卡/狀態需要的圖
// 切換關卡前:oldFrames[i]=null; System.gc(); 再載新圖
| 格式 | 適用場景 | 體積/秒 | 解碼記憶體 |
|---|---|---|---|
| MIDI (SP-MIDI) | BGM、長背景音樂 | ~1 KB | 極低 |
| AMR-NB | 語音、旁白 | ~1.5 KB | 低 |
| WAV/PCM | 短音效(<1s) | ~8-16 KB | 中 |
| ToneControl 序列 | 極簡提示音、零資源檔 | 0 KB | 極低 |
| 檢查點 | 工具/方法 | 合格線 |
|---|---|---|
| 啟動峰值記憶體 | WTK Memory Monitor / Runtime.freeMemory() | < 60% 可用堆疊 |
| 場景切換無殘留 | 連續切換 20 次觀察 freeMemory 曲線 | 曲線穩定回落,無階梯狀上升 |
| JAR 體積 | WTK Package / ProGuard 混淆 | 目標裝置限制內(如 S40 64KB) |
| 圖片總大小 | 資源目錄統計 | 運行時載入集合 < 100KB |
J2ME 記憶體優化沒有銀彈,只有「處處留心皆優化」的工程紀律:代碼層手動置空、資源層極致壓縮、架構層嚴格生命週期與物件池。這三招合併施展,能在 200KB 堆疊裡跑出商業級遊戲。這份在極限中求生存的經驗,正是移動端效能工程師最寶貴的底色。