Cyclic references in PHP 7.4 - php

In PHP 7.4 I noticed the number of collected cycles returned by gc_collect_cycles is always zero when there is a destructor method in a cyclic referenced object.
class A {
public function __destruct() {
}
}
gc_disable();
$a1 = new A;
$a2 = new A;
$a1->ref = $a2;
$a2->ref = $a1;
$a1 = $a2 = NULL;
echo('removed cycles: '.gc_collect_cycles()); // Output: removed cycles: 0
When I remove the __destruct method the output is:
removed cycles: 2
You can see this behavior started as of PHP 7.4.0beta4
What is going on here ? Are garbage cycles get collected in the destructor even when GC is disabled?

Since PHP 7.4, the initial garbage collection run will only call destructors on objects that have them, and the actual destruction of the object is deferred to the next GC run. You can see this if you perform two calls to gc_collect_cycles(): https://3v4l.org/0LIVn
The reason for this behavior is that destructors can introduce additional references to the object, such that it is no longer valid to destroy it. Previous versions used an unreliable heuristic to detect this case. PHP 7.4 will instead delay destruction to a separate GC run.

Related

PHP: Confusing disparity between invoking reflection method using constructed array or func_get_args() in version 5.4

This is a very edge case in PHP 5.4 regarding passing objects by reference where get this error:
PHP Warning: Parameter 1 to A::foo() expected to be a reference, value given
But only as a compound effect of:
Using reflection to set an inherited method as 'accessible',
AND that method taking an explicitly referential argument (&argument sig)
AND then invoking it with func_get_args() as opposed to constructing the array of args manually.
No idea why these things all cause this behaviour or if they should.
Important to note that this effect isn't present in PHP 5.5.
This is the code that will cause the above error, but if you comment the line with COMMENT THIS LINE the code runs fine (e.g. the object gets passed correctly to the 'foo' function):
class A {
private function foo(&$arg1) {
var_dump('arg1: ', $arg1);
}
}
class B extends A {
public function bar() {
$x = new stdClass();
$x->baz = 'just a value';
$this->callPrivate($x);
}
private function callPrivate($x)
{
$method = new \ReflectionMethod(
'A',
'foo'
);
//* for some reason, the private function needs to have been changed to be 'accessible' for this to work in 5.4
$method->setAccessible(true);
//working 5.4 (* see above) but not in 5.5
$arguments = func_get_args();
//not working in either
$arguments = array($x); // <---- COMMENT THIS LINE TO SEE IT WORK IN PHP 5.4
return $method->invokeArgs($this, $arguments);
}
}
$y = new B();
$y->bar();
I can't see why there would be any difference between the two $arguments arrays since var_dumping them shows the same output. Therefore, I assume it is to do with something lower level like object 'pointers' being different (out of my depth here)?
The other question is if this is a bug in PHP 5.4, 5.5 or both?
Prior to PHP 5.5.6 func_get_args() took arguments from the VM stack, copied them and returned them in an array. In PHP 5.5.6 an optimization was introduced which avoids these expensive copies in the common case. Instead of copying the zvals only the refcount is increased (by-ref args notwithstanding.)
Normally such a change would have zero effect on user code. But there are a few places in the engine where observable behavior differs based on the refcount of the zval. One such place is by-reference passing:
For the case of a dynamic function call, a zval can be passed by reference either if it is a reference or if it has refcount==1.
Before PHP 5.5.6 the zvals in the array returned by func_get_args() always had refcount==1, so they went through based on that second case. As of PHP 5.5.6 this is no longer true as by-value zvals will always have refcount>1 and cause an error if you try to pass them by-reference.
Note: The code didn't actually work before PHP 5.5.6 (the by-ref was ignored). It was just an unfortunate coincidence that you didn't get an error telling you so ;)
Update: We decided to revert the change on the 5.5 branch due to the BC break. You will get the old behavior back in PHP 5.5.8 and the new behavior will only be in PHP 5.6.

