JMEDesktop & JScrollPane/JTable's memory performance

Hello, I rarely post here, but I’m a bit stumped with this one :slight_smile:



JTable with a surrounding JScrollPane (when used in JMEDesktop) seems to go through some serious memory problems when you try to scroll it.



I’ve created a Test file consisting of a simple JTable of just 44 rows and 5 columns, with simple text data. I also added a button that will remove the table and force a garbage collection to happen.



Loading it up is fine, the memory is acceptable at this point and is comparable to running the table in a normal Swing application. However if you scroll up and down you’ll notice a huge leap in memory usage (a normal swing app wouldn’t do this).



Here’s an image of the memory usage:







At position (1) I’ve loaded the application but not clicked on anything yet. Usage here is 59 MB

At position (2) I’ve scrolled the table to the very bottom.

At position (3) I’ve scrolled it back up to the top. Notice that a bit of automatic garbage collection happened as I was scrolling upwards.

After here I rapidly move the scrollbar up and down for a while which resulted in the spikeyness between (3) and (4). This peaks at a usage of a whopping 340 MB

I then pressed the button removing the table and forcing a garbage collection which then puts me at position (4) 66 MB.



So I don’t think there’s a memory leak since position (4) has almost the same usage as position (1), however there’s clearly an issue of it using far more memory than needed to scroll such a simple table.



Running the same test in a normal swing app results in an almost constant memory usage throughout.



Here’s the test case that I used to get the results above (code borrowed in part from TestJMEDesktop.java):



TestJTable.java:

[java]

