Published on

Memory Model & Object Lifetime on ESP32

Authors

📘 Foundation Article 4: Memory Model & Object Lifetime on ESP32

Track: C++ Firmware Engineering Foundations for ESP32 Environment: ESP32 + VSCode + Arduino Community Edition Project Base: IndustrialNode/IndustrialNode.ino Use Case: Generic Sensor + Relay Node



1️⃣ Struktur Memory pada ESP32 (Gambaran Nyata)

ESP32 + Arduino + FreeRTOS bukan memory tunggal.

Ada 3 domain penting dalam firmware Anda:

  1. Static region (.data, .bss)
  2. Heap global
  3. Stack per task

Yang sering salah:

Engineer hanya melihat:

ESP.getFreeHeap();

Dan mengira itu representasi kesehatan memory.

Itu keliru.


Diagram Konseptual Layout Memory (FreeRTOS Context)

Image

Image

Image

Image

Model realistis:

Flash
├── .text (code)
├── .rodata
DRAM
├── .data (global initialized)
├── .bss  (global zero init)
├── Heap (shared runtime)
├── loopTask Stack
├── WiFi Task Stack
├── TCP/IP Stack
├── Timer Stack
└── IDLE Stack

Kunci:

Heap shared. Stack isolated per task. Static region fixed sejak boot.


2️⃣ Stack per Task — Failure Model Nyata

Setiap task dibuat dengan:

xTaskCreatePinnedToCore(
    function,
    name,
    stack_size,
    ...
);

stack_size adalah batas keras.

Tidak bisa auto-grow.


Failure Model 1 — Local Buffer Besar

Contoh realistis:

void loop()
{
    char jsonBuffer[2048];
    buildJson(jsonBuffer);
    publish(jsonBuffer);
}

Jika stack loopTask = 4096 byte:

  • jsonBuffer 2048
    • call frame
    • Serial internal
    • MQTT internal call chain

Anda sudah di zona bahaya.


Failure Model 2 — Deep Call Chain

loop()
 └── control()
     └── sensor.update()
         └── filter()
             └── compute()

Setiap layer menambah frame.

Stack usage kumulatif.

Engineer sering menganggap stack hanya soal array besar. Padahal call depth juga faktor.


🔬 Stack Audit Mini-Lab (IndustrialNode)

Tambahkan ke loop:

Serial.print("Loop HighWater: ");
Serial.println(uxTaskGetStackHighWaterMark(nullptr));

Tambahkan ke sensorTask juga.

Interpretasi:

  • HighWaterMark = sisa stack minimum yang pernah tersedia.
  • Jika mendekati 0 → pernah hampir overflow.

Rule praktikal:

Target minimal sisa stack 30–40%.

Ini bukan teori. Ini discipline produksi.


Kenapa Stack Overflow Sangat Berbahaya?

Karena:

  • Bisa overwrite TCB (Task Control Block).
  • Bisa merusak scheduler internal.
  • Bisa menyebabkan crash di tempat tidak terkait.
  • Bisa memicu watchdog tanpa jejak jelas.

Dan yang paling berbahaya:

Heap masih terlihat sehat.

Engineer salah arah.


Stack Discipline Foundation Rule

Di tahap Foundation kita kunci:

  1. Tidak ada buffer besar di stack tanpa audit.
  2. Tidak ada recursion.
  3. Tidak ada object besar sebagai local variable.
  4. Semua task memiliki stack size eksplisit dan diaudit.
  5. HighWaterMark dipantau saat development.

3️⃣ Heap Global — Resource yang Diperebutkan

Heap pada ESP32 bukan milik Anda saja.

Ia dipakai oleh:

  • new / delete
  • malloc / free
  • WiFi driver
  • TCP/IP stack
  • TLS
  • MQTT
  • Arduino String
  • JSON library
  • Internal buffer

Dan semuanya hidup paralel dalam sistem multitasking.


Model Mental yang Harus Dikunci

                 Heap (Shared)
 ┌──────────────────────────────────┐
 │ loopTask allocations             │
 │ WiFi internal buffers            │
 │ TCP/IP stack buffers             │
 │ TLS crypto buffers               │
 │ MQTT payload buffers             │
 │ Arduino String                   │
 │ JSON serialization               │
 └──────────────────────────────────┘

Tidak ada “heap saya”. Yang ada hanya “heap bersama”.


Failure Model 1 — Heap Exhaustion

Gejala:

  • malloc return null
  • new return null
  • MQTT publish gagal
  • TLS handshake gagal
  • OTA gagal

Audit sederhana:

Serial.print("Free Heap: ");
Serial.println(ESP.getFreeHeap());

Serial.print("Min Free Heap: ");
Serial.println(ESP.getMinFreeHeap());

MinFreeHeap lebih penting daripada FreeHeap.

Kenapa?

Karena spike memory sering terjadi saat:

  • WiFi reconnect
  • TLS renegotiation
  • OTA start