Memory leak in Magento/Zend Framework

When running this simple script I get the output posted below.
It makes me think that there is a memory leak in either my code or the Zend Framework/Magento stack. This issue occurs when iterating any kind of Magento collection.
Is there anything that I am missing or doing wrong?
Script:
$customersCollection = Mage::getModel('customer/customer')->getCollection();
foreach($customersCollection as $customer) {
$customer->load();
$customer = null;
echo memory_get_usage(). "\n";
}
Output:
102389104
102392920
...
110542528
110544744
Your issue is that you are issuing fairly expensive queries with each iteration, when you could load the necessary data via the collection queries:
$collection = Mage::getResourceModel('customer/customer_collection')->addAttributeToSelect('*');
will do the same, but all in one query. The caveat to this approach is that if there are any custom event observers for customer_load_before or customer_load_after events (there are no core observers for these), the observer will need to be run manually for each data model.
Edit: credit to osonodoar for spotting an incorrect class reference (customer/customer vs customer/customer_collection)
The memory for an object (or other value) can only be freed when there are no references to it anywhere in the PHP process. In your case, the line $customer = null only decreases the number of references to that object by one, but it doesn't make it reach zero.
If you consider a simpler loop, this may become clearer:
$test = array('a' => 'hello');
foreach ( $test as $key => $value )
{
// $value points at the same memory location as $test['a']
// internally, that "zval" has a "refcount" of 2
$value = null;
// $value now points to a new memory location, but $test['a'] is unnaffected
// the refcount drops to 1, but no memory is freed
}
Because you are using objects, there is an added twist - you can modify the object inside the loop without creating a new copy of it:
$test = array('a' => new __stdClass);
// $test['a'] is an empty object
foreach ( $test as $key => $value )
{
// $value points at the same object as $test['a']
// internally, that object has a "refcount" of 2
$value->foo = "Some data that wasn't there before";
// $value is still the same object as $test['a'], but that object now has extra data
// This requires additional memory to store that object
$value = null;
// $value now points to a new memory location, but $test['a'] is unnaffected
// the refcount drops to 1, but no memory is freed
}
// $test['a']->foo now contains the string assigned in the loop, consuming extra memory
In your case, the ->load() method is presumably expanding the amount of data in each of the members of $customersCollection in turn, requiring more memory for each. Inspecting $customersCollection before and after the loop would probably confirm this.
First off, when unsetting variables use unset($variable) instead of $variable=null. It does essentially the same thing, but is much clearer as to your intent.
Second, PHP is meant to die - memory leaks aren't a huge issue, as a PHP request lasts maybe a few seconds, and then the process dies and all memory it was using is freed up for the next request. Unless you are running into scaling issues, it's nothing to worry about.
Edit: which isn't to say don't worry about the quality of your code, but for something like this, its most likely not worth the effort of trying to prevent it from happening unless it is causing problems.
Other way out to handle memory leak is that call exec within loop and let that exec function do the job part which results in memory leak.
So once it completes its part and terminates all memory leak within that exec will be released.
So with huge iterations this memory loss which keeps adding otherwise will be taken care.
#benmarks response would be the right approach here, as calling load() within a loop is a very very expensive call.
Calling $customer->load() would allocate memory incrementally that would be referenced by $customersCollection, that memory won't be released until the end of the loop.
However, if load() needs to be called for any reason, the code below won't leak memory, as the GC releases all the memory allocated by the model in each iteration.
$customersCollection = Mage::getModel('customer/customer')->getCollection();
foreach($customersCollection as $customer) {
$customerCopy = Mage::getModel('customer/customer')->load($customer->getId());
//Call to $customerCopy methods
echo memory_get_usage(). "\n";
}

How to destroy an object?

