Memory Leak

classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|

Memory Leak

aceinc
I have created a monster in Flex V3.0 (SDK 3.5).

The monster is effectively a group of tools that allow me to create
applications with an Oracle DB back end somewhat efficiently. The front end
is quite lovely, and generally works well. However, I am using module
loader, and when I unload modules I do not get my memory back in most cases.

I have succeeded in creating very small modules that do very little, and
they will give up their memory when unloaded, but anything beyond one or two
fields causes the memory leak.

The application that is currently causing the most grief has many (> 100)
main modules each which references a number of of core objects which are sub
classes of things like combo boxes and text input, etc.

Two things I did to try to resolve the issue are;

1) Change every add event to use a weak reference.

2) Added a group of code to override add & remove event listeners, track
them and create a routine that removes all event listeners during the
unloading of a module by looping through every object and running a clear
events method.

Unfortunately I have not been able to find a way to track down the offending
references so that I know where to look to fix things.

This is a mess of my own making, but I sure could use some help tracking
down the culprits and squashing them.

What is my next step?



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
This post was updated on .
To get a flavor for the application, think of a desktop application where
there is a menu bar, from which you select items. These items would bring up
"programs" (modules/panels) which are discreet items that perform very
specific functions. When finished with the "program" it exits (unloads the
module) and waits for the user to select the next "program" to run.

It is of course much more complex than this, and there is some communication
between modules which can be visible at the same time through parent
application or sometimes directly.

However the problem I have is if I load the application, select a single
menu item, close that same menu item 10 times, I end up with 10 occurrences
in the profiler. I also end up with 10x the classes that were called by the
module. For example if I have 5 classes of "X" in the module, after loading
& unloading 10 times there will be 50 occurrences of "X" in the profiler.



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

Alex Harui-2
IIRC, if you can run the Flash Builder profiler, you can try to see what the references are to the objects coming from the modules.

Another technique is to comment out parts of the module until it unloads.

Are there any SWF assets in the modules?  Animations and timer-based/interval-based code tends to pin modules in memory.

Also note that anonymous functions can pin a module as well, especially when the anonymous function is an event handler.  The entire scope chain is pinned while the event handler is attached to a dispatcher.

HTH,
-Alex

On 5/23/18, 4:01 PM, "aceinc" <[hidden email]> wrote:

    To get a flavor for the application, think of a desktop application where
    there is a menu bar, from which you select items. These items would bring up
    "programs" (modules/panels) which are discreet items that perform very
    specific functions. When finished with the "program" it exits (unloads the
    module) and waits for the user to select the next "program" to run.
   
    It is of course much more complex than this, and there is some communication
    between modules which can be visible at the same time through parent
    application or sometimes directly.
   
    However the problem I have is if I load the application, select a single
    menu item, close that same menu item 10 times, I end up with 10 occurrences
    in the profiler. I also end up with 10x the classes that were called by the
    module. For example if I have 5 objects of "X" in the module, after loading
    & unloading 10 times there will be 50 occurrences of "X" in the profiler.
   
   
   
    --
    Sent from: https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fapache-flex-users.2333346.n4.nabble.com%2F&data=02%7C01%7Caharui%40adobe.com%7Cd44f1b28156848461a2d08d5c1010890%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C636627132607933665&sdata=acGI8LRXjCuohRIIvSVca72dKIP84pDmd%2BqhzW1D5po%3D&reserved=0
   

Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
In reply to this post by aceinc
By way of example here is a sample class I have created. The killMe method is
an attempt to make the GC want to take out the trash.

