# How Memory Management Works in Nelson: Architecture and Best Practices

> Explore Nelson's deterministic reference counting and copy-on-write memory management. Learn how variables and Data blocks work to automatically free memory. Optimize your Nelson code.

- Repository: [The Nelson Programming Language/nelson](https://github.com/nelson-lang/nelson)
- Tags: internals
- Published: 2026-03-08

---

**Nelson uses a deterministic reference-counting system with copy-on-write semantics, where every variable is an `ArrayOf` object pointing to a shared `Data` block that automatically frees memory when the last reference is removed.**

Memory management in Nelson is implemented through a sophisticated runtime architecture that balances performance with safety. The nelson-lang/nelson repository employs atomic reference counting and shallow copying strategies to minimize overhead while handling large numerical datasets efficiently. Understanding these internal mechanisms helps developers write memory-efficient code and avoid hidden leaks in long-running sessions.

## Core Architecture of Memory Management in Nelson

### Reference-Counted Data Blocks

At the heart of Nelson's memory model lies the `Data` structure defined in [`modules/types/src/include/Data.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/include/Data.hpp). This structure contains the actual raw bytes, dimension information, type classification, and an atomic reference counter named `owners`.

When a `Data` block is created, `owners` initializes to **1**. Each time an `ArrayOf` object copies or references this data, the counter increments via `owners.fetch_add(1, std::memory_order_relaxed)` in [`modules/types/src/cpp/Data.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/cpp/Data.cpp). When an `ArrayOf` is destroyed or reassigned, `owners.fetch_sub(1, std::memory_order_acq_rel)` decrements the count. Upon reaching zero, the destructor automatically releases the allocated buffer.

### The ArrayOf Container

The `ArrayOf` class in [`modules/types/src/include/ArrayOf.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/include/ArrayOf.hpp) serves as the high-level container visible to Nelson programmers. It maintains a pointer to a `Data` block and implements shallow copy semantics by default. This design allows large arrays to be passed between functions or assigned to new variables with minimal overhead, as only the pointer and reference count are copied rather than the underlying bytes.

### Copy-on-Write Semantics

Nelson implements **copy-on-write (COW)** optimization to balance sharing efficiency with data integrity. When multiple `ArrayOf` instances reference the same `Data` block, they share memory safely through the reference count. However, when a mutating operation requires exclusive access to the data, Nelson automatically allocates a new `Data` block, copies the contents, and leaves the original shared block untouched.

This mechanism occurs transparently during operations like indexed assignment or arithmetic modifications, ensuring that changes to one variable do not unexpectedly affect others that were assigned from it.

## Memory Management API and Utilities

### Monitoring Memory with who and whos

The **memory_manager** module provides introspection tools to inspect the current workspace. The `who` function, implemented in [`modules/memory_manager/src/cpp/Who.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Who.cpp), iterates over the current `Scope` object to list variable names. The `whos` function in [`modules/memory_manager/src/cpp/Whos.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Whos.cpp) constructs detailed tables showing variable dimensions, classes, and byte sizes by querying `ArrayOf::getByteSize()`.

Note that sparse matrices report `NaN` or "-" for byte size in `whos` output because their compressed storage does not represent a simple contiguous block, as handled in lines 78-84 of [`Whos.cpp`](https://github.com/nelson-lang/nelson/blob/main/Whos.cpp).

### Clearing Variables and Freeing Memory

The `clear` command, defined in [`modules/memory_manager/src/cpp/Clear.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Clear.cpp), removes specified variables from the current `Scope` by calling `Scope::deleteVariable`. This decrements the reference count of the underlying `Data` block, potentially freeing memory immediately if no other references exist.

Users can clear specific variables (`clear var1 var2`), the entire workspace (`clear all`), or maintain persistent variables while clearing others.

### Locking and Persistent Variables

Nelson provides mechanisms to protect variables from accidental deletion. The `lockVariable` function in [`modules/memory_manager/src/cpp/LockVariable.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/LockVariable.cpp) marks variables as locked, causing `clear` operations to skip them. Conversely, `unlockVariable` removes this protection.

The `persistent` keyword declares variables that survive function scope across multiple invocations, maintaining their `Data` blocks in memory throughout the session unless explicitly cleared.

## Best Practices for Memory Management in Nelson

- **Explicitly clear large temporaries**: Use `clear varname` on intermediate arrays that are no longer needed. This immediately decrements reference counts and releases underlying buffers, preventing invisible memory buildup in long scripts.

- **Minimize intermediate copies during modifications**: When performing multiple mutations on a large array, avoid creating chains of temporary variables. Each assignment increases reference counts and delays deallocation until all references are cleared.

- **Use persistent variables sparingly**: Declare variables `persistent` only when caching data across function calls is essential. Persistent variables remain in memory for the entire session and are excluded from `clear all` operations.

- **Lock variables only when necessary**: Apply `lockVariable` to protect critical data from accidental deletion, but unlock them as soon as protection is no longer required. Locked variables cannot be cleared and may cause memory leaks if forgotten.

- **Prefer sparse types for zero-heavy data**: Use sparse matrix types when working with data containing many zeros. Nelson reports compressed storage sizes in `whos`, indicating reduced memory footprint compared to dense arrays.

- **Monitor workspace regularly**: Periodically execute `whos` or `memoryinfo` to inspect physical and virtual memory usage. This helps identify unexpectedly large variables or reference accumulation.

- **Limit global scope usage**: Prefer local variables within functions over global workspace variables. Globals remain reachable throughout the session and cannot be automatically reclaimed when functions exit.

## Code Examples

### Freeing Large Temporary Variables

```matlab
% Allocate a large matrix (~200 MB)
A = rand(5000);

% Create a derived array (shallow copy, refcount = 2)
B = A .* 2;

% Remove reference to A
clear A
% Memory remains allocated because B still references the data

% Now free the remaining reference
clear B
% Refcount reaches 0, memory is released immediately

```

### Using Persistent Variables

```matlab
function out = cachedComputation()
    persistent cache
    if isempty(cache)
        % Expensive initialization happens only once
        cache = rand(4000);
    end
    out = cache * 2;
end

% After multiple calls, 'cache' remains in memory
% It survives 'clear all' unless explicitly cleared within the function

```

### Locking Variables for Protection

```matlab
% Create important data
myData = rand(3000);

% Prevent accidental deletion
lockVariable('myData');

% This command will NOT free the memory
clear myData

% Explicitly unlock when protection is no longer needed
unlockVariable('myData');

% Now the variable can be cleared
clear myData

```

Internally, `clear myData` invokes `ClearVariable` in [`modules/memory_manager/src/cpp/Clear.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Clear.cpp), which calls `Scope::deleteVariable`. The `lockVariable` and `unlockVariable` functions are implemented in [`modules/memory_manager/src/cpp/LockVariable.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/LockVariable.cpp).

## Key Source Files

| File | Purpose |
|------|---------|
| [`modules/types/src/include/Data.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/include/Data.hpp) | Defines the reference-counted `Data` structure with atomic `owners` counter |
| [`modules/types/src/cpp/Data.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/cpp/Data.cpp) | Implements reference count increment/decrement operations using `fetch_add` and `fetch_sub` |
| [`modules/types/src/include/ArrayOf.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/types/src/include/ArrayOf.hpp) | High-level container implementing shallow copies and copy-on-write semantics |
| [`modules/memory_manager/src/cpp/Who.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Who.cpp) | Lists variable names from the current scope |
| [`modules/memory_manager/src/cpp/Whos.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Whos.cpp) | Displays detailed variable information including byte sizes |
| [`modules/memory_manager/src/cpp/Clear.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Clear.cpp) | Implements variable removal and reference count decrementing |
| [`modules/memory_manager/src/cpp/LockVariable.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/LockVariable.cpp) | Handles variable locking to prevent deletion |
| [`modules/memory_manager/src/cpp/MemoryInformation.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/MemoryInformation.cpp) | Platform-specific system memory statistics |

## Summary

- Nelson employs **reference counting** via atomic `owners` counters in `Data` blocks, eliminating the need for garbage collection while ensuring deterministic memory release.
- The **copy-on-write** pattern allows efficient sharing of large arrays between variables through shallow copies in `ArrayOf` containers.
- **Explicit memory management** commands (`clear`, `lockVariable`, `unlockVariable`, `persistent`) provide fine-grained control over variable lifetimes.
- **Monitoring tools** (`who`, `whos`, `memoryinfo`) expose internal memory usage and reference status to help optimize resource consumption.
- Following best practices—such as clearing large temporaries, minimizing persistent variables, and using sparse types—prevents memory leaks and maintains performance in long-running Nelson sessions.

## Frequently Asked Questions

### How does Nelson handle memory deallocation without a garbage collector?

Nelson uses deterministic **reference counting** where every `Data` block tracks active references through an atomic `owners` counter. When `ArrayOf` objects are destroyed or reassigned, they decrement this counter using `owners.fetch_sub(1, std::memory_order_acq_rel)`. Once the count reaches zero, the `Data` destructor immediately releases the raw buffer, ensuring memory is freed at predictable moments without garbage collection overhead.

### What is copy-on-write in Nelson and when does it trigger?

**Copy-on-write (COW)** is an optimization that allows multiple `ArrayOf` variables to share the same underlying `Data` block through shallow copies. The shared data remains read-only across all references. When any variable attempts to modify the data—such as through indexed assignment or arithmetic operations—Nelson automatically allocates a new `Data` block, copies the contents, and applies the mutation to the exclusive copy, leaving the original shared block untouched for other references.

### Why do sparse matrices show NaN or "-" for byte size in the whos command?

Sparse matrices use **compressed storage formats** that do not allocate a contiguous block of memory proportional to their dimensions. Because the actual memory footprint depends on the number of non-zero elements and internal compression structures, `ArrayOf::getByteSize()` cannot return a simple byte calculation. The `whos` implementation in [`modules/memory_manager/src/cpp/Whos.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/Whos.cpp) specifically checks for sparse types and displays "-" or `NaN` to indicate that the storage is compressed rather than dense.

### How can I prevent a variable from being accidentally cleared in Nelson?

Use the **`lockVariable`** function to protect critical data from deletion. When a variable is locked, the `clear` command skips it during cleanup operations, as implemented in [`modules/memory_manager/src/cpp/LockVariable.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/memory_manager/src/cpp/LockVariable.cpp). The variable remains accessible and modifiable, but cannot be removed from the workspace until you explicitly call `unlockVariable`. This is particularly useful for protecting large baseline datasets or configuration constants during complex interactive sessions.