As far as I know (which is very little) , there are two ways, given:
$var = new object()
Then:
// Method 1: Set to null
$var = null;
// Method 2: Unset
unset($var);
Other better method? Am I splitting hairs here?
You're looking for unset().
But take into account that you can't explicitly destroy an object.
It will stay there, however if you unset the object and your script pushes PHP to the memory limits the objects not needed will be garbage collected. I would go with unset() (as opposed to setting it to null) as it seems to have better performance (not tested but documented on one of the comments from the PHP official manual).
That said, do keep in mind that PHP always destroys the objects as soon as the page is served. So this should only be needed on really long loops and/or heavy intensive pages.
A handy post explaining several mis-understandings about this:
Don't Call The Destructor explicitly
This covers several misconceptions about how the destructor works. Calling it explicitly will not actually destroy your variable, according to the PHP5 doc:
PHP 5 introduces a destructor concept similar to that of other
object-oriented languages, such as C++. The destructor method will be
called as soon as there are no other references to a particular
object, or in any order during the shutdown sequence.
The post above does state that setting the variable to null can work in some cases, as long as nothing else is pointing to the allocated memory.
Short answer: Both are needed.
I feel like the right answer was given but minimally. Yeah generally unset() is best for "speed", but if you want to reclaim memory immediately (at the cost of CPU) should want to use null.
Like others mentioned, setting to null doesn't mean everything is reclaimed, you can have shared memory (uncloned) objects that will prevent destruction of the object. Moreover, like others have said, you can't "destroy" the objects explicitly anyway so you shouldn't try to do it anyway.
You will need to figure out which is best for you. Also you can use __destruct() for an object which will be called on unset or null but it should be used carefully and like others said, never be called directly!
see:
http://www.stoimen.com/blog/2011/11/14/php-dont-call-the-destructor-explicitly/
What is difference between assigning NULL and unset?
May be in a situation where you are creating a new mysqli object.
$MyConnection = new mysqli($hn, $un, $pw, $db);
but even after you close the object
$MyConnection->close();
if you will use print_r() to check the contents of $MyConnection, you will get an error as below:
Error:
mysqli Object
Warning: print_r(): Property access is not allowed yet in /path/to/program on line ..
( [affected_rows] => [client_info] => [client_version] =>.................)
in which case you can't use unlink() because unlink() will require a path name string but in this case $MyConnection is an Object.
So you have another choice of setting its value to null:
$MyConnection = null;
now things go right, as you have expected. You don't have any content inside the variable $MyConnection as well as you already cleaned up the mysqli Object.
It's a recommended practice to close the Object before setting the value of your variable to null.
In PHP you are not destroying an object, you are destroying a pointer to the object. It is a big difference.
In other languages you can destroy an object and all the other pointers will give you exceptions or rubbish, but it is not a case for PHP.
This is a simple prove that you cannot destroy an object, you can only destroy a link to it.
$var = (object)['a'=>1];
$var2 = $var;
$var2->a = 2;
unset($var2);
echo $var->a;
returns
2
See it in action here: https://eval.in/1054130
I would go with unset because it might give the garbage collector a better hint so that the memory can be available again sooner. Be careful that any things the object points to either have other references or get unset first or you really will have to wait on the garbage collector since there would then be no handles to them.

What does PHP's gc_enable function do exactly?