package Classes.Input
{
        import flash.events.DataEvent;
        import flash.events.Event;
        import flash.events.FocusEvent;
        import flash.events.KeyboardEvent;
        import flash.events.MouseEvent;
        import flash.ui.Keyboard;
        import flash.utils.Dictionary;
       
        import mx.controls.ComboBox;
        import mx.events.ListEvent;
       
        /**
         * postValidate Code to execute after validateData method completes.
         *
         **/

        [Event(name="postValidate", type="flash.events.DataEvent")]
       
        /**
         * Create an extended version of the ComboBox that allows the definition of
invalid indexes and
         * multiple keystroke selection.
         *
         * <pre>
         * The developer defines one or more invalid indexes in a comma separated
list using the property badIndexes.
         * Most commonly the developer would specify "0" which is the first element
in the list.
         *
         * New properties are;
         * badIndexes - A string that allows the developer to define a comma
separated list of invalid indexes.
         * isVald - A boolean that defines whether the field is valid.
         *
         * New Methods are;
         * validateData() - This method checks if the data is valid and returns
true if valid, false if invalid.
         *
         * Enhanced Properties;
         * value - When the property value is set, the combobox will attempt to
find the appropriate value in the
         * dataprovider, and set the selectedIndex to that item. It will set the
value to -1 if not found.
         *
         * </pre>
         */
        public class ValidatedComboBox extends ComboBox
        {
                /** Bad indexes - A comma separated list of invalid indexes. */
                private var _badIndexes:String = "";
               
                /** Bad Data - A comma separated list of invalid Data. */
                private var _badData:String = "";
               
                /** Has this field passed validation */
                private var _isValid:Boolean = true;
               
                /** value */
                private var _value:Object;
               
                /** should we validate data */
                private var _doValidateData:Boolean = true;
               
                /** promptLabel */
                private var _promptLabel:String;
               
               
                /** dataType */
                private var _dataType:String = "string";
               
                /** dataField */
                private var _dataField:String = "DATA";
               
                /** toolTipField */
                private var _toolTipField:String = "";
               
                /** Default value A literal that represents the value that will replace
the "value"
                 * property when the method setDefault is executed */
                private var _defaultValue:String = "";
               
                private var _eventHandler:Function = this["checkData"];
               
                private var _tabOut:Function = null;
               
                private var _focusOutTab:Boolean = false;

                private var _typedText:String = "";
               
                public function ValidatedComboBox()
                {
                        //TODO: implement function
                        super();
                        this.addEventListener(Event.CHANGE,_eventHandler,false,0,true)
                }
            override protected function createChildren():void
            {
                super.createChildren();
                       
                        if(this.textInput)
                        { // Use ValidatedTextInput instead of TextInput and Setup various fields
                                textInput["id"] = "vti" + this.id;
// textInput.parentDrawsFocus = true;
                        }
            }
               
                /**
                 * badIndexes- A comma seperated list of indexes that will be considered
invalid.
                 *
                 * @return the specified bad indexes
                 */
                [Inspectable( type="String" , defaultValue="" )]
                public function get badIndexes():String
                {
                        return this._badIndexes;
                }
                /**
                 * Sets the the specified bad indexes
                 */
                public function set badIndexes( badIndexes:String ):void
                {
                        this._badIndexes = badIndexes;
                        if (_badIndexes.length < 1)
                        {
                                errorString = "";
                                _isValid = true;
                        }
                }
               
                /**
                 * badData- A comma seperated list of Data that will be considered
invalid.
                 *
                 * @return the specified bad Data
                 */
                [Inspectable( type="String" , defaultValue="" )]
                public function get badData():String
                {
                        return this._badData;
                }
                /**
                 * Sets the the specified bad Data
                 */
                public function set badData( badData:String ):void
                {
                        this._badData = badData;
                        if (_badData.length < 1)
                        {
                                errorString = "";
                                _isValid = true;
                        }
                }
               
                /**
                 * promptLabel - Specify a human readable label for the field.
                 *
                 * @return the specified promptLabel
                 */

                [Inspectable( type="String" , defaultValue="" )]
                public function get promptLabel():String
                {
                        return this._promptLabel;
                }
                /**
                 * Sets the the specified promptLabel
                 */
                public function set promptLabel( promptLabel:String ):void
                {
                        this._promptLabel = promptLabel;
                }
               
                /**
                 * dataType - specify whether to use numeric or string matching when
setting
                 * the value of this field.
                 *
                 * <p>
                 * Use this field when the drop down's data is a number. Particularly
useful if
                 * the current field in the database is string and can contain leading
zeroes.
                 * </p>  
                 *
                 * @return the specified validate data flag.
                 */
                [Inspectable( type="String" , defaultValue=false,
enumeration="string,number" )]
                public function get dataType():String
                {
                        return this._dataType;
                }
               
                /**
                 * Sets the specified validate data flag.
                 */
                public function set dataType( dataType:String ):void
                {
                        this._dataType = dataType;
                }
               
                /**
                 * doValidateData - specify the whether this field should be validated.
Default is true.
                 *
                 * @return the specified validate data flag.
                 */
                [Inspectable( type="Boolean" , defaultValue=false,
enumeration="true,false" )]
                public function get doValidateData():Boolean
                {
                        return this._doValidateData;
                }
               
                /**
                 * Sets the specified validate data flag.
                 */
                public function set doValidateData( doValidateData:Boolean ):void
                {
                        this._doValidateData = doValidateData;
                }
               
                /**
                 * toolTipField - Specify a field in the dataProvider whose value will be
used as the
                 * to populate the toolTip.
                 *
                 * @return the specified toolTipField
                 */
                [Inspectable( type="String" , defaultValue="" )]
                public function get toolTipField():String
                {
                        return this._toolTipField;
                }
                /**
                 * Sets the the specified toolTipField
                 */
                public function set toolTipField( toolTipField:String ):void
                {
                        this._toolTipField = toolTipField;
                }
               
                /**
                 * defaultValue - allows the specification of a default value for a field.
                 * To set the current text/value properties use the the setDefault()
method.
                 *
                 * @return the specified defaultValue value
                 */
                [Inspectable( type="String" , defaultValue="" )]
                public function get defaultValue():String
                {
                        return this._defaultValue;
                }
                /**
                 * Sets the specified Default value
                 */
                public function set defaultValue( defaultValue:String ):void
                {
                        this._defaultValue = defaultValue;
                        var event:Event;
                }
               
                /**
                 * dataField - the field to be used as the data field from the
dataProvider.
                 *
                 * The value of the field defined here is what the value is set to based
                 * on the selection.  
                 *
                 * @return the specified dataFiel
                 */
                [Inspectable( type="String" , defaultValue="DATA" )]
                public function get dataField():String
                {
                        return this._dataField;
                }
                /**
                 * Sets the the specified dataField
                 */
                public function set dataField( dataField:String ):void
                {
                        this._dataField = dataField;
                }
               
                /**
                 * tabOut - A function to execute if the tabkey is pressed..
                 *
                 * @return the specified tabOut Function
                 */
                [Inspectable( type="Function" , defaultValue="" )]
                public function get tabOut():Function
                {
                        return this._tabOut;
                }
                /**
                 * Sets the the specified tabOut
                 */
                public function set tabOut( tabOut:Function ):void
                {
                        this._tabOut = tabOut;
                }
               
                /**
                 * focusOutTab - A boolean defining if the last focus out was a tab.
                 *
                 * @return  focusOutTab
                 */
                public function get focusOutTab():Boolean
                {
                        return this._focusOutTab;
                }
                /**
                 * Sets the the specified focusOutTab
                 */
                public function set focusOutTab( focusOutTab:Boolean ):void
                {
                        this._focusOutTab = focusOutTab;
                }

                /**
                 * isValid - Returns a boolean that defines whether the current value is
valid.
                 */

                public function get isValid():Boolean
                {
                        return _isValid;
                }
               
                override public function get value():Object
                {
                        return _value;
                }
               
                override public function set dataProvider(dpValue:Object):void
                {
                        var objCurrentValue:Object = _value;
                        super.dataProvider = dpValue;
                        if (this.hasOwnProperty("dropdown"))
                                if (this.dropdown != null)this.dropdown.dataProvider = dpValue;
                        _value = objCurrentValue;
                        if (dpValue != null)
                        {
                                if (dpValue.length > 0)
                                {
                                        if (_value != null)
                                        {
                                                if(_value.toString().length > 0) setSelectedItem(_value);
                                        }
                                        else
                                        {
                                                if (this.selectedItem != null) _value = this.selectedItem[dataField];
                                        }
                  if (this.selectedIndex == -1)
                                        {
                                                this.selectedIndex = 0;
                                                _value = this.selectedItem[dataField];
                                                if (this.selectedItem != null)
                                                        if (this.selectedItem.hasOwnProperty(dataField))
                                                        {
                                                                _value = this.selectedItem[dataField];
                                                                dispatchEvent( new ListEvent( ListEvent.CHANGE ) );
                                                        }
                                        }
                                }
                        }
                        dpValue = null;
                        objCurrentValue = null;
                }
               
                override public function set selectedIndex(intValue:int):void
                {
// if (this.hasOwnProperty("id")) trace("id=" + this.id + " set intValue="
+ intValue);
                        super.selectedIndex = intValue;
                        if (intValue > -1)
                        {
                                if (this.selectedItem != null)
                                {
                                        if (this.selectedItem.hasOwnProperty(dataField))
                                        {
                                                 _value = this.selectedItem[dataField];
                                        }
                                }
                        }
                }
               
                /**
                 * Sets the the specified value based on the item selected.
                 */
               
                public function set value(value:Object): void
                {
// if (this.hasOwnProperty("id")) trace("id=" + this.id + " set value=" +
value);
// trace("ValidatedComboBox-" + this.id + "Set value=" + value);
                        this.selectedIndex = 0;
                        if (value != null)
                        {
                                setSelectedItem(value);
                                _value = value;
                        }
                        else
                        {
                                _value = "";
                        }
  if (this.selectedIndex > -1)
                        {
                                if (this.selectedItem != null)
                                        if (this.selectedItem.hasOwnProperty(dataField))
                                        {
                                                 _value = this.selectedItem[dataField];
                                        }
                        }
                }

                private function checkData(event:Event):void
                {
                        this.removeEventListener(Event.CHANGE,_eventHandler)
                        if (this.selectedItem)
                        {
                                this.
                                _value = this.selectedItem[dataField];
                                validateData();
                        }
                        this.addEventListener(Event.CHANGE,_eventHandler,false,0,true)
                }

                /**
                 * setDefault - Method that will replace the value of the value property
with the
                 * value of the defaultValue property.
                 */
                 
                public function setDefault():void
                {
                        _value = _defaultValue;
                        setSelectedItem(_value);
                }
               
                /**
                 * getDefault - Method that will return the value of the defaultValue
property.
                 *
                 * This method returns the defaultValue it exists to provide a consistenet
api
                 * between all "validated" components.
                 */
                 
                public function getDefault():String
                {
                        return _defaultValue;
                }

                /**
                 * validateData - Method that will validate the data that has been
entered.
                 */

                public function validateData():Boolean
                {
                        if (_badIndexes.length == 0 && _badData.length == 0)
                        {
                                this.dispatchEvent(new DataEvent("postValidate"));
                                return true;
                        }
                        var aryIndexesTemp:Array = _badIndexes.split(",");
                        if (_doValidateData)
                        {
                                _isValid = ((aryIndexesTemp.indexOf(this.selectedIndex.toString()) ==
-1) &&
                                                        (this.selectedIndex > -1));
                                if (_isValid && _badData.length > 0)
                                {
                                        var aryDataTemp:Array = _badData.split(",");
                                        if (this.selectedIndex > -1) _isValid =
(aryDataTemp.indexOf(this.dataProvider[this.selectedIndex][dataField].toString())
== -1);
                                }
                                if (!_isValid)
                                {
                                        this.errorString = '"' + this.selectedLabel + '" is an invalid choice.
' +
                                                                                         this.selectedLabel.toString();
                                }
                                else
                                {
                                        this.errorString = "";
                                }
                        }
                        this.dispatchEvent(new DataEvent("postValidate"));
                        return _isValid;
                }


                private function setSelectedItem(strFindItem:Object):void
                {
                        var strDataItem:String = "";
                        if (this.dataProvider != null)
                        {
                                this.selectedIndex = -1;
                                if (_dataType == "number")
                                {
                                        strFindItem = String(parseInt(strFindItem.toString(),10));
                                }
                                for (var i:int=0;i<this.dataProvider.length;i++)
                                {
                                        if (this.dataProvider[i] != null)
                                        {
                                                if (_dataType == &quot;number&quot;)
                                                {
                                                        strDataItem =
String(parseInt(this.dataProvider[i][dataField].toString(),10));
                                                }
                                                else
                                                {
                                                        strDataItem = this.dataProvider[i][dataField].toString();
                                                }
                                                if (strDataItem == strFindItem &amp;&amp; strDataItem.length ==
strFindItem.toString().length)
                                                {
                                                        this.selectedIndex = i;
          if (_toolTipField.length > 0)
                                                        {
                                                                this.toolTip = this.dataProvider[i][_toolTipField]
                                                        }
// dispatchEvent( new ListEvent( ListEvent.CHANGE ) );
                                                        break;
                                                }
                                        }
                                }
                        }
                }
               
  override protected function focusOutHandler(event:FocusEvent):void
                {
                        super.focusOutHandler(event);
                        _typedText = "";
                        validateData()
                }
               
                override protected function focusInHandler(event:FocusEvent):void
                {
                        this.removeEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler);
                        super.focusInHandler(event);
                        _typedText = "";
                        _focusOutTab = false;
               
