I have a simple class and assign its instance to its static variable. When I unset the instance, not sure why it doesn't actual release the memory.
class Foo {
public static $app;
public function __construct() {
self::$app = $this;
}
}
$foo = new Foo;
unset($foo);
var_dump($foo);
var_dump(Foo::$app);
Result:
Warning: Undefined variable $foo in /var/www/index.php on line 16
NULL object(Foo)#1 (0) { }
Obviously, the static $app point to the itself instance, we have unset the instance but doesn't really clean the memory for this variable. Why did it happen? Are the instance of the $app and the $foo different ones?
$foo and Foo::$app point to the same object as you desire. Any changes in terms of object properties will reflect either way if you do it.
This is because they point to the same copy in the memory as expected. However, PHP maintains a refcount indicating how many references to that memory location is present.
So, when you use unset($foo);, it doesn't garbage collect that memory location(and the value inside it), but rather just decreases the refcount of it. This can be proved using debug_zval_dump and PHP achieves the refcount maintenance using copy on write mechanism as mentioned in the linked doc.
Hence, unsetting $foo doesn't destroy the object itself but rather just the refcount and removes the variable from memory since the object still holds other references.
Snippet:
<?php
class Foo {
public static $app;
public function __construct() {
self::$app = $this;
}
}
$foo = new Foo;
var_dump($foo === Foo::$app);
debug_zval_dump(Foo::$app);
$foo = 90;// variable modified, copy on write mechanism done internally for Foo::$app, refcount modified
debug_zval_dump(Foo::$app);
Online Demo
You've only unset a reference to an object of class Foo, and that object will get garbage-collected next time garbage collector runs.
Classes, once loaded, live in the memory until request is terminated, and so will their static properties. If they hold a pointer to an object, that object won't be garbage-collected. If you want to garbage-collect Foo::$app, you'll need to unset it too.
Related
I was trying to make a "pool" like structure in a CLI program, which includes lots of "borrowing" and "recycling". And while testing things, I encountered something quite unexpected:
<?php
class FOO
{
public static $pool=[];
public static function get()
{
if(empty(self::$pool))
{
self::$pool[]=new self(mt_rand(1000,9999));
}
return array_shift(self::$pool);
}
protected static function recycle(FOO $foo)
{
echo "Recycling {$foo->num}\n";
self::$pool[]=$foo;
}
public $num;
protected function __construct(int $num)
{
$this->num=$num;
}
public function __destruct()
{
static::recycle($this);
}
}
function Bar()
{
$foo=FOO::get();
echo "Got {$foo->num}\n";
}
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
print_r(FOO::$pool);
echo "End.\n";
The output is:
Bar
Got 2911
Recycling 2911
Bar
Got 2911
Bar
Got 1038
Recycling 1038
Bar
Got 1038
Array
(
)
End.
If I limit the Bar() call to 3 times instead of 4, I got the following output:
Bar
Got 7278
Recycling 7278
Bar
Got 7278
Bar
Got 6703
Recycling 6703
Array
(
[0] => FOO Object
(
[num] => 6703
)
)
End.
Here you can see, when an object is "reused" (see 2911 and 1038 in example 1, 7278 in example 2), its __destruct() will not be called; instead, it will simply disappear.
I'm confused. Why would this happen? Why wouldn't FOO->__destruct got called every time? And is it possible to make instances of FOO auto recycle itself?
I'm testing this in PHP-7.2, behavior observed both in Windows and WSL-Ubuntu.
As far as I know destructors will not be called twice for the same object. It is generally a bad practice to reuse objects from the destructor. An object whose destructor was called should be destroyed, not reused. In your test script it doesn't cause any serious issues, but in real life, such usage could lead to unexpected behaviours if the developer is not careful.
At first when I read your question, I was worried that you have created a memory leak, but that is not the case. The destructor is called as soon as you leave the scope of Bar() only if it hasn't been called yet on this object. However, because you save the reference and increase the ref_count of this object inside of the __destruct() the garbage collector cannot collect the object yet. It must wait until the next time when you decrease the ref_count of this object.
The process can be explained as follows (simplified):
You call Bar() function which creates an instance of FOO. An object is created and assigned to $foo. (ref_count = 1)
Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means PHP starts the process of destroying the object.
2.1. The destructor is called. Inside of the destructor you increase the ref_count to 1.
2.2. The next step would be for the GC to collect the object, but the ref_count is not zero. This could mean a cycle or like in your example, a new reference has been created in the destructor. GC must wait until there are no uncyclical references to collect the object.
You call Bar() again. You shift the same object from static array, which means that the reference inside of FOO::$pool is gone, but you immediately assign it to $foo. (ref_count = 1)
Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means GC can finally collect this object. The destructor has already been called, so there is no other action to be taken here.
You didn't recycle the object, you merely prolonged its existance in the memory. You could access it for a little longer, but for PHP that object was already in the process of being destroyed.
I have the following code:
//init X (DB initialization with credentials)
$x = MySqlConnector::getMySql();
//I destroy $x
unset($x);
$x = null;
//I try to re-initialize the database, but it is already initialized
//as evident from my logs
$x = MySqlConnector::getMySql();
Relevant function:
public static function getMySql()
{
if (null === static::$instance)
{
include 'include/config.php';
static::$instance = new MySql(DBHOST, DBUSER, DBPASS);
}
return static::$instance;
}
That tells me that even after I kill off the variable that was holding the initialized object, somehow MySqlConnector stayed in memory.
How? I don't think it works with any other non-static class.
Static properties exist in the global scope, and are not associated to any particular instance.
You may be unsetting $x, but MySqlConnector::$instance stays defined.
Typically, in this kind of scenario, $instance will be a private static, so you wont be able to access the property directly, only through accesor methods, hence guaranteeing that only the Singleton class will have access to modify the property, and you wont be changing/setting/unsetting it but through properly defined methods, if they exist.
More info in the manual.
Is it possible to pass a session variable to a class property by reference so that if you then unset the class property it will also unset the session variable? I thought that the following would work but the session is not being unset.
class SomeClass
{
public function __construct()
{
$this->Foo =& $_SESSION['bar'];
$this->Foo = 123;
unset($this->Foo);
echo $_SESSION['bar'];
}
}
new SomeClass; // Outputs "123"
No, unsetting a reference in PHP simply removes the alternate handle that allowed you to refer to the original value -- it does not affect the original value itself.
There is no immediate workaround available, in this case you would have to keep $_SESSION and 'bar' in separate variables and bring them together when the time comes to do what you need.
I am having some memory problems with a script that uses objects set up with inherited static variables like this.
class a
{
public static $a = "a";
}
class b extends a
{
private $instanceVar = 'hey';
private $otherVar = 'you';
public function DoStuff()
{
echo self::$a;
}
}
then code that uses the classes like this
while(condition)
{
$obj = new b();
$obj -> DoStuff();
unset($obj);
}
My question is, will unsetting obj trigger garbage collection and the unsetting of its instance variables since it also holds a reference to the the inherited static variable?
unset in this code doesn't bring anything.
With and without it the object will be successfully collected when it's possible.
will unsetting obj trigger garbage collection
Not it won't. Garbage collector will be called automatically when it makes sense.
since it also holds a reference to the the inherited static variable
It doesn't. Objects don't hold references to a static properties.
If you care so much about GC and have PHP >= 5.3.0 have a look to
gc_collect_cycles and garbage collection in general
I was looking for the __destroy() method in ArrayObject but found no implementation.
If I set the variable containing an ArrayObject to NULL, will it correctly destroy all the objects stored in it and free memory? Or should I iterate the ArrayObject to destroy each of the objects before unsetting it?
When you unset or null the ArrayObject only the ArrayObject instance is destroyed. If the ArrayObject contains other objects, those will only be destroyed as long as there is no reference to them from somewhere else, e.g.
$foo = new StdClass;
$ao = new ArrayObject;
$ao[] = $foo;
$ao[] = new StdClass;
$ao = null; // will destroy the ArrayObject and the second stdClass
var_dump($foo); // but not the stdClass assigned to $foo
Also see http://www.php.net/manual/en/features.gc.refcounting-basics.php
In PHP you never really have to worry about memory usage beyond your own scope. unset($obj) will work fine, in your case. Alternatively you could simply leave the function you're in:
function f() {
$obj = new ArrayObject();
// do something
}
And the data will be cleaned up just fine.
PHPs internal memory management is rather simply: a reference count is kept for each piece of data and if that's 0 then it gets released. If only the ArrayObject holds the object, then it has a refcount of 1. Once the ArrayObject is gone, the refcount is 0 and the object will be gone.