1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
|
Debugger.Memory
===============
The [`Debugger API`][debugger] can help tools observe the debuggee's memory use
in various ways:
- It can mark each new object with the JavaScript call stack at which it was
allocated.
- It can log all object allocations, yielding a stream of JavaScript call stacks
at which allocations have occurred.
- It can compute a *census* of items belonging to the debuggee, categorizing
items in various ways, and yielding item counts.
If <i>dbg</i> is a [`Debugger`][debugger-object] instance, then the methods and
accessor properties of `dbg.memory` control how <i>dbg</i>
observes its debuggees' memory use. The `dbg.memory` object is
an instance of `Debugger.Memory`; its inherited accessors and methods are
described below.
## Allocation Site Tracking
The JavaScript engine marks each new object with the call stack at which it was
allocated, if:
- the object is allocated in the scope of a global object that is a debuggee of
some [`Debugger`][debugger-object] instance <i>dbg</i>; and
- <code><i>dbg</i>.memory.[trackingAllocationSites][tracking-allocs]</code> is
set to `true`.
- A [Bernoulli trial][bernoulli-trial] succeeds, with probability equal to the
maximum of
[`d.memory.allocationSamplingProbability`][alloc-sampling-probability] of all
`Debugger` instances `d` that are observing the global that this object is
allocated within the scope of.
Given a [`Debugger.Object`][object] instance <i>dobj</i> referring to some
object, <code><i>dobj</i>.[allocationSite][allocation-site]</code> returns a
[saved call stack][saved-frame] indicating where <i>dobj</i>'s referent was
allocated.
## Allocation Logging
If <i>dbg</i> is a [`Debugger`][debugger-object] instance, and
<code><i>dbg</i>.memory.[trackingAllocationSites][tracking-allocs]</code> is set
to `true`, then the JavaScript engine logs each object allocated by <i>dbg</i>'s
debuggee code. You can retrieve the current log by calling
<code><i>dbg</i>.memory.[drainAllocationsLog][drain-alloc-log]</code>. You can
control the limit on the log's size by setting
<code><i>dbg</i>.memory.[maxAllocationsLogLength][max-alloc-log]</code>.
## Censuses
A *census* is a complete traversal of the graph of all reachable memory items
belonging to a particular `Debugger`'s debuggees. It produces a count of those
items, broken down by various criteria. If <i>dbg</i> is a
[`Debugger`][debugger-object] instance, you can call
<code><i>dbg</i>.memory.[takeCensus][take-census]</code> to conduct a census of
its debuggees' possessions.
Accessor Properties of the `Debugger.Memory.prototype` Object
-------------------------------------------------------------
If <i>dbg</i> is a [`Debugger`][debugger-object] instance, then
`<i>dbg</i>.memory` is a `Debugger.Memory` instance, which inherits the
following accessor properties from its prototype:
## `trackingAllocationSites`
A boolean value indicating whether this `Debugger.Memory` instance is
capturing the JavaScript execution stack when each Object is allocated. This
accessor property has both a getter and setter: assigning to it enables or
disables the allocation site tracking. Reading the accessor produces `true`
if the Debugger is capturing stacks for Object allocations, and `false`
otherwise. Allocation site tracking is initially disabled in a new Debugger.
Assignment is fallible: if the Debugger cannot track allocation sites, it
throws an `Error` instance.
You can retrieve the allocation site for a given object with the
[`Debugger.Object.prototype.allocationSite`][allocation-site] accessor
property.
## `allocationSamplingProbability`
A number between 0 and 1 that indicates the probability with which each new
allocation should be entered into the allocations log. 0 is equivalent to
"never", 1 is "always", and .05 would be "one out of twenty".
The default is 1, or logging every allocation.
Note that in the presence of multiple <code>Debugger</code> instances
observing the same allocations within a global's scope, the maximum
<code>allocationSamplingProbability</code> of all the
<code>Debugger</code>s is used.
## `maxAllocationsLogLength`
The maximum number of allocation sites to accumulate in the allocations log
at a time. This accessor can be both fetched and stored to. Its default
value is `5000`.
## `allocationsLogOverflowed`
Returns `true` if there have been more than
[`maxAllocationsLogLength`][#max-alloc-log] allocations since the last time
[`drainAllocationsLog`][#drain-alloc-log] was called and some data has been
lost. Returns `false` otherwise.
Debugger.Memory Handler Functions
---------------------------------
Similar to [`Debugger`'s handler functions][debugger], `Debugger.Memory`
inherits accessor properties that store handler functions for SpiderMonkey to
call when given events occur in debuggee code.
Unlike `Debugger`'s hooks, `Debugger.Memory`'s handlers' return values are not
significant, and are ignored. The handler functions receive the
`Debugger.Memory`'s owning `Debugger` instance as their `this` value. The owning
`Debugger`'s `uncaughtExceptionHandler` is still fired for errors thrown in
`Debugger.Memory` hooks.
On a new `Debugger.Memory` instance, each of these properties is initially
`undefined`. Any value assigned to a debugging handler must be either a function
or `undefined`; otherwise a `TypeError` is thrown.
Handler functions run in the same thread in which the event occurred.
They run in the compartment to which they belong, not in a debuggee
compartment.
## `onGarbageCollection(statistics)`
A garbage collection cycle spanning one or more debuggees has just been
completed.
The *statistics* parameter is an object containing information about the GC
cycle. It has the following properties:
## `collections`
The `collections` property's value is an array. Because SpiderMonkey's
collector is incremental, a full collection cycle may consist of
multiple discrete collection slices with the JS mutator running
interleaved. For each collection slice that occurred, there is an entry
in the `collections` array with the following form:
```
{
"startTimestamp": timestamp,
"endTimestamp": timestamp,
}
```
Here the `timestamp` values are [timestamps][timestamps] of the GC slice's start
and end events.
## `reason`
A very short string describing the reason why the collection was
triggered. Known values include the following:
* `"API"`
* `"EAGER_ALLOC_TRIGGER"`
* `"DESTROY_RUNTIME"`
* `"LAST_DITCH"`
* `"TOO_MUCH_MALLOC"`
* `"ALLOC_TRIGGER"`
* `"DEBUG_GC"`
* `"COMPARTMENT_REVIVED"`
* `"RESET"`
* `"OUT_OF_NURSERY"`
* `"EVICT_NURSERY"`
* `"FULL_STORE_BUFFER"`
* `"SHARED_MEMORY_LIMIT"`
* `"PERIODIC_FULL_GC"`
* `"INCREMENTAL_TOO_SLOW"`
* `"DOM_WINDOW_UTILS"`
* `"COMPONENT_UTILS"`
* `"MEM_PRESSURE"`
* `"CC_FINISHED"`
* `"CC_FORCED"`
* `"LOAD_END"`
* `"PAGE_HIDE"`
* `"NSJSCONTEXT_DESTROY"`
* `"SET_NEW_DOCUMENT"`
* `"SET_DOC_SHELL"`
* `"DOM_UTILS"`
* `"DOM_IPC"`
* `"DOM_WORKER"`
* `"INTER_SLICE_GC"`
* `"REFRESH_FRAME"`
* `"FULL_GC_TIMER"`
* `"SHUTDOWN_CC"`
* `"USER_INACTIVE"`
## `nonincrementalReason`
If SpiderMonkey's collector determined it could not incrementally
collect garbage, and had to do a full GC all at once, this is a short
string describing the reason it determined the full GC was necessary.
Otherwise, `null` is returned. Known values include the following:
* `"GC mode"`
* `"malloc bytes trigger"`
* `"allocation trigger"`
* `"requested"`
## `gcCycleNumber`
The GC cycle's "number". Does not correspond to the number
of GC cycles that have run, but is guaranteed to be monotonically
increasing.
Function Properties of the `Debugger.Memory.prototype` Object
-------------------------------------------------------------
## `drainAllocationsLog()`
When `trackingAllocationSites` is `true`, this method returns an array of
recent `Object` allocations within the set of debuggees. *Recent* is
defined as the `maxAllocationsLogLength` most recent `Object` allocations
since the last call to `drainAllocationsLog`. Therefore, calling this
method effectively clears the log.
Objects in the array are of the form:
```
{
"timestamp": timestamp,
"frame": allocationSite,
"class": className,
"size": byteSize,
"inNursery": inNursery,
}
```
Where
* `timestamp` is the [timestamp][timestamps] of the allocation event.
* `allocationSite` is an allocation site (as a
[captured stack][saved-frame]). Note that this property can be null if the
object was allocated with no JavaScript frames on the stack.
* `className` is the string name of the allocated object's internal
`[[Class]]` property, for example "Array", "Date", "RegExp", or (most
commonly) "Object".
* `byteSize` is the size of the object in bytes.
* `inNursery` is true if the allocation happened inside the nursery. False
if the allocation skipped the nursery and started in the tenured heap.
When `trackingAllocationSites` is `false`, `drainAllocationsLog()` throws an
`Error`.
## `takeCensus(options)`
Carry out a census of the debuggee compartments' contents. A *census* is a
complete traversal of the graph of all reachable memory items belonging to a
particular `Debugger`'s debuggees. The census produces a count of those
items, broken down by various criteria.
The <i>options</i> argument is an object whose properties specify how the
census should be carried out.
If <i>options</i> has a `breakdown` property, that determines how the census
categorizes the items it finds, and what data it collects about them. For
example, if `dbg` is a `Debugger` instance, the following performs a simple
count of debuggee items:
dbg.memory.takeCensus({ breakdown: { by: 'count' } })
That might produce a result like:
{ "count": 1616, "bytes": 93240 }
Here is a breakdown that groups JavaScript objects by their class name,
non-string, non-script items by their C++ type name, and DOM nodes with
their node name:
{
by: "coarseType",
objects: { by: "objectClass" },
other: { by: "internalType" },
domNode: { by: "descriptiveType" }
}
which produces a result like this:
{
"objects": {
"Function": { "count": 404, "bytes": 37328 },
"Object": { "count": 11, "bytes": 1264 },
"Debugger": { "count": 1, "bytes": 416 },
"ScriptSource": { "count": 1, "bytes": 64 },
// ... omitted for brevity...
},
"scripts": { "count": 1, "bytes": 0 },
"strings": { "count": 701, "bytes": 49080 },
"other": {
"js::Shape": { "count": 450, "bytes": 0 },
"js::BaseShape": { "count": 21, "bytes": 0 },
"js::ObjectGroup": { "count": 17, "bytes": 0 }
},
"domNode": {
"#text": { "count": 1, "bytes": 12 }
}
}
In general, a `breakdown` value has one of the following forms:
* <code>{ by: "count", count:<i>count<i>, bytes:<i>bytes</i> }</code>
The trivial categorization: none whatsoever. Simply tally up the items
visited. If <i>count</i> is true, count the number of items visited; if
<i>bytes</i> is true, total the number of bytes the items use directly.
Both <i>count</i> and <i>bytes</i> are optional; if omitted, they
default to `true`. In the result of the census, this breakdown produces
a value of the form:
{ "count": n, "bytes": b }
where the `count` and `bytes` properties are present as directed by the
<i>count</i> and <i>bytes</i> properties on the breakdown.
Note that the census can produce byte sizes only for the most common
types. When the census cannot find the byte size for a given type, it
returns zero.
* <code>{ by: "bucket" }</code>
Do not do any filtering or categorizing. Instead, accumulate a bucket of
each node's ID for every node that matches. The resulting report is an
array of the IDs.
For example, to find the ID of all nodes whose internal object
`[[class]]` property is named "RegExp", you could use the following code:
const report = dbg.memory.takeCensus({
breakdown: {
by: "objectClass",
then: { by: "bucket" }
}
});
doStuffWithRegExpIDs(report.RegExp);
* <code>{ by: "allocationStack", then:<i>breakdown</i>, noStack:<i>noStackBreakdown</i> }</code>
Group items by the full JavaScript stack trace at which they were
allocated.
Further categorize all the items allocated at each distinct stack using
<i>breakdown</i>.
In the result of the census, this breakdown produces a JavaScript `Map`
value whose keys are `SavedFrame` values, and whose values are whatever
sort of result <i>breakdown</i> produces. Objects allocated on an empty
JavaScript stack appear under the key `null`.
SpiderMonkey only tracks allocation sites for items if requested via the
[`trackingAllocationSites`][tracking-allocs] flag; even then, it does
not record allocation sites for every kind of item that appears in the
heap. Items that lack allocation site information are counted using
<i>noStackBreakdown</i>. These appear in the result `Map` under the key
string `"noStack"`.
* <code>{ by: "objectClass", then:<i>breakdown</i>, other:<i>otherBreakdown</i> }</code>
Group JavaScript objects by their ECMAScript `[[Class]]` internal property values.
Further categorize JavaScript objects in each class using
<i>breakdown</i>. Further categorize items that are not JavaScript
objects using <i>otherBreakdown</i>.
In the result of the census, this breakdown produces a JavaScript object
with no prototype whose own property names are strings naming classes,
and whose values are whatever sort of result <i>breakdown</i> produces.
The results for non-object items appear as the value of the property
named `"other"`.
* <code>{ by: "coarseType", objects:<i>objects</i>, scripts:<i>scripts</i>, strings:<i>strings</i>, domNode:<i>domNode</i>, other:<i>other</i> }</code>
Group items by their coarse type.
Use the breakdown value <i>objects</i> for items that are JavaScript
objects.
Use the breakdown value <i>scripts</i> for items that are
representations of JavaScript code. This includes bytecode, compiled
machine code, and saved source code.
Use the breakdown value <i>strings</i> for JavaScript strings.
Use the breakdown value <i>domNode</i> for DOM nodes.
Use the breakdown value <i>other</i> for items that don't fit into any of
the above categories.
In the result of the census, this breakdown produces a JavaScript object
of the form:
```
{
"objects": result,
"scripts": result,
"strings": result,
"domNode:" result,
"other": result,
}
```
where each <i>result</i> is a value of whatever sort the corresponding
breakdown value produces. All breakdown values are optional, and default
to `{ type: "count" }`.
* `{ by: "internalType", then: breakdown }`
Group items by the names given their types internally by SpiderMonkey.
These names are not meaningful to web developers, but this type of
breakdown does serve as a catch-all that can be useful to Firefox tool
developers.
For example, a census of a pristine debuggee global broken down by
internal type name typically looks like this:
{
"JSString": { "count": 701, "bytes": 49080 },
"js::Shape": { "count": 450, "bytes": 0 },
"JSObject": { "count": 426, "bytes": 44160 },
"js::BaseShape": { "count": 21, "bytes": 0 },
"js::ObjectGroup": { "count": 17, "bytes": 0 },
"JSScript": { "count": 1, "bytes": 0 }
}
In the result of the census, this breakdown produces a JavaScript object
with no prototype whose own property names are strings naming types,
and whose values are whatever sort of result <i>breakdown</i> produces.
* <code>[ <i>breakdown</i>, ... ]</code>
Group each item using all the given breakdown values. In the result of
the census, this breakdown produces an array of values of the sort
produced by each listed breakdown.
To simplify breakdown values, all `then` and `other` properties are optional.
If omitted, they are treated as if they were `{ type: "count" }`.
If the `options` argument has no `breakdown` property, `takeCensus` defaults
to the following:
```js
{
by: "coarseType",
objects: { by: "objectClass" },
domNode: { by: "descriptiveType" },
other: { by: "internalType" }
}
```
which produces results of the form:
```
{
objects: { class: count, ... },
scripts: count,
strings: count,
domNode: { node name:count, ... },
other: { type name:count, ... }
}
```
where each `count` has the form:
```js
{ "count": count, bytes: bytes }
```
Because performing a census requires traversing the entire graph of objects
in debuggee compartments, it is an expensive operation. On developer
hardware in 2014, traversing a memory graph containing roughly 130,000 nodes
and 410,000 edges took roughly 100ms. The traversal itself temporarily
allocates one hash table entry per node (roughly two address-sized words) in
addition to the per-category counts, whose size depends on the number of
categories.
Memory Use Analysis Exposes Implementation Details
--------------------------------------------------
Memory analysis may yield surprising results, because browser implementation
details that are transparent to content JavaScript often have visible effects on
memory consumption. Web developers need to know their pages' actual memory
consumption on real browsers, so it is correct for the tool to expose these
behaviors, as long as it is done in a way that helps developers make decisions
about their own code.
This section covers some areas where Firefox's actual behavior deviates from
what one might expect from the specified behavior of the web platform.
## Objects
SpiderMonkey objects usually use less memory than the naïve "table of properties
with attributes" model would suggest. For example, it is typical for many
objects to have identical sets of properties, with only the properties' values
varying from one object to the next. To take advantage of this regularity,
SpiderMonkey objects with identical sets of properties may share their property
metadata; only property values are stored directly in the object.
Array objects may also be optimized, if the set of live indices is dense.
## Strings
SpiderMonkey has three representations of strings:
- Normal: the string's text is counted in its size.
- Substring: the string is a substring of some other string, and points to that
string for its storage. This representation may result in a small string
retaining a very large string. However, the memory consumed by the string
itself is a small constant independent of its size, since it is simply a
reference to the larger string, a start position, and a length.
- Concatenations: When asked to concatenate two strings, SpiderMonkey may elect
to delay copying the strings' data, and represent the result simply as a
pointer to the two original strings. Again, such a string retains other
strings, but the memory consumed by the string itself is a small constant
independent of its size, since it is simply a pair of pointers.
SpiderMonkey converts strings from the more complex representations to the
simpler ones when it pleases. Such conversions usually increase memory
consumption.
SpiderMonkey shares some strings amongst all web pages and browser JS. These
shared strings, called *atoms*, are not included in censuses' string counts.
## Scripts
SpiderMonkey has a complex, hybrid representation of JavaScript code. There
are four representations kept in memory:
- _Source code_. SpiderMonkey retains a copy of most JavaScript source code.
- _Compressed source code_. SpiderMonkey compresses JavaScript source code,
and de-compresses it on demand. Heuristics determine how long to retain the
uncompressed code.
- _Bytecode_. This is SpiderMonkey's parsed representation of JavaScript.
Bytecode can be interpreted directly, or used as input to a just-in-time
compiler. Source is parsed into bytecode on demand; functions that are never
called are never parsed.
- _Machine code_. SpiderMonkey includes several just-in-time compilers, each of
which translates JavaScript source or bytecode to machine code. Heuristics
determine which code to compile, and which compiler to use. Machine code may
be dropped in response to memory pressure, and regenerated as needed.
Furthermore, SpiderMonkey's just-in-time compilers generate inline caches for
type specialization. This information is dropped periodically to reduce memory
usage.
In a census, all the various forms of JavaScript code are placed in the
`"scripts"` category.
[debugger]: Debugger-API.md
[debugger-object]: Debugger.md
[tracking-allocs]: #trackingallocationsites
[bernoulli-trial]: https://en.wikipedia.org/wiki/Bernoulli_trial
[alloc-sampling-probability]: #allocsamplingprobability
[object]: Debugger.Object.md
[allocation-site]: Debugger.Object.md#allocationsite
[saved-frame]: ../SavedFrame/index
[drain-alloc-log]: #drainAllocationsLog
[max-alloc-log]: #maxAllocationsLogLength
[take-census]: #takecensus-options
[timestamps]: ./Conventions.md#timestamps
|