Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 299589 - EventManager.EventThread.run holding excess memory
Summary: EventManager.EventThread.run holding excess memory
Status: RESOLVED FIXED
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Framework (show other bugs)
Version: unspecified   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.5.2   Edit
Assignee: equinox.framework-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-01-13 21:27 EST by Randall Theobald CLA
Modified: 2010-01-14 13:35 EST (History)
2 users (show)

See Also:
john.arthorne: review+
tjwatson: review+


Attachments
Proposed patch. (1.19 KB, patch)
2010-01-13 21:34 EST, Randall Theobald CLA
tjwatson: iplog+
Details | Diff
Upated patch (1.55 KB, patch)
2010-01-14 13:25 EST, Thomas Watson CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Randall Theobald CLA 2010-01-13 21:27:55 EST
Build Identifier: M20091008-1320

I am a performance analyst for an adopting product. During memory runs (special GC settings to make memory measurements feasible), I have noticed differing values of memory use after our product simply comes up and is initialized (i.e. ready for work). Using core dumps and Eclipse MAT, I have found the problem and it is somewhat interesting. I haven't come across this type of problem in my 3 years of performance work yet.

Here is the method
org.eclipse.osgi.framework.eventmgr.EventManager.EventThread.run:

  /**
   * This method pulls events from
   * the queue and dispatches them.
   */
  public void run() {
    try {
      while (true) {
        Queued item = getNextEvent();
        if (item == null) {
          return;
        }
        EventManager.dispatchEvent(item.listeners, item.dispatcher, item.action, item.object);
      }
      ...
   
The problem is that the loop local variable 'item' retains its previous value while the next call to getNextEvent() is blocked. I wouldn't have guessed it from the code, it looks like it should be cleared before the loop iterates again, but the bytecode shows that the compiler is not doing this. I thought that this might be a compiler bug, but looking at section 2.5.7 of the VM spec, (http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#17203), it looks like the compiler is correct. I quote that section here:

7. Local variables are declared by local variable declaration statements. Whenever the flow of control enters a block or a for statement, a new variable is created for each local variable declared in a local variable declaration statement immediately contained within that block or for statement. The local variable is not initialized, however, until the local variable declaration statement that declares it is executed. The local variable effectively ceases to exist when the execution of the block or for statement is complete. 

So unless there is an explicit clearing of the 'item' variable, either at the end of the loop block or at the beginning before the call to getNextEvent(), the previous value will be retained during the call of the next iteration. This is bad for us as in our product, we are frequently seeing this object holding on to over 20 MB, completely needlessly. When I changed the first lines of the loop in the run() method to this:

  //Queued item = getNextEvent();
  Queued item = null;
  item = getNextEvent();

Then I confirmed in the bytecode and by core dump that the memory was released. 

For us, this represents about 36% of our memory use after the product has loaded an empty workspace and inited itself. In my measurements, memory went from 64 MB without the change above to 41 MB with my change above. This is quite significant.

Since this is not a hot method by any means, there should be no ill effects from making this change.


Reproducible: Always

Steps to Reproduce:
Load up an Eclipse workbench, let it finish init'ing (so the even thread would be blocked in the getNextEvent() method--a Java would show this), take a heap/core/system dump. There will be one Java Local EventManager.EventThread.Queued (which is the item variable in the run method) instance for each EventManager.EventThread instance that is running. This should be cleared before blocking in getNextEvent().
Comment 1 Randall Theobald CLA 2010-01-13 21:34:33 EST
Created attachment 156067 [details]
Proposed patch.
Comment 2 Randall Theobald CLA 2010-01-13 21:35:32 EST
Added a patch for the proposed change. Please backport to at least the 3.5 service stream, even the 3.4 if possible. Thanks.
Comment 3 Thomas Watson CLA 2010-01-14 04:39:54 EST
John, please review for 3.5.2.  Seems like a simple (if not rather strange) change to make.  But since it shows significant memory improvements we should look to backport the fix TO 3.5.2 as well as release to HEAD.
Comment 4 John Arthorne CLA 2010-01-14 09:28:18 EST
Very interesting. It's equivalent, but the code might look a bit less strange if written like this:

while (true) {
  Queued item = getNextEvent();
  if (item == null) {
    return;
  }
  EventManager.dispatchEvent(...);
  //immediately clear item so it can be GC'd. See VM Spec 2.5.7 for why the
  //compiler will not automatically clear this variable for each loop iteration.
  item = null;
}

Anyway, +1 on either Randall's patch or the above
Comment 5 Thomas Watson CLA 2010-01-14 13:25:57 EST
Created attachment 156130 [details]
Upated patch

Updated patch with John's suggestion.  I agree with John that this makes the code a little more understanding.
Comment 6 Thomas Watson CLA 2010-01-14 13:26:34 EST
Comment on attachment 156067 [details]
Proposed patch.

Thanks. Updating for IP log.
Comment 7 Thomas Watson CLA 2010-01-14 13:35:53 EST
Patch released to HEAD and to 3.5.x stream.