this.addEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler,false,0,true);
                }

                override protected function textInput_changeHandler(event:Event):void
                {
                        _typedText += this.textInput.text;
// trace('_typedText=' + _typedText);
  if (!findFirstItem(_typedText))
                        {
                                _typedText = _typedText.substr(0,_typedText.length -1);
                                findFirstItem(_typedText);
                        }
// this.dispatchEvent(new Event(Event.CHANGE,true));
                }
                override protected function keyDownHandler(event:KeyboardEvent):void
                {
// trace("event="+event.toString());
                        this.removeEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler);
                        event.preventDefault();
                        if (event.keyCode == Keyboard.TAB)_focusOutTab = true;
                        if ((event.keyCode == Keyboard.TAB && _tabOut == null) || event.keyCode
== Keyboard.ENTER) return;
                        if (event.keyCode == Keyboard.TAB)
                        {
                                _tabOut();
                        }
     if(!event.ctrlKey)
                        {
                                if (event.keyCode == Keyboard.BACKSPACE || event.keyCode ==
Keyboard.DELETE)
                                {
                                        _typedText = _typedText.substr(0,_typedText.length -1);
                                        findFirstItem(_typedText);
                                }
                                if (event.keyCode == Keyboard.DOWN || event.keyCode == Keyboard.RIGHT)
                                {
                                        _typedText = "";
                                        if (this.dropdown.selectedIndex < this.dataProvider.length - 1)
                                        {
                                                this.selectedIndex++;
                                                this.dropdown.selectedIndex++;
                                                this.dropdown.scrollToIndex(this.dropdown.selectedIndex);
                                                _value = this.selectedItem[dataField];
                                        }
                                }
                                if (event.keyCode == Keyboard.UP || event.keyCode == Keyboard.LEFT)
                                {
                                        _typedText = "";
                                        if (this.dropdown.selectedIndex > 0)
                                        {
                                                this.selectedIndex--;
                                                this.dropdown.selectedIndex--;
                                                this.dropdown.scrollToIndex(this.dropdown.selectedIndex);
                                                _value = this.selectedItem[dataField];
                                        }
                                }
                                if ((event.charCode > 31) && (event.charCode < 128))
                                {
                                        _typedText += String.fromCharCode(event.charCode);
          if (!findFirstItem(_typedText))
                                        {
                                                _typedText = _typedText.substr(0,_typedText.length -1);
                                        }
                                }
  }
