Memory Management
TinyCLR has two kinds of memory:
- RAM — fast, volatile working memory. Cleared on power loss. Managed by the garbage collector.
- Flash — non-volatile program and resource storage. Survives power loss. Written occasionally, read like RAM.
This page covers managing both, plus the patterns for cleaning up hardware resources, large allocations for native interop, and special-purpose memory regions.
RAM and the garbage collector
RAM is your application's main workspace. TinyCLR is a managed runtime: you allocate objects, and the garbage collector (GC) automatically frees them when no live references remain. The GC typically runs when an allocation fails for lack of free space, so a healthy program with predictable allocation patterns hardly triggers it at all.
Running out of RAM crashes the application. Plan buffer sizes carefully — for example, when sizing a UART receive buffer, pick something large enough to handle real-world bursts but not so large that it wastes memory you'll need elsewhere.
Checking free and used RAM
Useful for debugging memory leaks or tuning allocation patterns:
using GHIElectronics.TinyCLR.Native;
var freeRam = Memory.ManagedMemory.FreeBytes;
var usedRam = Memory.ManagedMemory.UsedBytes;
For a fully-resolved count after a forced collection, use the standard .NET API:
var total = System.GC.GetTotalMemory(forceFullCollection: true);
Enabling GC trace output (helpful for diagnosing GC churn):
System.Diagnostics.Debug.EnableGCMessages(true);
Allocation efficiency
Allocating objects is cheap individually, but allocating in a hot loop creates GC pressure — the runtime spends time collecting garbage it didn't need to produce. Hoist allocations out of loops whenever the buffer can be reused:
// Inefficient — creates 10,000 single-element arrays.
for (int i = 0; i < 10000; i++) {
var buffer = new byte[1];
buffer[0] = source[i];
socket.Send(buffer, 0, 1, SocketFlags.None);
}
// Efficient — one allocation, reused 10,000 times.
var buffer = new byte[1];
for (int i = 0; i < 10000; i++) {
buffer[0] = source[i];
socket.Send(buffer, 0, 1, SocketFlags.None);
}
String allocations
Strings are immutable — every concatenation (a + b) creates a new string and the old one becomes garbage. In a tight loop, this churns through RAM. Use StringBuilder to build strings incrementally without producing intermediates.
Forcing collection
The GC does some work during idle time (running finalizers, compacting the heap). A tight allocation loop leaves no idle slots — if you need to know finalization is complete before continuing, force it:
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
Use sparingly. The GC is already good at scheduling itself; forcing it interrupts the application thread.
Hardware resource cleanup
Some objects represent physical hardware, not just memory — a GPIO pin, a SPI device, a network socket. The GC will eventually reclaim the managed wrapper, but it has no idea what should happen to the underlying hardware at that point. The pin might revert to its default state, mid-transfer DMA might be left dangling, etc. You need to manage the lifetime of these objects manually.
The example below opens an LED pin in a method, then exits without releasing it. The next attempt to open the same pin throws because it's still held by the orphaned reference:
void Main() {
BadExample();
// Throws — PE11 is already open.
var led = GpioController.GetDefault().OpenPin(SC20100.GpioPin.PE11);
led.SetDriveMode(GpioPinDriveMode.Output);
led.Write(GpioPinValue.Low);
}
void BadExample() {
var led = GpioController.GetDefault().OpenPin(SC20100.GpioPin.PE11);
led.SetDriveMode(GpioPinDriveMode.Output);
led.Write(GpioPinValue.High);
// No Dispose — the pin stays "open" forever.
}
Calling .Dispose() (or wrapping in a using block) releases the pin so it can be opened again:
void OkExample() {
using (var led = GpioController.GetDefault().OpenPin(SC20100.GpioPin.PE11)) {
led.SetDriveMode(GpioPinDriveMode.Output);
led.Write(GpioPinValue.High);
}
// Pin released — but its state on the wire after Dispose is hardware-specific.
}
For pins (and other hardware) you'll use repeatedly across methods, the cleanest pattern is to open once at startup, store as a class field, and reuse:
GpioPin led; // Accessible from anywhere in Program.
led = GpioController.GetDefault().OpenPin(SC20100.GpioPin.PE11);
led.SetDriveMode(GpioPinDriveMode.Output);
UseLed();
led.Write(GpioPinValue.Low);
void UseLed(){
led.Write(GpioPinValue.High);
}
Pinned memory for native interop
The GC may move objects in RAM to compact the heap — fine for managed code, but a problem for native interop. If a peripheral DMA is writing into a buffer and the GC moves that buffer mid-transfer, the DMA writes into the wrong address.
For buffers shared with native code (DMA, interrupts, hardware peripherals), allocate at a fixed address that the GC won't move:
using GHIElectronics.TinyCLR.Native;
var fixedBuffer = Memory.ManagedMemory.Allocate(1024);
// Use the buffer with native code...
Memory.ManagedMemory.Free(fixedBuffer);
Fixed-address memory is not managed. You must call Memory.ManagedMemory.Free(...) when done or the memory leaks for the lifetime of the application.
Flash memory
Flash holds the program code and any resources you've embedded. It's read at runtime just like RAM but has special requirements for writing. When you deploy, Visual Studio's deployment window prints what was loaded and how much flash remains free:
Assemblies deployed. There are 2,408,408 bytes left in the deployment area.
You normally don't write to flash at runtime — the application will function even with zero free flash remaining.
For runtime writes to persistent storage, see Secure Storage.
For embedding fonts, images, certificates, and other binary blobs into the flash deployment, see Resources.
Other memory types
- Battery-backed RAM — SITCore devices include 4 KB of RAM that survives power-off when a backup battery is connected via the VBAT pin. Documented on the Real-Time Clock page since the RTC uses it.
- Unmanaged heap — for very large buffers (graphics frames, audio) where you're willing to manage memory manually to avoid GC overhead.
- External memory — using the external SDRAM on devices that have it, with the security implications spelled out.
API reference
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Native | Memory statistics and management classes |