When allocating memory for an object, the size of the object is first rounded up to 32 byte alignment. Each 32 byte piece of address space is called a "slot". For objects smaller than a "huge size" threshold, the memory manager performs a series of attempts to place the object in memory:
- The memory manager keeps linked lists of previously freed pieces of heap, called "bins". Each bin holds pieces of heap with a fixed per-bin size in slots. If the bin for the right size is not empty, it picks the first entry and places the object there.
- The memory that hasn't been used yet is managed via a bumper allocator. A bumper pointer points to the byte beyond the occupied address space. If there is still enough unused address space, the bumper is increased accordingly, and the object is placed in unused space.
- A separate bin is kept for previously freed pieces of heap of varying sizes larger than the specific sizes mentioned above. The memory manager traverses this list and tries to find a piece it can split to accommodate the new object.
- The memory manager searches the lists of specifically sized bins larger than the object to be allocated and tries to split one of those.
- Finally, if none of the above works, the memory manager reserves more address space and allocates the object using the bumper allocator.
Huge objects are handled by their own allocator. For each of those one or more separate memory pages are obtained from the OS and managed separately.
Additionally, each new chunk of address space the memory manager obtains from the OS gets a header that holds a number of flags for each slot:
- object: The first slot occupied by an object is flagged with this bit.
- extends: Any further slots occupied by an object are flagged with this bit.
- mark: When the garbage collector runs, it sets this bit if the object is still in use.
For any object found in those places the mark bits are set recursively for anything it references.
In the sweep phase the garbage collector then traverses the whole heap and frees any objects not marked before. The resulting released memory is sorted into the bins to be used for further allocations. If a chunk of address space is completely empty, it is decommitted, but the address space is retained (see Basic Principles above). If the memory usage grows again, the same address space is re-used.
The garbage collector is triggered either manually by calling the gc() function or by a heuristic that takes the following aspects into account:
- The additional address space reservation since the last garbage collector run. If the amount of address space is more than double the amount of used memory after the last garbage collector run, we run the garbage collector again.
Another way to debug memory usage are the logging categories qt.qml.gc.statistics and qt.qml.gc.allocatorStats. If you enable the Debug level for qt.qml.gc.statistics, the garbage collector will print some information every time it runs:
- How much total address space is reserved
- How much memory was in use before and after the garbage collection
- How many objects of various sizes were allocated so far
The Debug level for qt.qml.gc.allocatorStats prints more detailed statistics that also include how the garbage collector was triggered, timings for the mark and sweep phases and a detailed breakdown of memory usage by bytes and chunks of address space.
© 2023 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.