// trace("_typedText=" + _typedText);
               
this.addEventListener(KeyboardEvent.KEY_DOWN,this.keyDownHandler,false,0,true);
//    super.keyDownHandler(event);
  }
 
                private function findFirstItem(strFindItem:String):Boolean
                {
                        if (this.dataProvider != null)
                        {
                                if (strFindItem.length == 0)
                                {
                                        this.selectedIndex = 0;
                                        this.dropdown.selectedIndex = 0;
                                        this.dropdown.scrollToIndex(0);
                                        _value = this.selectedItem[dataField];
                                        dispatchEvent( new ListEvent( ListEvent.CHANGE ) );
                                        return true;
                                }
                                this.dispatchEvent(new MouseEvent("mouseOut"));
                                for (var i:int=0;i<this.dataProvider.length;i++)
                                {
                                        if
(this.dataProvider[i][this.labelField].toString().substr(0,strFindItem.length).toUpperCase()
== strFindItem.toUpperCase())
                                        {
                                                this.selectedIndex = i;
                                                this.dropdown.selectedIndex = i;
                                                this.dropdown.scrollToIndex(i);
                                                _value = this.selectedItem[dataField];
  if (_toolTipField.length > 0)
                                                {
                                                        this.toolTip = this.dataProvider[i][_toolTipField]
                                                        this.dispatchEvent(new MouseEvent("mouseOver"));
                                                }
                                                dispatchEvent( new ListEvent( ListEvent.CHANGE ) );
                                                return true;
                                        }
                                }
                        }
                        return false;
                }
               
            public function killMe():void
                {
                        trace('unloading='+this.id);
                        this.dataProvider = null;
                        this.dropdown.dataProvider = null;
                        this.selectedIndex = -1;
                        this.selectedItem = null;
                        this.value = null;
                        callLater(clearEvents);
                }
               
                protected var dctListeners:Dictionary = new Dictionary(); // added