/*

  • Copyright © 2003-2009 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */



    package jmetest.awt.swingui;



    import java.awt.Color;

    import java.awt.event.ActionEvent;

    import java.lang.reflect.InvocationTargetException;

    import java.net.URL;



    import javax.swing.JDesktopPane;

    import javax.swing.JScrollPane;

    import javax.swing.SwingUtilities;



    import com.jme.app.SimpleGame;

    import com.jme.image.Image;

    import com.jme.image.Texture;

    import com.jme.input.AbsoluteMouse;

    import com.jme.input.InputHandler;

    import com.jme.input.KeyboardLookHandler;

    import com.jme.renderer.ColorRGBA;

    import com.jme.renderer.Renderer;

    import com.jme.scene.Node;

    import com.jme.scene.Spatial;

    import com.jme.scene.state.BlendState;

    import com.jme.scene.state.TextureState;

    import com.jme.system.DisplaySystem;

    import com.jme.util.TextureManager;

    import com.jmex.awt.swingui.JMEDesktop;

    import java.awt.event.ActionListener;

    import javax.swing.JButton;

    import javax.swing.JTable;

    import javax.swing.table.DefaultTableModel;



    /

    *

    */

    public class TestJMEJTable extends SimpleGame

    {

    private JMEDesktop jmeDesktop;

    private Node desktopNode;

    private KeyboardLookHandler lookHandler;



    public TestJMEJTable()

    {

    }



    protected void simpleUpdate()

    {

    if ( jmeDesktop.getFocusOwner() == null )

    {

    lookHandler.setEnabled( true );

    }

    else

    {

    lookHandler.setEnabled( false );

    }

    }



    public static void main( String[] args ) throws Exception

    {

    TestJMEJTable testJMEJTable = new TestJMEJTable();

    testJMEJTable.setConfigShowMode( ConfigShowMode.AlwaysShow );

    testJMEJTable.start();

    }



    /

  • Called near end of initGame(). Must be defined by derived classes.

    */

    protected void simpleInitGame()

    {

    display.setTitle( “jME-JTable test” );

    display.getRenderer().setBackgroundColor( ColorRGBA.red.clone() );



    // move the ‘default’ keys (debug normals, toggle lighting, etc.) to a separated input handler

    InputHandler handlerForDefaultKeyActions = input;

    // remove the first person nested handlers

    handlerForDefaultKeyActions.removeAllFromAttachedHandlers();

    // create a new handler for our input

    input = new InputHandler();

    // add the default handler as a child

    input.addToAttachedHandlers( handlerForDefaultKeyActions );

    // create another look handler

    lookHandler = new KeyboardLookHandler( cam, 50, 1 );

    // and nest it

    input.addToAttachedHandlers( lookHandler );



    jmeDesktop = new JMEDesktop( “test internalFrame” );

    jmeDesktop.setup( display.getWidth(), display.getHeight(), false, input );

    jmeDesktop.setLightCombineMode( Spatial.LightCombineMode.Off );

    desktopNode = new Node( “desktop node” );

    desktopNode.attachChild( jmeDesktop );

    rootNode.attachChild( desktopNode );

    rootNode.setCullHint( Spatial.CullHint.Never );



    fullScreen();



    jmeDesktop.getJDesktop().setBackground( new Color( 1, 1, 1, 0.2f ) );



    try

    {

    SwingUtilities.invokeAndWait( new Runnable()

    {

    public void run()

    {

    // Only access the Swing UI from the Swing event dispatch thread!

    // See SwingUtilities.invokeLater()

    // and http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html for details.

    createSwingStuff();

    }

    } );

    }

    catch ( InterruptedException e )

    {

    // ok - just leave

    return;

    }

    catch ( InvocationTargetException e )

    {

    throw new RuntimeException( e );

    }



    createCustomCursor();

    }



    private void createCustomCursor()

    {

    cursor = new AbsoluteMouse( “cursor”, display.getWidth(), display.getHeight() );



    // Get a picture for my mouse.

    TextureState ts = display.getRenderer().createTextureState();

    URL cursorLoc = TestJMEJTable.class.getClassLoader().getResource(

    “jmetest/data/cursor/cursor1.png” );

    Texture t = TextureManager.loadTexture( cursorLoc, Texture.MinificationFilter.NearestNeighborNoMipMaps,

    Texture.MagnificationFilter.Bilinear, Image.Format.GuessNoCompression, 1, true );

    ts.setTexture( t );

    cursor.setRenderState( ts );



    // Make the mouse’s background blend with what’s already there

    BlendState as = display.getRenderer().createBlendState();

    as.setBlendEnabled( true );

    as.setSourceFunction( BlendState.SourceFunction.SourceAlpha );

    as.setDestinationFunction( BlendState.DestinationFunction.OneMinusSourceAlpha );

    as.setTestEnabled( true );

    as.setTestFunction( BlendState.TestFunction.GreaterThan );

    cursor.setRenderState( as );



    // Assign the mouse to an input handler

    cursor.registerWithInputHandler( input );



    statNode.attachChild( cursor );



    // important for JMEDesktop: use system coordinates

    cursor.setUsingDelta( false );

    cursor.getXUpdateAction().setSpeed( 1 );

    cursor.getYUpdateAction().setSpeed( 1 );



    cursor.setCullHint( Spatial.CullHint.Never );

    }



    private void fullScreen()

    {

    final DisplaySystem display = DisplaySystem.getDisplaySystem();



    desktopNode.getLocalRotation().set( 0, 0, 0, 1 );

    desktopNode.getLocalTranslation().set( display.getWidth() / 2, display.getHeight() / 2, 0 );

    desktopNode.getLocalScale().set( 1, 1, 1 );

    desktopNode.setRenderQueueMode( Renderer.QUEUE_ORTHO );

    desktopNode.setCullHint( Spatial.CullHint.Never );

    }



    private AbsoluteMouse cursor;

    private JScrollPane pane = null;



    protected void createSwingStuff()

    {

    final JDesktopPane desktopPane = jmeDesktop.getJDesktop();

    desktopPane.removeAll();



    JTable table = createJTable();



    pane = new JScrollPane();

    pane.setViewportView(table);

    pane.setLocation(50, 50);

    pane.setSize(300, 300);



    desktopPane.add(pane);



    JButton button = new JButton(“Remove Table and Garbage Collect”);

    button.setSize(button.getPreferredSize());

    button.setLocation(400, 50);

    desktopPane.add(button);

    button.addActionListener(new ActionListener()

    {

    public void actionPerformed(ActionEvent event)

    {

    if (pane != null)

    {

    desktopPane.remove(pane);

    pane = null;

    }

    desktopPane.repaint();

    desktopPane.revalidate();

    Runtime.getRuntime().gc();

    }

    });



    desktopPane.repaint();

    desktopPane.revalidate();

    }



    private JTable createJTable()

    {

    DefaultTableModel model = new DefaultTableModel();

    JTable table = new JTable(model);

    model.addColumn(“Name”);

    model.addColumn(“Title”);

    model.addColumn(“Note”);

    model.addColumn(“Creation”);

    model.addColumn(“Enabled”);



    for (int i=0; i<44; i++)

    {

    model.addRow(new Object[] { “Bob”, “Mister”, “Low”, String.valueOf(i), “-”});

    }



    table.setOpaque(false);

    return table;

    }



    protected void cleanup()

    {

    if ( jmeDesktop != null )

    {

    jmeDesktop.dispose();

    }

    super.cleanup();

    }

    }

    [/java]



    To see the same table in a simple swing application:



    [java]

    package jmetest.awt.swingui;



    import javax.swing.JFrame;

    import javax.swing.JScrollPane;

    import javax.swing.JTable;

    import javax.swing.table.DefaultTableModel;



    public class JTableApp

    {

    public static void main(String[] args)

    {

    javax.swing.SwingUtilities.invokeLater(new Runnable()

    {

    public void run()

    {

    createAndShowGUI();

    }

    });

    }



    private static void createAndShowGUI()

    {

    //Create and set up the window.

    JFrame frame = new JFrame(“JTableApp”);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);



    JTable table = createJTable();



    JScrollPane pane = new JScrollPane();

    pane.setViewportView(table);

    pane.setLocation(50, 50);

    pane.setSize(300, 300);



    frame.getContentPane().add(pane);



    //Display the window.

    frame.pack();

    frame.setVisible(true);

    }



    private static JTable createJTable()

    {

    DefaultTableModel model = new DefaultTableModel();

    JTable table = new JTable(model);

    model.addColumn(“Name”);

    model.addColumn(“Title”);

    model.addColumn(“Note”);

    model.addColumn(“Creation”);

    model.addColumn(“Enabled”);



    for (int i=0; i<44; i++)

    {

    model.addRow(new Object[] { “Bob”, “Mister”, “Low”, String.valueOf(i), “-”});

    }



    table.setOpaque(false);

    return table;

    }

    }

    [/java]