Before you tell me to read the manual, check out the php.net documentation for this function:
Warning
This function is currently not documented; only its argument list is available.
That was helpful!
This page explains that it enables garbage collection for cyclic references. Where and when is this useful? Could someone show me an example of its use? Preferably an example where a cyclic reference is created and then collected.
gc_enable is only needed if you call gc_disable. There is really no sane reason to do this, as that would cause cyclic references to not be garbage collected (like pre-5.3, when the cyclic GC did not exist).
PHP's garbage collector works by reference counting. You can think of a variable as a "pointer" to an object. When an object has no pointers to it, it is "dead" because nothing can reach it, so it is garbage collected.
//one thing points to the Foo object
$a = new Foo();
//now two things do
$b = $a;
//now only $b points to it
$a = null;
//now nothing points to Foo, so php garbage collects the object
$b = null;
Consider this though:
$a = new Foo();
$b = new Bar();
$b->foo = $a;
$a->bar = $b;
$a = $b = null;
At this point nothing is holding on to $a or $b except the objects themselves. This is a cyclic reference, and in previous versions of php (< 5.3), would not be collected. The cyclic collector in 5.3 can now detect this and clean up these objects.
There is a full chapter on Garbage Collection in the PHP Manual explaining this:
Reference Counting Basics
Collecting Cycles
Performance Considerations
I usually try not to just link offsite, but feel it's too much to summarize.
There are reasons why we use gc_disable and gc_enable.
In the latest PHP manual, it states
Can be very useful for big projects, when you create a lot of objects that should stay in memory. So GC can't clean them up and just wasting CPU time.
Issue in composer:
https://github.com/composer/composer/pull/3482#issuecomment-65199153
Solution and people replies:
https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799
Please be reminded that the second link above contains a lot of comments with graphics.

php garbage collection while script running

I have a PHP script that runs on cron that can take up to an 15 minutes to execute. At regular intervals I have it spitting out memory_get_usage() so I can see what is happening. The first time it tells me my usage I am at 10 megs. When the script finishes I am at 114 megs!
Does PHP do it's garbage collection while the script is running? Or what is happening to all that memory? Is there something I can do to force garbage collection. The task that my script is doing is a nightly import of a couple thousand nodes into Drupal. So it is doing the same thing a lot of times.
Any suggestions?
The key is that you unset your global variables as soon as you don't need them.
You needn't call unset explicitly for local variables and object properties because these are destroyed when the function goes out of scope or the object is destroyed.
PHP keeps a reference count for all variables and destroys them (in most conditions) as soon as this reference count goes to zero. Objects have one internal reference count and the variables themselves (the object references) each have one reference count. When all the object references have been destroyed because their reference coutns have hit 0, the object itself will be destroyed. Example:
$a = new stdclass; //$a zval refcount 1, object refcount 1
$b = $a; //$a/$b zval refcount 2, object refcount 1
//this forces the zval separation because $b isn't part of the reference set:
$c = &$a; //$a/$c zval refcount 2 (isref), $b 1, object refcount 2
unset($c); //$a zval refcount 1, $b 1, object refcount 2
unset($a); //$b refcount 1, object refcount 1
unset($b); //everything is destroyed
But consider the following scenario:
class A {
public $b;
}
class B {
public $a;
}
$a = new A;
$b = new B;
$a->b = $b;
$b->a = $a;
unset($a); //cannot destroy object $a because $b still references it
unset($b); //cannot destroy object $b because $a still references it
These cyclic references are where PHP 5.3's garbage collector kicks in. You can explicitly invoke the garbage collector with gc_collect_cycles.
See also Reference Counting Basics and Collecting Cycles in the manual.
PHP garbage collection is largely a reference counter (it does have some cycle detection.) If you are keeping references which are still accessible around these will easily add up if not freed.
Use unset() to free variables you are no longer using. If you simply overwrite variables (eg, with null) this will only allow the GC to reduce to the amount of space required by that variable, but not as much as unset which actually allows the destruction of the referenced value.
You should also properly release any resources etc. that you use.
You will still see memory increase during runtime as the GC is free to release it at its own discression, such as when there are free cpu cycles or when it starts to run low on memory.
Use unset() as much as possible, check used memory more often. yes, php does garbage collection during runtime on a few conditions.
here is a helpful post on php.net.
If the memory is increasing that much, then you are probably not releasing it. You have created a memory leak. Garbage collection won't help you if you don't unset variables, destroy objects and/or they go out of scope.
Are you unsetting the nodes you load once you are done with them? I've written PHP scripts that run for hours, processing millions of database records, with no problems and memory usage that goes up and down within a very acceptable range.

Categories