tags:

views:

534

answers:

3

Hi,

I'm currently working on a GWT application as a proof of technology for future projects. I like the way of building my AJAX code in Java instead of JavaScript. But I seem to be running into a memory problem when I repeat calls to an RPC service. The browser memory usage keeps growing and growing.

When searching Google I keep reading about how great GWT is and that its impossible to get memory leaks so can anyone explain why my browser (Firefox and Chromium) memory is rocketing?

Thanks in advance for helping me, Bram

The code:

...

class ExampleTable extends Composite

  private RPCService rpcService;
  private Timer tableUpdater;

  public ExampleTable(){
   ... Init timer and RPC Service
   ... Add components
   initWidget();
  }

  private void getTableDataFromRPCService() {
  this.rpcService.getData(new AsyncCallback<ArrayList<Data>>() {

      @Override
      public void onSuccess(ArrayList<Data> result) {
          ExampleTable.this.updateTable(result);
      }

      @Override
      public void onFailure(Throwable caught) {
          //Do nothing
      }
  });
  }

  private void updateTable(ArrayList<Data> tableData){
    ... Update the table
  }

  private void startUpdateTask() {
      this.serviceUpdater = new Timer() {
          @Override
          public void run() {
            ExampleTable.this.getTableDataFromRPCService();
          }
      };
      serviceUpdater.scheduleRepeating(2000);
  }
}

EDIT:

I've spent some time to write a test application which can be downloaded here. I ran the application for about half an hour with the table update enabled after that Firefox took about 350MB of memory. I also ran the test with the update table disabled for an hour memory usage in Firefox went to little over 100MB.

