JDK-8116176 : Memory leak on Windows screen lock
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 7u21
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2013-07-11
  • Updated: 2022-10-21
  • Resolved: 2013-08-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8
8Resolved
Related Reports
Duplicate :  
Description
The attached sample program produces a memory leak when the screen lock is active on windows.


package com.thalesgroup.de.gsr.test.testplugin.javafx.regionimage;

import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;

import javafx.embed.swt.FXCanvas;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.paint.Color;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * @author Jochen Mönch
 */
public class StandaloneTestJavaFX {

    private static final Logger     log               = Logger.getLogger( StandaloneTestJavaFX.class );
    private static final int        WIDTH             = 1280;
    private static final int        HEIGHT            = 1000;
    private FXCanvas                fxCanvas;
    public Color                    color             = Color.rgb( 255, 0, 0 );
    private Canvas                  canvas;
    private PixelFormat<ByteBuffer> pixelFormat;
    private byte[]                  imageData;
    private int                     cycles            = 0;
    private long                    lastCycleRateLog  = System.currentTimeMillis();
    private int                     overallCycles     = 0;
    private final byte              blueByte;
    private final byte              greenByte;
    private final byte              redByte;
    private final byte              alphaByte;
    private long                    lastUpdate        = System.currentTimeMillis();
    private long                    lastAdd           = System.currentTimeMillis();
    private final List<Rectangle2D> pendingRectangles = new LinkedList<Rectangle2D>();
    private boolean                 changed           = false;
    private boolean                 stop              = false;
    private Shell                   shell;

    public static void main(final String[] args) {

        BasicConfigurator.configure();
        StandaloneTestJavaFX test = new StandaloneTestJavaFX();
        test.run();
    }

    /**
     * Constructor. Opens Gui.
     * 
     * @param parent
     */
    public StandaloneTestJavaFX() {

        createShell();
        createGUI( shell );
        createImageData();
        blueByte = (byte) (Math.round( color.getBlue() * 255 ) & 0xff);
        greenByte = (byte) (Math.round( color.getGreen() * 255 ) & 0xff);
        redByte = (byte) (Math.round( color.getRed() * 255 ) & 0xff);
        alphaByte = (byte) 0xff;
    }

    public void run() {

        shell.open();

        new Thread( new Runnable() {

            @Override
            public void run() {

                while( !stop ) {
                    doWork();
                }
            }
        }, "FXPainter" ).start();
        while( !shell.isDisposed() ) {
            if( !shell.getDisplay().readAndDispatch() ) {
                shell.getDisplay().sleep();
            }
        }
    }

    private void createShell() {

        shell = new Shell( Display.getDefault() );
        shell.setSize( WIDTH, HEIGHT );
        shell.setLayout( new FillLayout() );
    }

    private void createGUI(final Composite parent) {

        fxCanvas = new FXCanvas( parent, SWT.NONE );
        Group rootGroup = new Group();
        rootGroup.setCache( false );
        Scene scene = new Scene( rootGroup, Color.TRANSPARENT );
        fxCanvas.setScene( scene );
        canvas = createCanvas();
        rootGroup.getChildren().add( canvas );
    }

    private void createImageData() {

        imageData = new byte[WIDTH * 4 * HEIGHT];
        pixelFormat = PixelFormat.getByteBgraInstance();
        for( int y = 0; y < HEIGHT; y++ ) {
            for( int x = 0; x < WIDTH; x++ ) {
                int startIdx = getStartIndex( y, x );
                imageData[startIdx] = 0x0;
                imageData[startIdx + 1] = 0x0;
                imageData[startIdx + 2] = 0x0;
                imageData[startIdx + 3] = 0x0;
            }
        }
    }

    private Canvas createCanvas() {

        Canvas canvas = new Canvas();
        canvas.setCache( true );
        canvas.setWidth( WIDTH );
        canvas.setHeight( HEIGHT );
        canvas.setVisible( true );
        return canvas;
    }

