Memory leak(?) and inefficient buffering problem

Just for quick self introduction, I’m fairly new to computer programming.
My background is just a semester of single CS introductory class. (I was majoring mechanical engineering back then a year ago.)
This semester I decided upon a personal long term project, and you might have guessed that it is making a game.
I delved into some forums such as this and watched tutorial videos, and I got “something” going on for now.
However there seems to be few problems with my program.

First thing is that the program seems to have a memory leakage.
Currently, the only task it does is to perpetually update steady, unchanging game state.
However, I checked on the task manager and the program kept on hogging more memory space.
It had some occassional memory drops, but it just kept on increaseing in the long run.
The parts that I coded by myself didn’t seem to have problems, but I’m not sure of some parts that I got from "follow-along"s.

Second probem is that its image buffering sucks.
I’m using a double buffering method from a video tutorial of user “thejavahub”, but I don’t see much effect.
Also it takes more than 20ms just to do the task I mentioned above, which I believe will cause the game to go lower than 50 fps.
Below I’m posting two classes of my program that should hold enough source code for those willing to help.

/*EDIT
I forgot to mention what my questions were:

  1. What’s causing the memory leak? How do I prevent it? (Or is it even a memory leak?)
  2. How do I get a smooth panning mechanism instead of the current ugly one?
  3. What do you think about the program’s “drawing algorithm”? Any improvement and/or criticism?
  4. Any other criticism that is not related to my questions?

I want to learn programming well and from lots of perspective.
Harsh criticisms (criticisms, not insults) or even minor details such as missed programming convention is welcomed.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class GameBoard extends JPanel implements Runnable {
	// Dependent to tile image size.
	private final int SCALE = 16;
	private final long PERIOD = 20 * 1000000; // Milliseconds to Nanoseconds
	
	private static int uIndex = 0;
	private static int vIndex = 0;
	private static int wIndex = 1;
	long beforeTime;
	long afterTime;
	long difference;
	long sleepTime;
	private Image TILE_0;
	private Image TILE_0_TOP;
	private Image TILE_A;
	private Image TILE_A_TOP;
	private Image TILE_B;
	private Image TILE_B_TOP;
	
	private Graphics bufferGraphics;
	private Image bufferImage;
	private Thread game;
	private volatile boolean running = false;
	
	private GameTile tileGrid;
	
	public GameBoard() {
		loadTileImage();
		tileGrid = new GameTile(2);
		setBackground(Color.WHITE);
		setFocusable(true);
		requestFocus();
		
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				switch(e.getKeyCode()) {
				default:
					break;
				case KeyEvent.VK_UP:
					if(uIndex < tileGrid.getU() / 2)
						uIndex++;
					break;
				case KeyEvent.VK_DOWN:
					if(uIndex > -(tileGrid.getU() / 2))
						uIndex--;
					break;
				case KeyEvent.VK_LEFT:
					if(vIndex < tileGrid.getV() / 2)
						vIndex++;
					break;
				case KeyEvent.VK_RIGHT:
					if(vIndex > -(tileGrid.getV() / 2))
						vIndex--;
					break;
				case KeyEvent.VK_PAGE_UP:
					if(wIndex < tileGrid.getW() - 2)
						wIndex++;
					break;
				case KeyEvent.VK_PAGE_DOWN:
					if(wIndex > 2)
						wIndex--;
					break;
				case KeyEvent.VK_SPACE:
					uIndex = 0;
					vIndex = 0;
					break;
				case KeyEvent.VK_ESCAPE:
					System.exit(0);
					break;
				}
			}
			@Override
			public void keyReleased(KeyEvent e) {}
			@Override
			public void keyTyped(KeyEvent e) {}
		});
	}
	
	public void addNotify() {
		super.addNotify();
		startGame();
	}
	private void startGame() {
		if(game == null || !running) {
			game = new Thread(this);
			game.start();
			running = true;
		}
	}
	public void stopGame() {
		if(running) {
			running = false;
		}
	}
	@SuppressWarnings("static-access")
	public void run() {
		while(running) {
			beforeTime = System.nanoTime();
			gameUpdate();
			gameRender();
			paintScreen();
			afterTime = System.nanoTime();
			difference = afterTime - beforeTime;
			if(difference < PERIOD) {
				sleepTime = PERIOD - difference;
				try {
					game.sleep(sleepTime / 1000000);
				} catch (InterruptedException e) {}
				System.out.printf("%d.%03d ms\n", difference / 1000000, difference % 1000000 / 1000);
			} else {
				System.out.printf("LAG! %d.%03d ms\n", difference / 1000000, difference % 1000000 / 1000);
			}
			
		}
	}
	private void gameUpdate() {
		if(running || game != null) {
			// TODO Update game board.
		}
	}
	private void gameRender() {
		if(bufferImage == null) {
			// Create the buffer.
			bufferImage = createImage(800, 600);
			if(bufferImage == null) {
				System.out.println("ERROR: Buffer image still does not exist!");
				return;
			} else {
				bufferGraphics = bufferImage.getGraphics();
			}
		}
		// Clear the screen.
		bufferGraphics.setColor(Color.WHITE);
		bufferGraphics.fillRect(0, 0, 800, 600);
		draw(bufferGraphics);
	}
	public void draw(Graphics g) {
		drawTile(g);
	}
	public void paintScreen() {
		Graphics g;
		try {
			g = this.getGraphics();
			if(bufferImage != null && g != null) {
				g.drawImage(bufferImage, 0, 0, null);
			}
			Toolkit.getDefaultToolkit().sync();
			g.dispose();
		} catch(Exception e) {}
	}
	
	private void loadTileImage() {
		TILE_0 = new ImageIcon("tile0.png").getImage();
		TILE_0_TOP = new ImageIcon("tile0top.png").getImage();
		TILE_A = new ImageIcon("tileA.png").getImage();
		TILE_A_TOP = new ImageIcon("tileAtop.png").getImage();
		TILE_B = new ImageIcon("tileB.png").getImage();
		TILE_B_TOP = new ImageIcon("tileBtop.png").getImage();
	}
	private void drawTileImage(int i, int j, int k, Graphics g) {
		if(tileGrid.getTileID(i, j, k) == 0)
			g.drawImage(TILE_0, toX(i, j), toY(i, j, k), null);
		else if(tileGrid.getTileID(i, j, k) == 1)
			g.drawImage(TILE_A, toX(i, j), toY(i, j, k), null);
		else if(tileGrid.getTileID(i, j, k) == 2)
			g.drawImage(TILE_B, toX(i, j), toY(i, j, k), null);
	}
	private void drawTileTopImage(int i, int j, int k, Graphics g) {
		if(tileGrid.getTileID(i, j, k) == 0)
			g.drawImage(TILE_0_TOP, toX(i, j), toY(i, j, k), null);
		else if(tileGrid.getTileID(i, j, k) == 1)
			g.drawImage(TILE_A_TOP, toX(i, j), toY(i, j, k), null);
		else if(tileGrid.getTileID(i, j, k) == 2)
			g.drawImage(TILE_B_TOP, toX(i, j), toY(i, j, k), null);
	}
	private void drawTile(Graphics g) {
		for(int i = 0; i < tileGrid.getU(); i++) {
			for(int j = 0; j < tileGrid.getV(); j++) {
				drawTileImage(i, j, wIndex - 1, g);
				drawTileImage(i, j, wIndex, g);
				drawTileTopImage(i, j, wIndex + 1, g);
			}
		}
	}
	private int toX(int i, int j) {
		return((getWidth() / 2 - SCALE) - SCALE * (i + uIndex) + SCALE * (j + vIndex));
	}
	private int toY(int i, int j, int k) {
		return((getHeight() / 2 - SCALE * tileGrid.getV() / 2) + SCALE * (i + uIndex + j + vIndex) / 2 - SCALE * (k - wIndex));
	}
}
public class GameTile {
	private static int u;
	private static int v;
	private static int w;
	private static int[][][] tileGrid;
	public GameTile(int size) {
		if(size == 0)
			size = 1;
		u = (64 * size);
		v = (64 * size);
		w = (64 * size);
		tileGrid = new int[u][v][w];
		generateTileGrid();
	}
	private void generateTileGrid() {
		int test;
		for(int i = 0; i < u; i++) {
			for(int j = 0; j < v; j++) {
				for(int k = 0; k < w; k++) {
					test = ((i + j + k) % 4);
					if(test == 0)
						tileGrid[i][j][k] = 1;
					else if(test == 1)
						tileGrid[i][j][k] = 2;
					else
						tileGrid[i][j][k] = 0;
				}
			}
		}
	}
	public void setTileID(int i, int j, int k, int tileID) {
		tileGrid[i][j][k] = tileID;
	}
	public int getU() {
		return u;
	}
	public int getV() {
		return v;
	}
	public int getW() {
		return w;
	}
	public int getTileID(int i, int j, int k) {
		return tileGrid[i][j][k];
	}
	public int[][][] getTileGrid() {
		return tileGrid;
	}
}

Looking at your code i don’t see anything obvious that would cause memory leak, but i do have a few criticisms. :wink:

The first one, and possibly causing the memory leak, is that you silence exceptions, i see you do this a few times.
Try not to silence exceptions, because if your program is throwing a lot of silenced exceptions, it could easily cause all sorts of performance problems, and try to use specific exceptions that you know could be thrown rather than the standard Exception.

The second one, try to add more comments or arrange you code based on functionality, to make it easier to read, because large walls of code is tend to be hard to read, and limits the amount of people who would post here.

As for your drawing algorithm, it looks fine, i don’t know how much of an improvement it is as i have little experience with it, but you could try using bufferstrategy rather than a bufferedimage its supposed to be faster.

@Magn919
Silencing Exceptions doesn’t cause memory leaks…

@OP
You create a new Image every time you render :wink:

I didn’t mean to say it “did”, i meant it could cause performance problems if the program would start spamming exceptions, which in turn could cause memory leak, but thanks for correcting me, wasn’t really sure about what impact exceptions had on memory.

First of all, thank you for your time and help.

@Magn919
Going point by point, most of the silencing(?) was done because it was shown as error unless something was done about it.
Like I said I’m very new to programming, so what do you suggest as best course of action for the exceptions?
Should I just call System.exit(0); line?

Organizational skill is something I need to improve on and quickly. Thank you for pointing that out.
I can fix the commenting habit by myself, but in terms of actual code I think I need more knowledge to really keep things tidy.
What I plan on doing along my long term project is to tidy up all the code everytime I reach certain “checkpoints”.

The last point I didn’t quite understand. It would be more helpful if you could be more detailed about the bufferstrategy.

@ra4king
I’m missing a huge chuck of programming knowledge to see how that create new Image loop is happening.
What I had in mind was that loadTileImage() method will only get called once in GameBoard class’ default constructor.
Could you please write out a simple explanation of how and why the Images are being continuously created?

Ah my bad, I didn’t see that it was in an if conditional statement.
I was talking about line 130 :slight_smile:

Looking at the rest of the code, nothing seems wrong or potentially causing a leak.

Concerning BufferStrategy, that offers faster rendering due to efficient double buffering done behind the scenes like page flipping. This post should explain a bit how it works.

you don’t call dispose() for bufferGraphics. It might cause resource leaks.

Also, it’s better to put dispose() to finally clause in paintScreen().

It is safe not to call dispose() on a Graphics instance.

The resource is guaranteed to be released eventually.

Correct me if I’m wrong. The following is taken from javadoc for System.runFinalizersOnExit( ):

[quote]By default, finalization on exit is disabled.
[/quote]
But System.exit(0) is used in GameBoard (line 74), so Graphics.finalize() might not be called …

But System.exit(0) is used in GameBoard (line 74), so Graphics.finalize() might not be called …
[/quote]
Why worry about a ‘memory leak’ if it only happens during the termination of your process?!

Upon process termination, all graphics resources are reclaimed anyway, just like any open file-handles are closed.