05/22/2018
                protected var bolInClearEvents:Boolean = false;
               
                override public function addEventListener(type:String, listener:Function,
useCapture:Boolean = false, priority:int =
                                                                                                  0, useWeakReference:Boolean = true):void
                {
                        if (bolInClearEvents == false)
                        {
                                var key:Object = {type: type, useCapture: useCapture};
                                if (dctListeners[key])
                                {
                                        removeEventListener(type, dctListeners[key], useCapture);
                                        dctListeners[key] = null;
                                }
                                dctListeners[key] = listener;
                                super.addEventListener(type, listener, useCapture, priority,
useWeakReference);
                        }
                }
               
                public function clearEvents():void
                {
                        bolInClearEvents = true;
                        try
                        {
                                for (var key:Object in dctListeners)
                                {
                                        removeEventListener(key.type, dctListeners[key], key.useCapture);
                                        dctListeners[key] = null;
                                }
                        }
                        catch (e:Error)
                        {
                                trace('TestBig-clearEvents error='+e.message);
                        }
                        dctListeners = null;
                }
       

        }
}



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
This is a traditional business application. There are no videos or other
gimcracks.

However some of the modules have multiple tabs with close to 200 (maybe
more) data entry objects, including enhanced versions of the combo box, text
input, text area, etc.



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

