How to explore objects pinned in memory

This page contains scripts and strategies useful for debugging pinned in memory objects.

Overview

Pinned in memory objects are normal during the execution. However, if they cannot be garbaged collected, they will lead to an increase in memory size, as they cannot be moved around when compacting memory.

Common issues

Parameters to FFI calls are pinned in memory. If one of those parameters is a string literal, it will not be garbaged collected.

Useful Scripts

Count all objects that are pinned in memory

2 timesRepeat: [ Smalltalk garbageCollect ].
(SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ]) size
  

Print string for all objects that are pinned in memory.

Here we just get the string representation as getting the actual objects can create references to it, making it harder to find from where they are referenced

2 timesRepeat: [ Smalltalk garbageCollect ].
(SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ]) collect: [ :each | each printString ]
  

Select all objects that are pinned in memory.

SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ]
  

To get accurate results it is best to do a few gargage collects before, so pinned objects that can be gargaged collected are removed.

2 timesRepeat: [ Smalltalk garbageCollect ].
(SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ])
  

Unpinning all pinned objects

(SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ])
		do: [ :e | e unpinInMemory ]
  

Exploring pinned objects

2 timesRepeat: [ Smalltalk garbageCollect ].
pinned := SystemNavigation  default allObjects 
	select: [ :each | each isPinnedInMemory ]
  
pinned size
  
pinned groupedBy: #className
  

Exploring pinned objects by avoiding any new references

2 timesRepeat: [ Smalltalk garbageCollect ].
pinnedData := (SystemNavigation  default allObjects 
	select: [ :each | each isPinnedInMemory])
	collect: [ :each | each class name  -> each printString ].
  
pinnedData groupedBy: #key
  

References to pinned objects

We can use ReferenceFinder Object subclass: #ReferenceFinder instanceVariableNames: 'backlinks objectsLeft testBlock foundBlock' classVariableNames: '' package: 'ReferenceFinder-Core-Base' to find references to a particular pinned objects.

To get accurate results it is best to do a few garbage collects before, and not store the list of pinned objects in any local variable or inspect it

NOTE: This does not work if the pinned objects is a method literal. So of the list of references to a pinned objects that is not garbage collected is nil, it means that the object is most likely a methid literal.

2 timesRepeat: [ Smalltalk garbageCollect ].
ReferenceFinder findPathTo:  ((SystemNavigation default allObjects 
	select: [ :e | e isPinnedInMemory ]) at: 4) 
  

Possible issues

Handle MessageCatcher

MessageCatcher ProtoObject subclass: #MessageCatcher instanceVariableNames: 'accumulator' classVariableNames: '' package: 'Debugging-Core' instances can be problematic when iterating over all objects to find pinned objects. That is because Object>>#isPinnedInMemory isPinnedInMemory "Answer if the receiver is pinned. The VM's garbage collector routinely moves objects as it reclaims and compacts memory. But it can also pin an object so that it will not be moved, which can make it easier to pass objects out through the FFI." <primitive: 183 error: ec> ^self primitiveFailed no longer returns a boolean when used within a select block.

The code below adds isPinnedInMemory to MessageCatcher

MessageCatcher 
	compile: (Object>>#isPinnedInMemory) sourceCode
	classified: (Object>>#isPinnedInMemory) category
  

Memory size

It can also be useful to compute the memory size to understand the effects of pinned objects

SystemNavigation default allObjects sum: #sizeInMemory
  
Smalltalk vm statisticsReport