Thanks for the info but I dont think you can expect a solution unless you do it yourself, nobody is developing JMEDektop anymore.

I’m a bit surprised to hear that but thanks for letting me know. I’ll soldier on and try to find the reason and if I find a fix I’ll bump this thread with details for those that use JMEDesktop.

It’s seems that someone do.

http://hub.jmonkeyengine.org/groups/user-code-projects/forum/topic/jme3-swing3d-gui/?topic_page=2&num=15

http://soundmodul.heim.at/starcom/swingGui/swingGui.html

On further investigation, it looks to be a design fault or limitation in JMEDesktop. The way it handles the scrollable table is to create a new image/subtexture for any region of the component that changes. Obviously I can’t accept this.



To resolve it, I’ve flipped my code inside out by having the jme stuff render inside a JMECanvas all surrounded by Swing. Now I get superb performance on Swing components.



The downside to this is I can’t seem to get transparency working on components to show part of the JME display behind them. I know about some sort of pipeline that can achieve what I’m looking for (if I switch to JOGL), but I’m also aware of shockingly poor support for it and I don’t want to run into problems later where half the graphics cards aren’t compatible with it. So my solution so far is to use a mixture of JMECanvas and JMEDesktop. Those components that absolutely must have transparency will be rendered through JMEDesktop.



With hindsight I’d have made my own GUI, but I can’t justify the time to do that now, or even to swap to a different one.