Alex Harui-2
In general, child UI widgets don't cause leaks unless they attach something to the stage.  So ComboBox is always suspect because it has a dropDown that is attached to the SystemManager/Stage.  A few other components listen for events from the stage.

Also, I want to remind you that foo.addEventListener("someEvent", this.someHandler) creates a reference from foo to the 'this' object.  What you are tracking in your addEventListener override appears to be who is attaching listeners to objects you control the code for, but the leak often stems from module code attaching listeners to objects outside of the module, like the stage, the main app, data objects coming from a server.

Hopefully one of the techniques I suggested earlier will help you figure it out.

HTH,
-Alex

On 5/23/18, 7:25 PM, "aceinc" <[hidden email]> wrote:

    This is a traditional business application. There are no videos or other
    gimcracks.
   
    However some of the modules have multiple tabs with close to 200 (maybe
    more) data entry objects, including enhanced versions of the combo box, text
    input, text area, etc.
   
   
   
    --
    Sent from: https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fapache-flex-users.2333346.n4.nabble.com%2F&data=02%7C01%7Caharui%40adobe.com%7C52a3974da3d141c52fce08d5c11d9450%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C636627255140560646&sdata=kNoHoiaHksF41QSqzuux943JuO8xoEoKlJabzUmP%2BYE%3D&reserved=0
   

Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
Alex, I have implemented the code to destroy event listeners in all of my
widgets, as well as the calling class. The calling class is called from the
root as a module. Before unloading the module I do the following to try and
remove all event listeners;

private function clearModuleEvents(mdlModule:ModuleLoader):void
{
        trace('Starting clearModuleEvents')
    for(var idx:int = mdlModule.numChildren - 1; idx >= 0; idx--)
    {
    trace('idx=' + idx + 'isContainer(objChild)=' +isContainer(objChild) +
'isContainer(mdlModule.getChildAt(idx))=' +
isContainer(mdlModule.getChildAt(idx)));
        var objChild:DisplayObject = mdlModule.getChildAt(idx);
        trace('flash.utils.getQualifiedClassName(objChild)=' +
flash.utils.getQualifiedClassName(objChild));
        var childContainer:DisplayObjectContainer = objChild as
DisplayObjectContainer;
        clearChildEvents(childContainer);
                if ("clearEvents" in objChild)
                {
                        var fncClearEvents:Function = objChild["clearEvents"];
            if (objChild.hasOwnProperty('id')) trace('Clearing Events in==>>' +
objChild['id']);
                        fncClearEvents();
                        fncClearEvents = null;
                }
                if ("dataProvider" in objChild)objChild["dataProvider"] = null;
                if ("selectedIndex" in objChild)objChild["selectedIndex"] = -1;
                if ("selectedItem" in objChild)objChild["selectedItem"] = null;
                if ("value" in objChild)objChild["value"] = null;
    }
    objChild = null;
        trace('Finished clearModuleEvents')
}

private function clearChildEvents(dpsContainer:DisplayObjectContainer):void
{
    for(var idx:int = dpsContainer.numChildren - 1; idx >= 0; idx--)
    {
        var objChild:DisplayObject = dpsContainer.getChildAt(idx);
                if (isContainer(objChild))
                {
                var childContainer:DisplayObjectContainer = objChild as
DisplayObjectContainer;
                clearChildEvents(childContainer);
                }
                if ("killMe" in objChild)
                {
                        var fncKillMe:Function = objChild["killMe"];
            if (objChild.hasOwnProperty('id')) trace('Killing Events in==>>' +
objChild['id']);
                        fncKillMe();
                        fncKillMe = null;
                }
                else if ("clearEvents" in objChild)
                {
                        var fncClearEvents:Function = objChild["clearEvents"];
            if (objChild.hasOwnProperty('id')) trace('Clearing Events in==>>' +
objChild['id']);
                        fncClearEvents();
                        fncClearEvents = null;
                }
                if ("dataProvider" in objChild)objChild["dataProvider"] = null;
                if ("selectedIndex" in objChild)objChild["selectedIndex"] = -1;
                if ("selectedItem" in objChild)objChild["selectedItem"] = null;
// if ("value" in objChild)objChild["value"] = null;
                objChild = null;
    }
}