    private void doWork() {

        final long now = System.currentTimeMillis();
        if( now - lastAdd >= 1 ) {
            addNewData( 1000 );
            lastAdd = now;
        }
        if( now - lastUpdate >= 50 ) {
            updateContent( (int) Math.round( 255.0 / 30.0 ) );
            lastUpdate = now;
        }

        if( !fxCanvas.isDisposed() ) {
            if( now - lastCycleRateLog > 1000 ) {
                double timeSpend = (now - lastCycleRateLog) / 1000.0;
                double cycleRate = cycles / timeSpend;
                log.info( "CycleRate: " + cycleRate + ", total cycles: " + cycles );
                lastCycleRateLog = now;
                cycles = 0;
            }
            paintContent( overallCycles );
            cycles++;
            overallCycles++;
        } else {
            stopWork();
        }
    }

    private void stopWork() {

        stop = true;
    }

    private void addNewData(final int numRects) {

        synchronized( imageData ) {
            for( int i = 0; i < numRects; i++ ) {
                int x = (int) Math.round( Math.random() * (WIDTH - 5) );
                int y = (int) Math.round( Math.random() * (HEIGHT - 5) );
                pendingRectangles.add( new Rectangle2D( x, y, 5, 5 ) );
            }
        }
    }

    private void updateContent(final int decreaseAlphaBy) {

        synchronized( imageData ) {
            adjustAlpha( imageData, decreaseAlphaBy );
            for( Rectangle2D rectangle : pendingRectangles ) {
                fillRect( imageData, color, (int) Math.round( rectangle.getMinX() ),
                        (int) Math.round( rectangle.getMinY() ), (int) Math.round( rectangle.getWidth() ),
                        (int) Math.round( rectangle.getHeight() ) );
            }
            pendingRectangles.clear();
            changed = true;
        }
    }

    private void paintContent(final int cycle) {

        synchronized( imageData ) {
            if( changed ) {
                GraphicsContext gc = canvas.getGraphicsContext2D();
                PixelWriter pixelWriter = gc.getPixelWriter();
                pixelWriter.setPixels( 0, 0, WIDTH, HEIGHT, pixelFormat, imageData, 0, WIDTH * 4 );
                changed = false;
            }
        }
    }

    private void adjustAlpha(final byte[] imageData, final int decreaseBy) {

        for( int i = 0; i < imageData.length; i += 4 ) {
            imageData[i + 3] = (byte) Math.max( (imageData[i + 3] & 0xff) - decreaseBy, 0x0 );
        }
    }

    private void fillRect(final byte[] imageData, final Color color, final int startX, final int startY,
            final int width, final int height) {

        for( int y = startY; y < startY + height; y++ ) {
            for( int x = startX; x < startX + width; x++ ) {
                int index = getStartIndex( y, x );
                imageData[index] = blueByte;
                imageData[index + 1] = greenByte;
                imageData[index + 2] = redByte;
                imageData[index + 3] = alphaByte;
            }
        }
    }

    private int getStartIndex(final int row, final int column) {

        return row * WIDTH * 4 + column * 4;
    }
}

Comments
This is a known issue with the JavaFX Canvas node. See RT-24903.
10-08-2013

It's the setPixels() call in the paintContent() method in the code above (the very last test case) that causes the runnables to not be garbage collected for some reason. I'm reassigning this to Graphics team to take a look.
26-07-2013

I used jmap to get a heap dump of the java process after it started throwing the OOMs, and it seems that the runnables posted via the runLater() never get garbage collected for some reason. This needs further investigation.
26-07-2013

Jochen: thank you for testing this, and for the test cases. This is not an SWT issue, obviously. Note that you do have to use the runLater() whenever you need to access FX APIs. I'll need to instrument your last test case more to understand what objects cause the memory leak, but at first sight this looks like a Graphics issue related to the GraphicsContext or PixelWriter APIs.
16-07-2013

