Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
memory.qdoc
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3/*!
4
5\page qtqml-javascript-memory.html
6\meta {keywords} {qmltopic}
7\title Memory Management in the JavaScript Engine
8\brief Describes how the JavaScript Engine manages memory.
9
10\section1 Introduction
11
12This document describes the \e dynamic memory management of the JavaScript
13Engine in QML. It is a rather technical, in depth description. You only need
14to read this if you care about the exact characteristics of JavaScript memory
15management in QML. In particular, it can be helpful if you're trying to
16optimize your application for maximum performance.
17
18\note By compiling your QML code to C++ using the \l{Qt Quick Compiler}
19you can avoid much of the JavaScript heap usage. The generated C++ code uses the
20familiar C++ stack and heap for storing objects and values. The
21\l{JavaScript Host Environment}, however, always uses some JavaScript-managed
22memory, no matter if you use it or not. If you use features that cannot be
23compiled to C++, the engine will fall back to interpretation or JIT compilation
24and use JavaScript objects stored on the JavaScript heap, though.
25
26\section1 Basic Principles
27
28The JavaScript engine in QML has a dedicated memory manager that requests
29address space in units of multiple pages from the operating system. Objects,
30strings, and other managed values created in JavaScript are then placed in this
31address space, using the JavaScript engine's own allocation scheme. The
32JavaScript engine does not use the C library's malloc() and free(), nor the
33default implementations of C++'s new and delete to allocate memory for
34JavaScript objects.
35
36Requests for address space are generally done with mmap() on Unix-like systems
37and with VirtualAlloc() on windows. There are several platform-specific
38implementations of those primitives. Address space reserved this way is not
39immediately committed to physical memory. Rather, the operating system notices
40when a page of memory is actually accessed and only then commits it. Therefore,
41the address space is practically free and having a lot of it gives the
42JavaScript memory manager the leverage it needs to place objects in an efficient
43way on the JavaScript heap. Furthermore, there are platform-specific techniques
44to tell the operating system that a chunk of address space, though still
45reserved, does not have to be mapped into physical memory for the time being.
46The operating system can then decommit the memory as needed and use it for other
47tasks. Crucially, most operating systems do not guarantee immediate action on
48such a decommit request. They will only decommit the memory when it is actually
49needed for something else. On Unix-like systems we generally use madvise() for
50this. Windows has specific flags to VirtualFree() to do the equivalent.
51
52\note There are memory profiling tools that do not understand this mechanism and
53over-report JavaScript memory usage.
54
55All values stored on the JavaScript heap are subject to garbage collection.
56None of the values are immediately "deleted" when they go out of scope or are
57otherwise "dropped". Only the garbage collector may remove values from the
58JavaScript heap and return memory (see \l{Garbage Collection} below for how
59this works).
60
61\section1 QObject-based Types
62
63QObject-based types, and in particular everything you can phrase as a QML
64element, are allocated on the C++ heap. Only a small wrapper around the pointer
65is placed on the JavaScript heap when a QObject is accessed from JavaScript.
66Such a wrapper, however, can own the QObject it points to. See
67\l{QJSEngine::ObjectOwnership}. If the wrapper owns the object, it will be
68deleted when the wrapper is garbage-collected. You can then also manually
69trigger the deletion by calling the destroy() method on it. destroy() internally
70calls \l{QObject::deleteLater()}. It will therefore not immediately delete the
71object, but wait for the next event loop iteration.
72
73QML-declared \e properties of objects are stored on the JavaScript heap. They
74live as long as the object they belong to lives. Afterwards they are removed the
75next time the garbage collector runs.
76
77\section1 Object Allocation
78
79In JavaScript, any structured type is an object. This includes function objects,
80arrays, regular expressions, date objects and much more. QML has a number of
81internal object types, such as the above mentioned QObject wrapper. Whenever
82an object is created, the memory manager locates some storage for it on the
83JavaScript heap.
84
85JavaScript strings are also managed values, but their string data is not
86allocated on the JavaScript heap. Similar to QObject wrappers, the heap objects
87for strings are just thin wrappers around a pointer to string data.
88
89When allocating memory for an object, the size of the object is first rounded up
90to 32 byte alignment. Each 32 byte piece of address space is called a "slot".
91For objects smaller than a "huge size" threshold, the memory manager performs
92a series of attempts to place the object in memory:
93\list
94\li The memory manager keeps linked lists of previously freed pieces of heap,
95 called "bins". Each bin holds pieces of heap with a fixed per-bin size in
96 slots. If the bin for the right size is not empty, it picks the first entry
97 and places the object there.
98\li The memory that hasn't been used yet is managed via a bumper allocator. A
99 bumper pointer points to the byte beyond the occupied address space. If
100 there is still enough unused address space, the bumper is increased
101 accordingly, and the object is placed in unused space.
102\li A separate bin is kept for previously freed pieces of heap of varying sizes
103 larger than the specific sizes mentioned above. The memory manager
104 traverses this list and tries to find a piece it can split to accommodate
105 the new object.
106\li The memory manager searches the lists of specifically sized bins
107 larger than the object to be allocated and tries to split one of those.
108\li Finally, if none of the above works, the memory manager reserves more
109 address space and allocates the object using the bumper allocator.
110\endlist
111
112Huge objects are handled by their own allocator. For each of those one or more
113separate memory pages are obtained from the OS and managed separately.
114
115Additionally, each new chunk of address space the memory manager obtains from
116the OS gets a header that holds a number of flags for each slot:
117\list
118\li \e{object}: The first slot occupied by an object is flagged with this bit.
119\li \e{extends}: Any further slots occupied by an object are flagged with this
120 bit.
121\li \e{mark}: When the garbage collector runs, it sets this bit if the object is
122 still in use.
123\endlist
124
125\section1 Internal Classes
126
127In order to minimize the required storage for metadata on what members
128an object holds, the JavaScript engine assigns an "internal class" to each
129object. Other JavaScript engines call this "hidden class" or "shape".
130Internal classes are deduplicated and kept in a tree. If a property is
131added to an object, the children of the current internal class are checked to
132see if the same object layout has occurred before. If so, we can use the
133resulting internal class right away. Otherwise we have to create a new one.
134
135Internal classes are stored in their own section of the JavaScript heap that
136otherwise works the same way as the general object allocation described above.
137This is because internal classes have to be kept alive while the objects using
138them are collected. Internal classes are then collected in a separate pass.
139
140The actual property attributes stored in internal classes are \e not kept on
141the JavaScript heap, though, but rather managed using new and delete.
142
143\section1 Garbage Collection
144
145The garbage collector used in the JavaScript engine is a non-moving,
146Mark and Sweep design. Since Qt 6.8, it runs incrementally by default (unless
147\l QV4_GC_TIMELIMIT is set to 0).
148In the \e mark phase we traverse all the
149known places where live references to objects can be found. In particular:
150
151\list
152\li JavaScript globals
153\li Undeletable parts of QML and JavaScript compilation units
154\li The JavaScript stack
155\li The persistent value storage. This is where QJSValue and similar classes
156 keep references to JavaScript objects.
157\endlist
158
159For any object found in those places the mark bits are set recursively for
160anything it references.
161
162In the \e sweep phase the garbage collector then traverses the whole heap and
163frees any objects not marked before. The resulting released memory is sorted
164into the bins to be used for further allocations. If a chunk of address space
165is completely empty, it is decommitted, but the address space is retained
166(see \l{Basic Principles} above). If the memory usage grows again, the same
167address space is re-used.
168
169The garbage collector is triggered either manually by calling the \l [QML] {Qt::}{gc()} function
170or by a heuristic that takes the following aspects into account:
171
172\list
173\li The amount of memory managed by object on the JavaScript heap, but not
174 directly allocated on the JavaScript heap, such as strings and internal
175 class member data. A dynamic threshold is maintained for those. If it is
176 surpassed, the garbage collector runs and the threshold is increased. If
177 the amount of managed external memory falls far below the threshold, the
178 threshold is decreased.
179\li The total address space reserved. The internal memory allocation on the
180 JavaScript heap is only considered after at least some address space has
181 been reserved.
182\li The additional address space reservation since the last garbage collector
183 run. If the amount of address space is more than double the amount of
184 used memory after the last garbage collector run, we run the garbage
185 collector again.
186\endlist
187
188\section1 Analyzing Memory Usage
189
190In order to observe the development of both the address space and the number
191of objects allocated in it, it is best to use a specialized tool. The
192\l{\QC: Profiling QML Applications}{QML Profiler} provides a visualization that
193helps here. More generic tools cannot see what the JavaScript memory manager
194does within the address space it reserves and may not even notice that part
195of the address space is not committed to physical memory.
196
197Another way to debug memory usage are the
198\l{QLoggingCategory}{logging categories} \e{qt.qml.gc.statistics} and
199\e{qt.qml.gc.allocatorStats}. If you enable the \e{Debug} level for
200qt.qml.gc.statistics, the garbage collector will print some information every
201time it runs:
202
203\list
204\li How much total address space is reserved
205\li How much memory was in use before and after the garbage collection
206\li How many objects of various sizes were allocated so far
207\endlist
208
209The \e{Debug} level for qt.qml.gc.allocatorStats prints more detailed
210statistics that also include how the garbage collector was triggered, timings
211for the mark and sweep phases and a detailed breakdown of memory usage by bytes
212and chunks of address space.
213
214*/