--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

Alex Harui-2
Hi,

I might be missing something, but AFAICT, even if all of that works perfectly, you still don't know if your code has caused some Flex framework code to attach a listener to the stage, a Timer, a network class, etc.  IOW, I think you can run code like:

import mx.controls.ComboBox;
var cb:ComboBox = new ComboBox();
addChild(cb);
trace(cb.dropDown);

IIRC, simple code like this can pin a module into memory, and your clean up code won't catch it.

HTH,
-Alex

On 5/24/18, 1:30 PM, "aceinc" <[hidden email]> wrote:

    Alex, I have implemented the code to destroy event listeners in all of my
    widgets, as well as the calling class. The calling class is called from the
    root as a module. Before unloading the module I do the following to try and
    remove all event listeners;
   
    private function clearModuleEvents(mdlModule:ModuleLoader):void
    {
    trace('Starting clearModuleEvents')
        for(var idx:int = mdlModule.numChildren - 1; idx >= 0; idx--)
        {
        trace('idx=' + idx + 'isContainer(objChild)=' +isContainer(objChild) +
    'isContainer(mdlModule.getChildAt(idx))=' +
    isContainer(mdlModule.getChildAt(idx)));
            var objChild:DisplayObject = mdlModule.getChildAt(idx);
            trace('flash.utils.getQualifiedClassName(objChild)=' +
    flash.utils.getQualifiedClassName(objChild));
            var childContainer:DisplayObjectContainer = objChild as
    DisplayObjectContainer;
            clearChildEvents(childContainer);
    if ("clearEvents" in objChild)
    {
    var fncClearEvents:Function = objChild["clearEvents"];
        if (objChild.hasOwnProperty('id')) trace('Clearing Events in==>>' +
    objChild['id']);
    fncClearEvents();
    fncClearEvents = null;
    }
    if ("dataProvider" in objChild)objChild["dataProvider"] = null;
    if ("selectedIndex" in objChild)objChild["selectedIndex"] = -1;
    if ("selectedItem" in objChild)objChild["selectedItem"] = null;
    if ("value" in objChild)objChild["value"] = null;
        }
        objChild = null;
    trace('Finished clearModuleEvents')
    }
   
    private function clearChildEvents(dpsContainer:DisplayObjectContainer):void
    {
        for(var idx:int = dpsContainer.numChildren - 1; idx >= 0; idx--)
        {
            var objChild:DisplayObject = dpsContainer.getChildAt(idx);
    if (isContainer(objChild))
    {
           var childContainer:DisplayObjectContainer = objChild as
    DisplayObjectContainer;
           clearChildEvents(childContainer);
    }
    if ("killMe" in objChild)
    {
    var fncKillMe:Function = objChild["killMe"];
        if (objChild.hasOwnProperty('id')) trace('Killing Events in==>>' +
    objChild['id']);
    fncKillMe();
    fncKillMe = null;
    }
    else if ("clearEvents" in objChild)
    {
    var fncClearEvents:Function = objChild["clearEvents"];
        if (objChild.hasOwnProperty('id')) trace('Clearing Events in==>>' +
    objChild['id']);
    fncClearEvents();
    fncClearEvents = null;
    }
    if ("dataProvider" in objChild)objChild["dataProvider"] = null;
    if ("selectedIndex" in objChild)objChild["selectedIndex"] = -1;
    if ("selectedItem" in objChild)objChild["selectedItem"] = null;
    // if ("value" in objChild)objChild["value"] = null;
    objChild = null;
        }
    }
   
   
   
   
   
    --
    Sent from: https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fapache-flex-users.2333346.n4.nabble.com%2F&data=02%7C01%7Caharui%40adobe.com%7C110b4012ca394ee4b27b08d5c1b53108%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C636627906328800733&sdata=DIbMjs4iq5lHDN1NDhD8dKXQI9vdXxp71xlu4VcW9ys%3D&reserved=0
   

Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
I never use combobox directly, I always use "ValidatedCombobox" which has my
clearEvents method in it. Both ValidatedComboBox as well as the class (the
module that is loaded) that has the code;

var cb:ComboBox = new ValidatedComboBox();
addChild(cb);
trace(cb.dropDown);

in it, have the overridden event handler as well as the clearEvents code.
Would it not clear all of the events created explicitly as well as
implicitly when clearModuleEvents was call during just prior to unload?



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