I tried JavaFX8. I get immediately the following exception: java.lang.IllegalStateException: Not on FX application thread If I then alter the code and encapsulate the critical code part in a Platform.runLater the performance decreases massively and I go OOM within about 30 seconds without screen lock. Altered code for JavaFX8: package com.thalesgroup.de.gsr.test.testplugin.javafx.regionimage; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.PixelFormat; import javafx.scene.image.PixelWriter; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; /** * @author Jochen Mönch */ public class StandaloneTestJavaFX2 extends Application { private static final Logger log = Logger.getLogger( StandaloneTestJavaFX2.class ); private static final int WIDTH = 1280; private static final int HEIGHT = 1000; public Color color = Color.rgb( 255, 0, 0 ); private Canvas canvas; private PixelFormat<ByteBuffer> pixelFormat; private byte[] imageData; private int cycles = 0; private long lastCycleRateLog = System.currentTimeMillis(); private int overallCycles = 0; private final byte blueByte; private final byte greenByte; private final byte redByte; private final byte alphaByte; private long lastUpdate = System.currentTimeMillis(); private long lastAdd = System.currentTimeMillis(); private final List<Rectangle2D> pendingRectangles = new LinkedList<Rectangle2D>(); private boolean changed = false; private boolean stop = false; private Stage stage; public static void main(final String[] args) { BasicConfigurator.configure(); launch( args ); } @Override public void start(final Stage stage) throws Exception { createGUI( stage ); new Thread( new Runnable() { @Override public void run() { try { while( !stop ) { doWork(); } } catch( Throwable e ) { log.error( "Exception!", e ); } } } ).start(); } /** * Constructor. Opens Gui. * * @param parent */ public StandaloneTestJavaFX2() { createImageData(); blueByte = (byte) (Math.round( color.getBlue() * 255 ) & 0xff); greenByte = (byte) (Math.round( color.getGreen() * 255 ) & 0xff); redByte = (byte) (Math.round( color.getRed() * 255 ) & 0xff); alphaByte = (byte) 0xff; } private void createGUI(final Stage stage) { this.stage = stage; Group rootGroup = new Group(); rootGroup.setCache( false ); Scene scene = new Scene( rootGroup, Color.TRANSPARENT ); stage.setScene( scene ); canvas = createCanvas(); rootGroup.getChildren().add( canvas ); stage.sizeToScene(); stage.show(); } private void createImageData() { imageData = new byte[WIDTH * 4 * HEIGHT]; pixelFormat = PixelFormat.getByteBgraInstance(); for( int y = 0; y < HEIGHT; y++ ) { for( int x = 0; x < WIDTH; x++ ) { int startIdx = getStartIndex( y, x ); imageData[startIdx] = 0x0; imageData[startIdx + 1] = 0x0; imageData[startIdx + 2] = 0x0; imageData[startIdx + 3] = 0x0; } } } private Canvas createCanvas() { Canvas canvas = new Canvas(); canvas.setCache( true ); canvas.setWidth( WIDTH ); canvas.setHeight( HEIGHT ); canvas.setVisible( true ); return canvas; } private void doWork() { final long now = System.currentTimeMillis(); if( now - lastAdd >= 1 ) { addNewData( 1000 ); lastAdd = now; } if( now - lastUpdate >= 50 ) { updateContent( (int) Math.round( 255.0 / 30.0 ) ); lastUpdate = now; } if( stage.isShowing() ) { if( now - lastCycleRateLog > 1000 ) { double timeSpend = (now - lastCycleRateLog) / 1000.0; double cycleRate = cycles / timeSpend; log.info( "CycleRate: " + cycleRate + ", total cycles: " + cycles ); lastCycleRateLog = now; cycles = 0; } paintContent( overallCycles ); cycles++; overallCycles++; } else { stopWork(); } } private void stopWork() { stop = true; } private void addNewData(final int numRects) { synchronized( imageData ) { for( int i = 0; i < numRects; i++ ) { int x = (int) Math.round( Math.random() * (WIDTH - 5) ); int y = (int) Math.round( Math.random() * (HEIGHT - 5) ); pendingRectangles.add( new Rectangle2D( x, y, 5, 5 ) ); } } } private void updateContent(final int decreaseAlphaBy) { synchronized( imageData ) { adjustAlpha( imageData, decreaseAlphaBy ); for( Rectangle2D rectangle : pendingRectangles ) { fillRect( imageData, color, (int) Math.round( rectangle.getMinX() ), (int) Math.round( rectangle.getMinY() ), (int) Math.round( rectangle.getWidth() ), (int) Math.round( rectangle.getHeight() ) ); } pendingRectangles.clear(); changed = true; } } private void paintContent(final int cycle) { Platform.runLater( new Runnable() { @Override public void run() { synchronized( imageData ) { if( changed ) { GraphicsContext gc = canvas.getGraphicsContext2D(); PixelWriter pixelWriter = gc.getPixelWriter(); pixelWriter.setPixels( 0, 0, WIDTH, HEIGHT, pixelFormat, imageData, 0, WIDTH * 4 ); changed = false; } } } } ); } private void adjustAlpha(final byte[] imageData, final int decreaseBy) { for( int i = 0; i < imageData.length; i += 4 ) { imageData[i + 3] = (byte) Math.max( (imageData[i + 3] & 0xff) - decreaseBy, 0x0 ); } } private void fillRect(final byte[] imageData, final Color color, final int startX, final int startY, final int width, final int height) { for( int y = startY; y < startY + height; y++ ) { for( int x = startX; x < startX + width; x++ ) { int index = getStartIndex( y, x ); imageData[index] = blueByte; imageData[index + 1] = greenByte; imageData[index + 2] = redByte; imageData[index + 3] = alphaByte; } } } private int getStartIndex(final int row, final int column) { return row * WIDTH * 4 + column * 4; } }
15-07-2013

