「盡量不要在會無限循環的部分建立新的對象」
錯誤模式:
// 遊戲主迴圈 - 每幀 20-30 次
public void run() {
while (running) {
Enemy e = new Enemy(); // 每幀產生大量垃圾!
Bullet b = new Bullet();
Particle p = new Particle();
// ...
}
}
正確模式:物件池
class ObjectPool {
private Vector free = new Vector();
private Class clazz;
public ObjectPool(Class clazz, int initialSize) {
this.clazz = clazz;
for (int i = 0; i < initialSize; i++) {
try { free.addElement(clazz.newInstance()); }
catch (Exception ex) { ex.printStackTrace(); }
}
}
public synchronized T acquire() {
if (free.size() > 0) return (T)free.removeElementAt(free.size()-1);
try { return clazz.newInstance(); } // 池耗盡動態擴充
catch (Exception ex) { return null; }
}
public synchronized void release(T obj) {
resetObject(obj); // 重置狀態
free.addElement(obj);
}
}
// 使用
ObjectPool bulletPool = new ObjectPool<>(Bullet.class, 50);
// 遊戲迴圈
Bullet b = bulletPool.acquire();
if (b != null) { b.init(x, y, dir); activeBullets.addElement(b); }
// 子彈銷毀時
bulletPool.release(b);
activeBullets.removeElement(b);
應用場景:子彈、敵機、粒子、特效、路徑節點、網路封包物件等高頻創建/銷毀類別。
「另外要避免製作內存空洞」
現象示意:
// Heap 佈局演變
[Obj A: 200B][Obj B: 150B][Obj C: 300B] // 初始狀態
[FREE:200B][Obj B: 150B][Obj C: 300B] // 釋放 A
[Obj D: 120B][FREE:80B][Obj B: 150B][Obj C: 300B] // 配置 D (120B < 200B) → 產生 80B 碎片
[Obj D: 120B][Obj E: 80B][Obj B: 150B][Obj C: 300B] // 配置 E 恰好填補
[Obj D: 120B][FREE:80B][Obj B: 150B][Obj C: 300B] // 釋放 E → 碎片再現
大量不同大小物件頻繁分配/釋放,會產生大量無法利用的微小縫隙,導致 OutOfMemoryError 即使總可用記憶體充足。
| 策略 | 實施要點 | 適用場景 |
|---|---|---|
| 固定大小物件池 | 同類物件統一大小,池化複用,零碎片 | 子彈、粒子、敵人、瓦片 |
| 分級分配器 | 按 2^n 位元組對齊分配 (16, 32, 64, 128...),減少外部碎片 | 通用緩衝區、網路封包 |
| 階段性整理 | 場景切換/關卡結束時,強制 GC (System.gc()) 並重建長期存活物件 | 關卡轉換、大型資源卸載後 |
new 操作反模式:進入戰鬥 new BattleEngine()、new Monster[10]、new SkillEffect[]... 戰鬥中每幀 new DamageNumber()。
正規軍模式:
J2ME 記憶體管理的本質是確定性資源控制。物件池消除分配開銷與碎片,階段性建立/釋放匹配遊戲狀態機,這套方法論在現代 Unity (Object Pool), Unreal (Object Pool), Android (RecyclerView ViewHolder) 中依然是核心效能優化手段。