Alex Harui-2
AIUI, no.  If ValidatedComboBox subclasses mx.controls.ComboBox but doesn't override the DropDown, then the dropDown will make itself a child of the Stage and may also attach a listener to the Stage (stage.addEventListener).  I don't think your code can override the Stage's addEventListener so any cleanup calls will not work.   I think you would have to wrap every UIComponent in the framework.

I still think using the profiler or commenting out code is your best bet.

HTH,
-Alex

On 5/24/18, 2:13 PM, "aceinc" <[hidden email]> wrote:

    I never use combobox directly, I always use "ValidatedCombobox" which has my
    clearEvents method in it. Both ValidatedComboBox as well as the class (the
    module that is loaded) that has the code;
   
    var cb:ComboBox = new ValidatedComboBox();
    addChild(cb);
    trace(cb.dropDown);
   
    in it, have the overridden event handler as well as the clearEvents code.
    Would it not clear all of the events created explicitly as well as
    implicitly when clearModuleEvents was call during just prior to unload?
   
   
   
    --
    Sent from: https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fapache-flex-users.2333346.n4.nabble.com%2F&data=02%7C01%7Caharui%40adobe.com%7C21ce82c537da4a35661e08d5c1bb3abf%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C636627932246987998&sdata=ttGJoB%2F3lrApmzYWeXJuChZMpQ9cr0oAoBZ0bxJwWuc%3D&reserved=0
   

Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

aceinc
Alex, I took a different but similar approach. I created a small class, and
kept adding stuff to it until it failed. I determined it failed using the
profiler. I did this back in December, but resolving this seems like it will
take a fair amount of dedicated sleuthing. Large blocks of time can be hard
to come by.

So how would you resolve the issue where the drop down event listener is
added to the stage? I assume this is an example of a number of similar
things that can bite me in the butt.

Can I override the event listeners in the stage? If so, how do I determine
which events are pointing to things that I have removed, or am about to
remove?

Is there a way to tell all event listeners to use weak references?



--
Sent from: http://apache-flex-users.2333346.n4.nabble.com/
Reply | Threaded
Open this post in threaded view
|

Re: Memory Leak

Alex Harui-2
Unfortunately, you can't override event listeners in Flash.  But the framework code is theoretically designed to not cause pin modules in memory when used "properly".  The ComboBox dropdown probably isn't the culprit here, I was mainly using it to illustrate that your clean up code can't clean up everything.

There is another technique to try to figure out what is pinning the module in memory.  You can create a module loading test harness.  It loads the module using ModuleManager instead of ModuleLoader.  When the module loads, you run code that unloads it.  That tests that the module doesn't have any initialization code that is a problem.  If it doesn't unload, then you know to examine the initialization code in the module.  If it does unload, then you enhance the harness to put the module on the display list.  Then once it is on the display list, find some way to unload it without interacting with it, maybe a timer, or a button elsewhere in the UI.  That tests that the property validation code is a factor or not.  And so on.

Another technique I use is to stop in the debugger when the module doesn't unload and validate the number of children on the Stage, the SystemManager.rawChildren.  Because if something really is still attached at that level, that will be a problem.

BTW, are you using embedded fonts or remote classes that are unique to a module?  I think they can pin a module.  Anything that registers with a central registry.

It is rarely an easy or quick process, but maybe you'll see a pattern after a few modules so you will know where to look in the others.

HTH,
-Alex

On 5/24/18, 7:38 PM, "aceinc" <[hidden email]> wrote:

    Alex, I took a different but similar approach. I created a small class, and
    kept adding stuff to it until it failed. I determined it failed using the
    profiler. I did this back in December, but resolving this seems like it will
    take a fair amount of dedicated sleuthing. Large blocks of time can be hard
    to come by.
   
    So how would you resolve the issue where the drop down event listener is
    added to the stage? I assume this is an example of a number of similar
    things that can bite me in the butt.
   
    Can I override the event listeners in the stage? If so, how do I determine
    which events are pointing to things that I have removed, or am about to
    remove?
   
    Is there a way to tell all event listeners to use weak references?
   
   
   
    --
    Sent from: https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fapache-flex-users.2333346.n4.nabble.com%2F&data=02%7C01%7Caharui%40adobe.com%7Caf9c1574056e4a6fd36408d5c1e8a0b7%7Cfa7b1b5a7b34438794aed2c178decee1%7C0%7C0%7C636628127235729630&sdata=BUHpt%2Ffh7WjMM7U6WAIq9VGMSeDsgNmvrgv8w4y7Loc%3D&reserved=0