(To run this sample you need the Google visualization API for GWT which can be downloaded from Google but I'm not allowed to post the link because of a new user policy )

I just got home from work and start another test without the table data update to see if memory usage keeps increasing or if it stops at a certain point.

This is the client implementation class (GWTMemoryIssue.java):

public class GWTMemoryIssue implements EntryPoint {
//Run with or without table
private static final boolean WITH_TABLE = false; 

private final TestServiceAsync rpcService = GWT.create(TestService.class);

private Panel panel;
private Timer timer;
private Table table;

public void onModuleLoad() {
    RootPanel rootPanel = RootPanel.get();

    this.panel = new VerticalPanel();
    this.panel.setSize("100%", "100%");

    rootPanel.add(panel);

    if (WITH_TABLE) {
        loadTable();
    }else{
        startUpdateTask();
    }

}

private void startUpdateTask() {
    this.timer = new Timer() {

        @Override
        public void run() {
            GWTMemoryIssue.this.getTableData();

        }
    };
    this.timer.scheduleRepeating(2000);
}

public void loadTable() {
    Runnable onLoadCallback = new Runnable() {
        public void run() {
            GWTMemoryIssue.this.table = new Table(createTableData(), createTableOptions());
            GWTMemoryIssue.this.table.setSize("100%", "100%");
            GWTMemoryIssue.this.panel.add(GWTMemoryIssue.this.table);
            GWTMemoryIssue.this.startUpdateTask();
        }
    };

    VisualizationUtils.loadVisualizationApi(onLoadCallback, Table.PACKAGE);
}

private Options createTableOptions() {
    Options options = Options.create();

    return options;
}

private DataTable createTableData() {
    DataTable data = DataTable.create();

    data.addColumn(ColumnType.STRING, "Name");
    data.addColumn(ColumnType.NUMBER, "Intval 1");
    data.addColumn(ColumnType.NUMBER, "Intval 2");
    data.addColumn(ColumnType.NUMBER, "Intval 3");

    return data;
}

private void getTableData() {
    rpcService.getListOfItems(new AsyncCallback<ArrayList<ListItem>>(){
        public void onFailure(Throwable caught) {
            // Do nothing
        }

        public void onSuccess(ArrayList<ListItem> result) {
            if (WITH_TABLE){
                GWTMemoryIssue.this.updateTableData(result);
            }else{
                //Ignore the data from the server
            }
        }
    });
}

private void updateTableData(ArrayList<ListItem> result) {
    DataTable data = createTableData();

    data.addRows(result.size());

    int row = 0;
    for (ListItem li : result) {
        data.setValue(row, 0, li.getName());
        data.setValue(row, 1, li.getIntVal());
        data.setValue(row, 2, li.getIntSecondVal());
        data.setValue(row, 3, li.getThirdIntVal());
        row++;
    }

    this.table.draw(data, createTableOptions());
}
}
+1  A: 

I've been using GWT quite some time with many tables and RPCs and until now most memory leaks I found were my own fault.

The RPC layer does not seem to leak as far as I know and your example is just too simple to pose a problem.

You might need to take a look at what the updateTable method is actually doing, you might have a leak in your own code.

One thing that can cause huge memory leaks with GWT are Imagebundles in IE. It is a known fact that these leak extremely in GWT because they are using DXTransform to support alpha transparency. The memory goes up in large chunks everytime widgets are put on the screen. But there are tricks to avoid this.

David Nouls
I'm pretty new to GWT and started a few weeks ago with version 1.7. My first guess was that the update table logic was causing the problem, so I started to comment out the update logic and just do noting than the RPC call.Do I need to do some manual cleanup of the objects received using the RPC call? For the other pages I've created this problem didn't come up because they are not refreshing the data continuously. Is there some kind of a best practice for using RPC to fetch data from a server and keep refreshing the data?I'm not using any ImageBundles and IE isn't shipped with Ubuntu :)
Bram Pouwelse
If it is possible can you post the code for updatetable method. I think you are fetching new set of data continuously without cleaning up the objects or re-using them. but since we dont have code, I cant comment if that is "the" problem.
sbidwai
See next answer, the size of comments is limited to 600 chars.You should maybe consider giving us some more code to look at.
David Nouls
A: 

No there is no explicit operation needed to clean garbage in Javascript. It is supposed to run automatically (although the GC in the browser is not on the same level as in a modern JVM).

GWT does it best to avoid common pitfalls that would cause memory leaks in JS (circular references between JS and DOM nodes are badly handled in some browsers).

So the question is: is the memory usage always going up ? Or does it top out at a certain point (or it just crashes with some out of memory ?). It can be normal that your application seems to be growing and growing... but GC should kick in at some point.

In my application memory usage tops out at about 64MB. But I am not working on Ubuntu, IE on windows is our main target, although I sometimes test on FireFox as wel (and there I don't see a leak either).

One other thing you might need to do is to avoid polling every 2 seconds like you do. If a request takes longer than 2 secs you start queing up the requests (a browser has a limitation on the number of concurrent connections). so it's best to wait for the response before firing a new timer.

David Nouls
Thanks for trying to help met out!The 2 second polling is just to speed up the testing process, the real application will just update every minute or 30 seconds depending on the data which is displayed. But it's a page which tends to be open for several hours at the end user computer.I can't post the application code itself but I've updated the initial post and added another code sample and the complete test application to demo my problem.
Bram Pouwelse
Hmmm... the code you are showing has a lot of references to code I am not seing. so I am afraid I can not help you any further.What is this DataTable you are using ?You are also using a visualizer... does it leak without this visualisation enabled ?
David Nouls
A: 

With all the additional information you provided here are some thoughts. I guess the memory increase is caused by the lists remaining in memory. It's either possible the memory is not freed at all or the JavaScript garbage collector doesn't get time to clean up, due too the short time frame between updates. Here are some test you could do:

  1. To test if the garbage collector doesn't get time adapt you code such that the update only runs a finite number of times, and then check if the memory usage decreases over a few minutes. If the memory usage decreases, than it might be less of an issue in you real world example. But simple test it by setting the delay to the 30 seconds.
  2. You could help the garbage collector by clearing the list after they are used: I don't know what works best, so here are some suggestions: Remove object from the list, or loop over the list and set values to null. This should not be necessary in normal case, because the garbage collector would do it.

You can also try the following Firefox Memory profiler add-on to see if you can locate the memory increases: http://ajaxian.com/archives/enhanced-firefox-memory-profiler-add-on

Hilbrand