Skip to content

Commit 1956b82

Browse files
committed
document VisualVM memory leak query
1 parent 259655e commit 1956b82

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

docs/contributor/CONTRIBUTING.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,76 @@ To run the JVM configuration:
289289
To run the Native Image configuration:
290290

291291
mx --env ../../graal/vm/mx.vm/ce --exclude-components=slgm --dynamicimports /vm benchmark meso:nbody3 -- --python-vm=graalpython --jvm=graalvm-ce-python --jvm-config=native --python-vm-config=default --
292+
293+
### Finding Memory Leaks
294+
295+
For best performance we keep references to long-lived user objects (mostly
296+
functions, classes, and modules) directly in the AST nodes when using the
297+
default configuration of a single Python context (as is used when running the
298+
launcher). For better sharing of warmup and where absolutely best peak
299+
performance is not needed, contexts can be configured with a shared engine and
300+
the ASTs will be shared across contexts. However, that implies we *must* not
301+
store any user objects strongly in the ASTs. Here is a query to find any such
302+
leaks where a PythonObject is reacheable from any kind of Truffle AST Node
303+
subinstance in VisualVM:
304+
305+
findLeaks("com.oracle.graal.python.builtins.objects.object.PythonObject", "com.oracle.truffle.api.nodes.Node")
306+
307+
function findLeaks(to, from) {
308+
var objs = heap.objects(to, true)
309+
var leaks = []
310+
var path = []
311+
var cutOffPath = false
312+
313+
while (objs.hasMoreElements() && leaks.length < 100) {
314+
var o = objs.nextElement()
315+
path = []
316+
cutOffPath = false
317+
if (isReferencedFromAtMaxDepth(o, 20)) {
318+
if (!cutOffPath) {
319+
leaks.push(o)
320+
}
321+
}
322+
}
323+
return leaks
324+
325+
function isReferencedFromAtMaxDepth(obj, limit) {
326+
var refs = referrers(obj)
327+
while (refs.hasMoreElements()) {
328+
var o = refs.nextElement()
329+
var refClass = classof(o)
330+
var cutOffHere = false
331+
while (refClass != null) {
332+
if (refClass.name == from) {
333+
leaks.push(classof(o).name)
334+
return true
335+
} else if (refClass.name == "java.lang.ref.WeakReference" ||
336+
refClass.name == "java.lang.ref.SoftReference" ||
337+
refClass.name == "java.lang.ref.PhantomReference") {
338+
// any weak reference is fine
339+
return false;
340+
} else if (refClass.name == to) {
341+
// we have found another `to` object along the path, use this one as the shorter path
342+
cutOffHere = true
343+
}
344+
refClass = refClass.superclass
345+
}
346+
if (limit > 0) {
347+
if (isReferencedFromAtMaxDepth(o, limit - 1)) {
348+
if (!cutOffPath) {
349+
if (cutOffHere) {
350+
cutOffPath = true
351+
leaks.push(o)
352+
} else {
353+
leaks.push(classof(o).name)
354+
}
355+
}
356+
return true
357+
}
358+
}
359+
}
360+
return false
361+
}
362+
}
363+
364+
Running such a query on a multi-context heap should yield no results.

0 commit comments

Comments
 (0)