I tried it with 2.2.40 with the same results.
15-07-2013

I tried it with pure JavaFX and it shows the same behaviour. It runs OOM within seconds. I will try with the newest JavaFX version next. Here is the source I used. package com.thalesgroup.de.gsr.test.testplugin.javafx.regionimage; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import javafx.application.Application; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.PixelFormat; import javafx.scene.image.PixelWriter; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; /** * @author Jochen Mönch */ public class StandaloneTestJavaFX2 extends Application { private static final Logger log = Logger.getLogger( StandaloneTestJavaFX2.class ); private static final int WIDTH = 1280; private static final int HEIGHT = 1000; public Color color = Color.rgb( 255, 0, 0 ); private Canvas canvas; private PixelFormat<ByteBuffer> pixelFormat; private byte[] imageData; private int cycles = 0; private long lastCycleRateLog = System.currentTimeMillis(); private int overallCycles = 0; private final byte blueByte; private final byte greenByte; private final byte redByte; private final byte alphaByte; private long lastUpdate = System.currentTimeMillis(); private long lastAdd = System.currentTimeMillis(); private final List<Rectangle2D> pendingRectangles = new LinkedList<Rectangle2D>(); private boolean changed = false; private boolean stop = false; private Stage stage; public static void main(final String[] args) { BasicConfigurator.configure(); launch( args ); } @Override public void start(final Stage stage) throws Exception { createGUI( stage ); new Thread( new Runnable() { @Override public void run() { try { while( !stop ) { doWork(); } } catch( Throwable e ) { log.error( "Exception!", e ); } } } ).start(); } /** * Constructor. Opens Gui. * * @param parent */ public StandaloneTestJavaFX2() { createImageData(); blueByte = (byte) (Math.round( color.getBlue() * 255 ) & 0xff); greenByte = (byte) (Math.round( color.getGreen() * 255 ) & 0xff); redByte = (byte) (Math.round( color.getRed() * 255 ) & 0xff); alphaByte = (byte) 0xff; } private void createGUI(final Stage stage) { this.stage = stage; Group rootGroup = new Group(); rootGroup.setCache( false ); Scene scene = new Scene( rootGroup, Color.TRANSPARENT ); stage.setScene( scene ); canvas = createCanvas(); rootGroup.getChildren().add( canvas ); stage.sizeToScene(); stage.show(); } private void createImageData() { imageData = new byte[WIDTH * 4 * HEIGHT]; pixelFormat = PixelFormat.getByteBgraInstance(); for( int y = 0; y < HEIGHT; y++ ) { for( int x = 0; x < WIDTH; x++ ) { int startIdx = getStartIndex( y, x ); imageData[startIdx] = 0x0; imageData[startIdx + 1] = 0x0; imageData[startIdx + 2] = 0x0; imageData[startIdx + 3] = 0x0; } } } private Canvas createCanvas() { Canvas canvas = new Canvas(); canvas.setCache( true ); canvas.setWidth( WIDTH ); canvas.setHeight( HEIGHT ); canvas.setVisible( true ); return canvas; } private void doWork() { final long now = System.currentTimeMillis(); if( now - lastAdd >= 1 ) { addNewData( 1000 ); lastAdd = now; } if( now - lastUpdate >= 50 ) { updateContent( (int) Math.round( 255.0 / 30.0 ) ); lastUpdate = now; } if( stage.isShowing() ) { if( now - lastCycleRateLog > 1000 ) { double timeSpend = (now - lastCycleRateLog) / 1000.0; double cycleRate = cycles / timeSpend; log.info( "CycleRate: " + cycleRate + ", total cycles: " + cycles ); lastCycleRateLog = now; cycles = 0; } paintContent( overallCycles ); cycles++; overallCycles++; } else { stopWork(); } } private void stopWork() { stop = true; } private void addNewData(final int numRects) { synchronized( imageData ) { for( int i = 0; i < numRects; i++ ) { int x = (int) Math.round( Math.random() * (WIDTH - 5) ); int y = (int) Math.round( Math.random() * (HEIGHT - 5) ); pendingRectangles.add( new Rectangle2D( x, y, 5, 5 ) ); } } } private void updateContent(final int decreaseAlphaBy) { synchronized( imageData ) { adjustAlpha( imageData, decreaseAlphaBy ); for( Rectangle2D rectangle : pendingRectangles ) { fillRect( imageData, color, (int) Math.round( rectangle.getMinX() ), (int) Math.round( rectangle.getMinY() ), (int) Math.round( rectangle.getWidth() ), (int) Math.round( rectangle.getHeight() ) ); } pendingRectangles.clear(); changed = true; } } private void paintContent(final int cycle) { synchronized( imageData ) { if( changed ) { GraphicsContext gc = canvas.getGraphicsContext2D(); PixelWriter pixelWriter = gc.getPixelWriter(); pixelWriter.setPixels( 0, 0, WIDTH, HEIGHT, pixelFormat, imageData, 0, WIDTH * 4 ); changed = false; } } } private void adjustAlpha(final byte[] imageData, final int decreaseBy) { for( int i = 0; i < imageData.length; i += 4 ) { imageData[i + 3] = (byte) Math.max( (imageData[i + 3] & 0xff) - decreaseBy, 0x0 ); } } private void fillRect(final byte[] imageData, final Color color, final int startX, final int startY, final int width, final int height) { for( int y = startY; y < startY + height; y++ ) { for( int x = startX; x < startX + width; x++ ) { int index = getStartIndex( y, x ); imageData[index] = blueByte; imageData[index + 1] = greenByte; imageData[index + 2] = redByte; imageData[index + 3] = alphaByte; } } } private int getStartIndex(final int row, final int column) { return row * WIDTH * 4 + column * 4; } }
15-07-2013

Also, could you try running with more recent versions of FX? Notably: with 2.2.25 (released), 2.2.40 (ea builds at https://jdk7.java.net/download.html), and 8 (ea builds at https://jdk8.java.net/download.html) and see if this is still an issue?
15-07-2013

I doubt anything is leaking memory in Glass. This is likely a Prism/Graphics issue, or SWT issue. Jochen: could you try reproducing the memory leak with a pure FX or pure SWT apps that do something similar to what you're doing in your app?
15-07-2013

Hello Artem, I don't really understand what you want me to change. The readAndDispatch must take place in the main application thread for JavaFX and SWT to work and without the thread and because the main thread is doing readAndDispatch it would not be able to paint anything. Do I have to use a special JavaFX thread implementation? Regards Jochen
15-07-2013

The tests paints into JavaFX Canvas object from an arbitrary thread. Jochen, Could you provide the correct test case and check if the memory leak is observed, please?
11-07-2013

Seems like a glass or SWT integration issue.
11-07-2013