While looking for a memory leak in a Xojo app, I printed repeatedly Runtime.MemoryUsed value to the console and the object count with Runtime.ObjectCount. While objects and strings are created and destroyed, the memory used goes up regularly, often by jumps. Object count goes up and down as we do stuff in the application. So what is going on?
How is memory allocated?
When I remember the old days, over 20 years ago, where I used Turbo Pascal with DOS, the memory was allocated in paragraphs, a 16 byte unit. System memory was small in size (1 mega byte max) and even there allocating memory in smaller units was not done as it is highly inefficient to do so. And the memory model with Segments and Offsets made it convenient to only manage the segment part. Anyway, today memory is still allocated in 16 byte chunks with malloc, the C library to manage allocations.
When you allocate memory for an object or a string, the Xojo runtime will ask malloc for memory. And malloc eventually asks the OS for memory pages. A page is usually 4096 bytes, but can be bigger. I've seen computers with 16 KByte pages and newer Intel CPUs can do 4 MB per page. If you allocate a MemoryBlock of e.g. 5 MB, the malloc system will provide you straight away with such pages, maybe rounding up a bit or combining big and small page sizes. But for smaller allocations of 16 byte to 2048 byte, there will be pools. Since allocating pages has an overhead, malloc tends to allocate a bigger block, e.g. 1 MB for a new pool. There are pools for various sizes, e.g. 16, 32, 48, 64, 96, 128, etc. bytes in block sizes.
If you allocate a new string in Xojo with "Hello World", the malloc system will be asked for one block of 11 bytes (actual 12 for the Chr(0) on the end to be used as C string directly). This is rounded up to 16 bytes. If the pool for 16 byte objects is full, a new one is allocated of about 1 MB. That is why we see Runtime.MemoryUsed jump up by 1 MB regularly. There is some extra allocation for the tables used in management of those blocks.
When you free an object, it's marked as free. The block can be filled with a certain bit pattern to later detect reuse of freed memory. If all blocks in the given pool are free, the whole pool could be freed. But this usually doesn't happen. Getting pages from the OS is time consuming, so malloc prefers to keep the pool for later. But most of the pool may usually be marked as free.
For 21.1 we add a new MemoryStatisticsMBS class. This new class provides on macOS, iOS and Linux some statistics on the malloc system. Like the number of bytes for memory allocated, memory in use and memory free. While your app runs, the pools grow, but will contain free space for reuse later. We also add a Compact function, which can ask malloc to free available pages to the OS. Usually only a few pages can be released as their memory is completely marked free. The OS may call that for you if needed if the OS is under pressure.
When your running Xojo Web app shows you Runtime.MemoryUsed of 10 MB, it may be well, that only 2 MB are in active use and 8 MB available for reuse. To dump object counts, we have a new Memory Leak Testing example project for you coming with next version (in 11.1pr4 already). It can output a bit of statistics about what objects are allocated. And that shows that the object counts don't change much in using it here, but the memory pools like to grow as allocations are made and released.
Output of our DumpObjects function looks e.g. like this:
|4099||array||2||Array of Int64|
|4101||array||2||Array of Double|
|4104||array||31||Array of Strings|
|4105||array||138||Array of Objects|
|9||App||1||Should always be one App object|
|9||EndOfLine||1||EndOfLine object created on first use of EndOfLine|
|9||Session||2||2 session active right now|
|9||WebPage1||2||Each session has a web page|
Please do not hesitate to contact us with your questions.