I'm developing a game in java using the LWJGL library, and I came across a huge memory leak after creating an infinite terrain generator. Basically, the terrain is divided into chunks and I have a render distance variable like 8. I have a HashMap which maps every created Terrain with it's coordinate, represented by a custom Key class. Then, for each Key (coordinates) within the render distance, I'm checking if the HashMap contains this Key, if it doesn't I'm creating it and adding it to a List of terrains to render. Then, I render the terrains, clear the list, and every 5 seconds I check every key of the Hashmap to see if it is still in the render distance and remove the ones that don't.
The thing is that I have 2 memory leak, I mean that memory is increasing with time. To debug my code, I simply remove each part until the memory stops increasing. I found 2 parts causing the 2 leaks:
First, the HashMap is not clearing correctly the references to the Terrains. Indeed, I kept track of the size of the HashMap and it never goes beyond ~200 elements, even though the memory increases really fast when generating new terrains. I think I'm missing something about HashMap, I have to precise that I implemented a hashCode and equals methods to both Key and Terrain class.
Second, creating new Keys and storing a reference to the Terrain in my loops to avoid getting it in the HashMap every time I need it are causing a small but noticeable memory leak.
RefreshChunks is called every frame, and deleteUnusedChunks every 5 seconds
public void refreshChunks(MasterRenderer renderer,Loader loader, Vector3f cameraPosition) {
int camposX = Math.round(cameraPosition.x/(float)Terrain.CHUNK_SIZE);
int camposZ = Math.round(cameraPosition.z/(float)Terrain.CHUNK_SIZE);
campos = new Vector2f(camposX,camposZ);
for(int x = -chunkViewDist + camposX; x <= chunkViewDist + camposX; x++) {
for(int z = -chunkViewDist + camposZ; z <= chunkViewDist + camposZ; z++) {
Key key = new Key(x,z); //Memory increases
Terrain value = terrains.get(key); //Memory increases
if(Maths.compareDist(new Vector2f(x,z), campos, chunkViewDist)) {
if(value == null) {
terrains.put(key, new Terrain(loader,x,z,terrainInfo)); //Memory leak happens if I fill the HashMap
}
if(!terrainsToRender.contains(value)) {
terrainsToRender.add(value);
}
} else {
if(terrainsToRender.contains(value)) {
terrainsToRender.remove(value);
}
}
}
}
renderer.processTerrain(terrainsToRender);
if(DisplayManager.getCurrentTime() - lastClean > cleanGap) {
lastClean = DisplayManager.getCurrentTime();
deleteUnusedChunks();
}
}
public void deleteUnusedChunks() {
List<Key> toRemove = new ArrayList<Key>();
terrains.forEach((k, v) -> {
if(!Maths.compareDist(new Vector2f(k.x,k.y), campos, chunkViewDist)){
toRemove.add(k);
}
});
for(Key s:toRemove) {
terrains.remove(s);
}
}
Key class implementation:
public static class Key {
public final int x;
public final int y;
public Key(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Key)) return false;
Key key = (Key) o;
return x == key.x && y == key.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
Terrain class hashCode and equals implementation:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Terrain)) return false;
Terrain te = (Terrain) o;
return gridx == te.gridx && gridz == te.gridz;
}
@Override
public int hashCode() {
int result = gridx;
result = 31 * result + gridz;
return result;
}
I'm certainly missing something about the behavior of HashMaps and classes, thanks for your help.
Edit:
My questions are: • Is it normal that instantiating a new key class and create a reference to a terrain are making the memory increase over time?
• Why while the HashMap size stays the same when putting new elements and removing old ones the memory keeps increasing over time?
Edit 2: I tried generating terrains for about 10min, and at some point, the program used 12 GB of ram, but even though I couldn't get it to crash with a out of memory exception because the more I was generating, the less ram was added. But still I don't my game to use the maximum amount of ram available before starting to recycle it.