Dan spike itu bisa hanya terjadi 1 detik. Tetapi fatal.


4️⃣ Fragmentasi — Silent Killer

Fragmentasi bukan soal total memory.

Ia soal layout memory.


Ilustrasi Fragmentasi

Awal:
[########## 10000 bytes ##########]

Setelah banyak alloc/free:
[####][##][#####][#][######][##][###]

Total = 10000 bytes
Blok terbesar mungkin hanya 2000 bytes

Jika TLS butuh 4096 kontigu?

Gagal.

Padahal total heap cukup.


Kenapa Fragmentasi Terjadi?

Karena:

  • Allocation ukuran variatif.
  • Allocation dan free bergantian.
  • Payload MQTT panjang berubah-ubah.
  • JSON size dinamis.
  • String reallocation.
  • vector resize.

Mini-Lab Fragmentasi (IndustrialNode)

Tambahkan fungsi simulasi:

void fragmentationTest()
{
    for (int i = 0; i < 50; ++i)
    {
        String payload = "Sensor value: " + String(i);
    }
}

Panggil tiap 1 detik.

Pantau:

  • FreeHeap
  • MinFreeHeap

Biarkan berjalan 30–60 menit.

Anda akan melihat:

  • FreeHeap terlihat stabil.
  • Tetapi MinFreeHeap turun perlahan.

Itulah tanda fragmentasi.


Kenapa String Berbahaya dalam Jangka Panjang?

String melakukan:

  • Allocation
  • Reallocation
  • Copy
  • Free

Setiap variasi ukuran = potensi fragmentasi.


Pattern Salah (Hot Path Allocation)

void publishValue(int value)
{
    String payload = "{ \"v\": " + String(value) + " }";
    client.publish(topic, payload);
}

Dipanggil tiap 100ms.

Anda baru saja membuat fragmentation generator.


Pattern Lebih Stabil

Gunakan static buffer:

void publishValue(int value)
{
    static char payload[64];
    snprintf(payload, sizeof(payload), "{ \"v\": %d }", value);
    client.publish(topic, payload);
}

Tidak ada heap. Tidak ada fragmentasi. Deterministik.


Foundation Heap Discipline Rules

  1. Hindari allocation di loop utama.
  2. Hindari allocation di ISR (haram total).
  3. Hindari allocation di hot control path.
  4. Gunakan buffer statik untuk payload tetap.
  5. Audit MinFreeHeap saat test.
  6. Hindari String untuk sistem uptime panjang.

🔬 Heap Monitoring Template untuk Development

Tambahkan periodic audit:

void printHeapStats()
{
    Serial.print("FreeHeap: ");
    Serial.print(ESP.getFreeHeap());
    Serial.print(" | MinFreeHeap: ");
    Serial.println(ESP.getMinFreeHeap());
}

Panggil tiap 5–10 detik saat development.

Di production, bisa disimpan ke log remote.


Kapan Heap Boleh Digunakan?

Heap boleh digunakan jika:

  • Allocation sekali saat startup.
  • Object hidup sepanjang sistem.
  • Ukuran besar tidak bisa statik.
  • Library memang memerlukan dynamic memory.

Yang tidak boleh:

  • Allocation berulang dalam loop.
  • Allocation ukuran berubah-ubah.
  • Allocation + free cepat bergantian.

5️⃣ Object Lifetime — Lebih Penting dari Allocation

Allocation hanya menjawab:

Memory diambil dari mana?

Lifetime menjawab:

Kapan object dibuat? Kapan ia mati? Siapa pemiliknya? Di context mana ia hidup?


Failure Model — Lifetime Salah Align

Pattern yang sering terlihat di beginner-intermediate:

void loop()
{
    SensorService sensor;
    sensor.update();
}

Masalah:

  • Constructor dipanggil tiap loop.
  • Destructor dipanggil tiap loop.
  • Stack churn.
  • Potensi allocation internal.
  • Tidak deterministik.
  • Tidak scalable.

Pattern Benar untuk IndustrialNode

Object domain utama:

  • ControlApp
  • SensorService
  • RelayDriver

Harus memiliki:

System lifetime.

Contoh:

SensorService sensor;
RelayDriver relay;
ControlApp app(sensor, relay);

void setup()
{
    app.init();
}

void loop()
{
    app.update();
}

Semua object hidup sepanjang sistem.

Tidak ada churn. Tidak ada fragmentasi berulang. Tidak ada constructor/destructor hot path.


Rule Lifetime Foundation

  1. Object domain utama → static lifetime.
  2. Peripheral driver → static lifetime.
  3. Service layer → static lifetime.
  4. Allocation runtime → minimal dan terkontrol.

Jika object dibuat dan dihancurkan terus:

Itu bukan design embedded. Itu design desktop.


6️⃣ Destructor & Resource Release

Engineer desktop sering mengandalkan RAII.

Di firmware:

  • main() tidak pernah return.
  • Program tidak exit.
  • Global destructor hampir tidak pernah dipanggil.

Jika destructor Anda:

  • Publish MQTT.
  • Unlock mutex.
  • Close network.
  • Free hardware resource.

Anda sedang bergantung pada sesuatu yang mungkin tidak pernah terjadi.


Failure Model — Destructor Heavy Logic

class NetworkClient
{
public:
    ~NetworkClient()
    {
        client.disconnect();  // blocking
    }
};

Jika destruction terjadi dalam kondisi:

  • Heap tidak stabil.
  • Network unstable.
  • Scheduler busy.

Maka destructor bisa:

  • Blocking.
  • Deadlock.
  • Memicu watchdog.

Foundation Rule

Destructor hanya boleh release memory sederhana. Tidak boleh ada blocking. Tidak boleh ada network call. Tidak boleh ada ISR interaction.

Jika butuh shutdown sequence:

Buat fungsi eksplisit:

void shutdown();

Jangan sembunyikan di destructor.


7️⃣ Static vs Dynamic Allocation (Arsitektur IndustrialNode)

Sekarang kita hubungkan langsung ke use case.

IndustrialNode adalah:

  • Sensor read periodik.
  • Relay control deterministik.
  • MQTT publish periodik.

Semua ini predictable.

Artinya:

Dynamic allocation hampir tidak diperlukan.


  • Static object untuk semua service.
  • Static buffer untuk publish.
  • Fixed-size queue.
  • No new/delete di loop.
  • No String di hot path.

Kapan Dynamic Allocation Boleh?

Hanya jika:

  • Allocation sekali saat startup.
  • Resource opsional.
  • Ukuran besar dan jarang.

Contoh yang boleh:

std::unique_ptr<BigBuffer> buffer;

void setup()
{
    buffer = std::make_unique<BigBuffer>();
}

Tapi bukan di loop.


8️⃣ Hidden Allocation di C++

C++ modern memudahkan kita.

Tapi firmware tidak peduli pada keindahan sintaks.

Beberapa fitur yang harus diwaspadai:

  • std::string
  • std::vector
  • std::map
  • std::function
  • lambda capture besar
  • JSON library dinamis

Hidden Allocation Trap

Contoh tampak sederhana:

std::vector<int> values;
values.push_back(10);

vector akan:

  • Allocate heap.
  • Reallocate saat capacity penuh.
  • Copy memory.

Setiap reallocation = fragmentasi potensi.


Foundation Rule

Sebelum pakai fitur C++:

Tanyakan:

  1. Apakah ini allocate heap?
  2. Apakah capacity berubah-ubah?
  3. Apakah ada copy besar?
  4. Apakah ini di hot path?

Jika jawabannya tidak pasti → audit.


9️⃣ TLS Memory Spike — Kasus Nyata Produksi

TLS handshake bukan ringan.

Ia melakukan:

  • Certificate parsing.
  • Key exchange.
  • Crypto buffer allocation.
  • Session buffer allocation.

Heap spike bisa ribuan byte.

Jika heap:

  • Sudah terfragmentasi.
  • Sudah mepet limit.
  • Sudah banyak allocation kecil.

Handshake gagal.

Gejala:

  • MQTT reconnect gagal.
  • OTA gagal.
  • Reset sporadis.
  • Error tidak jelas.

Engineer sering menyalahkan:

  • WiFi unstable.
  • Broker unstable.
  • ISP.

Padahal:

Heap tidak siap untuk spike.


Production Thinking

Sebelum mengaktifkan TLS:

  1. Audit MinFreeHeap saat idle.
  2. Simulasikan reconnect berkali-kali.
  3. Pastikan ada margin aman.
  4. Hindari fragmentation generator.

TLS bukan fitur. Ia adalah stress test memory Anda.


🔟 Mental Model yang Harus Dikunci

Setelah Article 4, engineer harus mampu menjawab:

  • Berapa stack tiap task?
  • Berapa HighWaterMark?
  • Berapa FreeHeap & MinFreeHeap?
  • Apakah ada allocation di loop?
  • Apakah ada String di hot path?
  • Apakah object lifetime align dengan system lifetime?
  • Apakah TLS spike sudah dihitung?
  • Apakah destructor mengandung logic berat?

Jika tidak bisa menjawab ini:

Firmware belum layak produksi.


Penutup — Foundation Memory Discipline

Memory pada ESP32 bukan soal kapasitas.

Ia soal:

  • Layout.
  • Ownership.
  • Lifetime alignment.
  • Fragmentation behavior.
  • Interaction dengan scheduler.
  • Interaction dengan network.
  • Interaction dengan watchdog.

Firmware sensor + relay kecil bisa gagal bukan karena logic salah, tetapi karena lifetime dan allocation tidak disiplin.

Dan inilah alasan:

Artikel berikutnya (OOP for Embedded Firmware) tidak akan berbicara soal “class bagus”.

Ia akan berbicara soal:

Bagaimana OOP membantu atau menghancurkan memory discipline.


Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.