Memory leaks due to circular referencing in PHP
In PHP, the destructor is called as soon as all the references to the current object are removed. This logic of reference counting can fail when circular referencing occurs. Circular referencing is when different objects circularly refer to one another, or the same object refers to itself by using the keyword $this.
Ways to resolve circular referencing
- Restructure your code
- Dereference objects explicitly
- Use weak reference (PHP 7 7.4.0, PHP 8)
Restructuring code
The most reliable way to resolve circular referencing is to restructure your code. For example, if your classes depend on one another of some part of the code, then you can create a trait from this shared code and use it in the classes independently.
Dereferencing objects explicitly
Memory leak by circular dependency can also be resolved by explicitly dereferencing objects after they have performed their duties. Let’s look at this in code below:
<?phpclass ClassA{public $dependencyLink;public function destroyLink(){echo "Destroying dependencyLink A\n";unset($this->dependencyLink);}public function __destruct(){echo "Destructor of A\n";}}class ClassB{public $dependencyLink;public function helloWorld(){echo "HelloWorld\n";}public function __destruct(){echo "Destructor of B\n";}}$obj1 = new ClassA;$obj2 = new ClassB;$obj1->dependencyLink = $obj2;$obj2->dependencyLink = $obj1;// Showing circular referencing$obj2->dependencyLink->dependencyLink->helloWorld();// $obj1->destroyLink();unset($obj1);unset($obj2);echo "I am here at end of script\n";?>
On executing the code, you will see the following output:
HelloWorld
I am here at end of script
Destructor of A
Destructor of B
Explanation
-
Lines 2–15: We define a
ClassAwith a public variable$dependencyLinkand two public methodsdestroyLinkand__destruct. -
Similarly we define a
ClassBon lines 17–29. -
New objects of
ClassAandClassBare created on lines 31 and 32. -
We create a circular referencing on lines 34 and 35, which caused this behavior.
-
Line 38: The object
$obj2calls itshelloWorldmethod via circular referencing. -
The output shows that the objects were not
unsetby lines 43 and 44, and their destructor methods were executed after completing the script.
Now, uncomment the line 40 and execute the code again. This time the output will be in order.
HelloWorld
Destroying dependencyLink A
Destructor of B
Destructor of A
I am here at end of script
- By calling the
destroyLinkmethod at line 40, we unset thedependencyLink, resolving the problem of circular referencing.
Weak referencing
Another way to resolve circular referencing is by using the WeakReference class of PHP. This allows us to create a reference that does not disrupt the destruction process.
Note:
WeakReferenceclass only works in PHP 7 7.4.0, PHP 8.
Syntax
WeakReference::create($obj);
Arguments
The create method accepts an object and creates a weak reference against it.
<?phpclass ClassA{public $name = "A";public $circular_ref;public function __construct(){$this->circular_ref = WeakReference::create($this);}public function __destruct(){echo "Destructor of A\n";}}echo 'PHP version: ' . phpversion() . "\n";$obj1 = new ClassA;// Showing circular referencingecho $obj1->circular_ref->get()->name . "\n";unset($obj1);echo "I am here at end of script\n";?>
Explanation
- Lines 2–15: We define a
ClassAto demonstrate the weak circular reference using its variable$circular_ref. - Line 9 shows how we can create a weak reference to an object itself by using
$this. - Line 16 shows the version of PHP that we are using.
- Line 20 shows how we can get properties of a
WeakReferencedobject - Notice the version of PHP we are using is
7.4.1. Otherwise, this would not have worked.
Note:
WeakReferenceobjects cannot be serialized.
Free Resources