• I seem to have run into a problem with Javas garbage collection.
    25 replies, posted
I was waiting for this day. On the opengl engine that I am working on, I just implemented a custom text renderer. It runs 60 frames per second even with a bunch of texts updating every frame. The problem I am running into is, it is eating up memory. Within 2 minutes, it has taken up almost all the RAM on my system. Disabling the text fixes this. I have found out that the way I am getting sprites every frame is not the cause. I am thinking it is the quads array. Every time you call setText, it creates a new array and refills it. This seems to be where the problem is. My guess is that I am not de-allocating this correctly enough for garbage collection to come around a grab it. Help would be amazing and a tip on how I can prevent this! [CODE] public class Text extends Entity{ private String text; private String fontSheet; private Texture fontSheetTexture; private Quad[] quads; public Text(String string){ fontSheet = "res/fontSheet.png"; fontSheetTexture = new Texture(GL_CLAMP, GL_LINEAR); fontSheetTexture.loadTexture(fontSheet); quads = new Quad[string.length()]; setText(string); } /** * Called every frame. */ public void tick(){ for(int i = 0; i < quads.length; i++){ quads[i].scale = scale; quads[i].position = new Vector3f(position.x + (i * 1),position.y,position.z); quads[i].tick(); } } /** * Sets the text of the Text object. * @param newText The new text to be set. */ public void setText(String newText){ text = newText; quads = new Quad[text.length()]; for(int i = 0; i < text.length(); i++){ quads[i] = new Quad(); quads[i].scale = scale; quads[i].position = new Vector3f(position.x + (i * 1),position.y,position.z); int ci = text.charAt(i); int column = (ci % 16); int row = (ci / 16); fontSheetTexture.loadSprite(column, row); } } } [/CODE] [editline]18th March 2013[/editline] Also, I forgot to add: On occasion, the game will keep its process running after I exit the program via the x on the top right. Not sure why this is...
Try storing the quads in an [url=http://docs.oracle.com/javase/6/docs/api/java/util/ArrayList.html]arraylist[/url]. Also, if you're making multiple Text objects then be aware you're probably loading that texture again for each one you create.
[QUOTE=NovembrDobby;39962651]Try storing the quads in an [url=http://docs.oracle.com/javase/6/docs/api/java/util/ArrayList.html]arraylist[/url]. Also, if you're making multiple Text objects then be aware you're probably loading that texture again for each one you create.[/QUOTE] For each Text, I load up the texture once at the start and store it in a buffer. Then whenever I switch the text I call loadSprite and get a section of that buffer so it pretty much takes nothing to load up a new sprite. I figured out the absolute source, it is this line in the setText function: quads[i] = new Quad(); I checked out the Quad class and there doesn't seem to be anything that would leak memory there. I heard that custom classes will not be picked up by the garbage collector when redefined. I will try arraylists now. [editline]18th March 2013[/editline] Tried using an arraylist, did not help the problem. Not sure what to do now... [editline]18th March 2013[/editline] Just made sure, there is no memory leak in the Quad class, i have one created and leaving the program running does nothing. I am 99% sure now that the Quads in the arraylist are not being deleted and are instead being stored somewhere in the memory and left to rot.
Are you sure they are unsubscribed from all events?
[QUOTE=Tamschi;39963228]Are you sure they are unsubscribed from all events?[/QUOTE] What do you mean?
[QUOTE=Duskling;39963369]What do you mean?[/QUOTE] It seems Java doesn't have events like C#. I you have, for example, a collection that the Quads add themselves to to get drawn, they wouldn't be collected. In C# this can be implemented with MulticastDelegates, events are a shorthand for a certain kind of them. Forgetting to unsubscribe from the delegate is one of the most common ways to create a memory leak.
Does anybody have any ideas of what is going on? it has to be something simple that I am missing.
Can you run your program with [URL="http://www.eclipse.org/mat/"]this profiler[/URL]? If not search for Java memory profiler and use one that shows references.
[QUOTE=Tamschi;39973361]Can you run your program with [URL="http://www.eclipse.org/mat/"]this profiler[/URL]? If not search for Java memory profiler and use one that shows references.[/QUOTE] Sorry for the late response. Trying the profiler now. [editline]20th March 2013[/editline] The profiler isn't seeming to be working for me. I can't get the java process.
Can you post your Quad class and how you set the texture? What does loadSprite do?
[QUOTE=Tamschi;39984954]Can you post your Quad class and how you set the texture? What does loadSprite do?[/QUOTE] Texture class: [CODE] public class Texture { public int texID; private int width; private int height; private ByteBuffer buffer; public Texture(int wrap, int filter){ texID = glGenTextures(); glBindTexture(GL_TEXTURE_2D, texID); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter ); } /** * Loads the given texture into the buffer for later use. * @param path The path to the texture you want to load. */ public void loadTexture(String path) { try { // Open the PNG file as an InputStream InputStream in = new FileInputStream(path); // Link the PNG decoder to this stream PNGDecoder decoder = new PNGDecoder(in); width = decoder.getWidth(); height = decoder.getHeight(); // Decode the PNG file in a ByteBuffer buffer = ByteBuffer.allocateDirect(4 * decoder.getWidth() * decoder.getHeight()); decoder.decode(buffer, decoder.getWidth() * 4, Format.RGBA); buffer.flip(); // Close the input stream in.close(); } catch (IOException e) { e.printStackTrace(); System.exit(-1); } } /** * Loads a sprite from the buffer. * @param column the column that the sprite is located in. * @param row the row that the sprite is located in. * @param buf the buffer that contains the spritesheet data. */ public void loadSprite(int column, int row){ glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glPixelStorei(GL_UNPACK_SKIP_PIXELS, column * 16); glPixelStorei(GL_UNPACK_SKIP_ROWS, row * 16); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); } } [/CODE] the loadTexture function loads a texture into a buffer, and is called when you create the texture objcet. The load sprite object loads a subsection of that preloaded buffer. Quad class: [CODE] public class Quad extends Geometry { public Quad(){ Vertex v1 = new Vertex(-0.5f,0.5f,0.0f); v1.setRGBA(1f, 1f, 1f, 1f); v1.setUV(0f, 0f); Vertex v2 = new Vertex(-0.5f,-0.5f,0.0f); v2.setRGBA(1f, 1f, 1f, 1f); v2.setUV(0f, 1f); Vertex v3 = new Vertex(0.5f,-0.5f,0.0f); v3.setRGBA(1f, 1f, 1f, 1f); v3.setUV(1f, 1f); Vertex v4 = new Vertex(0.5f,0.5f,0.0f); v4.setRGBA(1f, 1f, 1f, 1f); v4.setUV(1f, 0f); vertData = new Vertex[]{v1,v2,v3,v4}; indices = new byte[]{0, 1, 2, 2, 3, 0}; vertexDataBuffer = BufferUtils.createFloatBuffer(40 * vertData.length); for(int i = 0; i < vertData.length; i++){ vertexDataBuffer.put(vertData[i].getElements()); } vertexDataBuffer.flip(); indicesBuffer = BufferUtils.createByteBuffer(indices.length); indicesBuffer.put(indices); indicesBuffer.flip(); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vertexDataBuffer, GL_DYNAMIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_DYNAMIC_DRAW); } /** * Renders the quad. */ public void render(){ glUseProgram(shaderProgramID); // Use this shader glBindTexture(GL_TEXTURE_2D, texture.texID); glIdentityModelMatrix(); glSetScale(scale); glSetPosition(position); glSetRotation(rotation); glSetColor(color); glUpdateModelMatrix(); // Position glEnableVertexAttribArray(0); // enable the position attribute glVertexAttribPointer(0, 3, GL_FLOAT, false, 36, 0); // tell opengl how to read the position attribute. // attribute, amount of elements, type, normalized?, stride, offset ( pointer to first component ) // UV Coordinates glEnableVertexAttribArray(2); // enable the color attribute glVertexAttribPointer(2, 2, GL_FLOAT, false, 36, 28); // tell opengl how to read the color attribute. glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_BYTE, 0); // draw the vertices // cleanup glUseProgram(0); glBindTexture(GL_TEXTURE_2D, 0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(2); } } [/CODE]
Do you have any destructor in Quad that causes the buffers to be deleted? If not you're definitely leaking VRAM allocated in the constructor. This can also fill the RAM, as the driver provides VRAM management and will put unused resources into system memory if there's no graphics memory left. I don't know if Java's GC is threaded, but if it is (It is most likely.) you need to queue up unused OpenGL objects somewhere and delete them from the rendering thread. [editline]21st March 2013[/editline] You should be able to use [URL="http://www.gremedy.com/"]gDEBugger[/URL] to check your OpenGL context for leaked objects. [editline]21st March 2013[/editline] I wrote an OpenTK wrapper that uses a deletion queue a while back, [URL="https://bitbucket.org/Tamschi/ootk/src"]it's on Bitbucket[/URL]. DeletionQueue.ProcessAll() is called once per frame but could be less often. The locking is necessary because finalizers in .NET are called on the GC thread and the Queue operations aren't atomic.
[QUOTE=Tamschi;39986321]Do you have any destructor in Quad that causes the buffers to be deleted? If not you're definitely leaking VRAM allocated in the constructor. This can also fill the RAM, as the driver provides VRAM management and will put unused resources into system memory if there's no graphics memory left. I don't know if Java's GC is threaded, but if it is (It is most likely.) you need to queue up unused OpenGL objects somewhere and delete them from the rendering thread. [editline]21st March 2013[/editline] You should be able to use [URL="http://www.gremedy.com/"]gDEBugger[/URL] to check your OpenGL context for leaked objects. [editline]21st March 2013[/editline] I wrote an OpenTK wrapper that uses a deletion queue a while back, [URL="https://bitbucket.org/Tamschi/ootk/src"]it's on Bitbucket[/URL]. DeletionQueue.ProcessAll() is called once per frame but could be less often. The locking is necessary because finalizers in .NET are called on the GC thread and the Queue operations aren't atomic.[/QUOTE] Java doesn't have destructors, and using finalize (called when the gc cleans up an object) is generally considered bad practice iirc. [editline]21st March 2013[/editline] I just looked it up, and the JVM doesn't guarantee finalize to ever be called, and should only be used in special cases.
why are you calling quads = new Quad[]; twice? once in the constructor and once in setText()? You can get the Eclipse Memory Analyzer Tool and grab a heap dump to see what the leaks are. It may just be the GC hasnt run yet. It's not instantly cleared. Java heap looks like a saw tooth pattern [editline]21st March 2013[/editline] P.S. Finalization behavior from the JVM spec [url]http://docs.oracle.com/javase/specs/jvms/se5.0/html/Concepts.doc.html#19147[/url] [editline]21st March 2013[/editline] you are doing massive amounts of allocations of Vector3fs. Are they immutable? Can you not simply do setX, setY, setZ?
[QUOTE=redx475;39986985]Java doesn't have destructors, and using finalize (called when the gc cleans up an object) is generally considered bad practice iirc. [editline]21st March 2013[/editline] I just looked it up, and the JVM doesn't guarantee finalize to ever be called, and should only be used in special cases.[/QUOTE] I'd argue that the OpenGL resources are a special case though, as the other option would be to use manual memory management for everything. The latter is more efficient, but also complicates development and makes a lot of classes potential memory leak sources. [QUOTE=Bang Train;39987178]why are you calling quads = new Quad[]; twice? once in the constructor and once in setText()? You can get the Eclipse Memory Analyzer Tool and grab a heap dump to see what the leaks are. It may just be the GC hasnt run yet. It's not instantly cleared. Java heap looks like a saw tooth pattern [editline]21st March 2013[/editline] P.S. Finalization behavior from the JVM spec [url]http://docs.oracle.com/javase/specs/jvms/se5.0/html/Concepts.doc.html#19147[/url] [editline]21st March 2013[/editline] you are doing massive amounts of allocations of Vector3fs. Are they immutable? Can you not simply do setX, setY, setZ?[/QUOTE] I doubt that the GC wouldn't run until that much memory is taken up by the application. I agree on the Vector classes though, creating that many will produce a lot of unnecessary garbage.
[QUOTE=Tamschi;39991457]I'd argue that the OpenGL resources are a special case though, as the other option would be to use manual memory management for everything. The latter is more efficient, but also complicates development and makes a lot of classes potential memory leak sources. I doubt that the GC wouldn't run until that much memory is taken up by the application. I agree on the Vector classes though, creating that many will produce a lot of unnecessary garbage.[/QUOTE] So I should make my own Vector3 class that has x,y, and z? That isn't a problem. I thought it was a possibility that the GC hasn't run yet, but I left the program on for about 3 minutes and it eats up all the Ram and then everything on my computer starts lagging and then it can freeze.
[QUOTE=Duskling;39992922]So I should make my own Vector3 class that has x,y, and z? That isn't a problem. I thought it was a possibility that the GC hasn't run yet, but I left the program on for about 3 minutes and it eats up all the Ram and then everything on my computer starts lagging and then it can freeze.[/QUOTE] For performance optimizations, it's a good idea to have no heap allocations in the parts of the program that run every frame. Your memory problem is unrelated though. If you don't delete the buffers and textures manually you're leaking them, as the GC can't collect unmanaged resources.
[QUOTE=Tamschi;39993206]For performance optimizations, it's a good idea to have no heap allocations in the parts of the program that run every frame. Your memory problem is unrelated though. If you don't delete the buffers and textures manually you're leaking them, as the GC can't collect unmanaged resources.[/QUOTE] Can you explain this a little bit more in depth? What I am getting from this is that when I create new quads I should do quads[i].texture = null?
for the VBO and EBO for the VAO Basically you want to have some way to clean up the VAO and VBO in the Quad class. You need to manage your own memory when dealing with OGL in Java. A method like cleanUp() is fine. Finalizers might work too, I guess. Makes you wish you had destructors, doesn't it? The OpenGL memory model is closer to C/C++, so it's usually harder to make it work in Java. Also, more generally, graphics cards don't like it when you create lots of tiny buffers. Creating and deleting buffers is slow. Buffers should be as static as possible, like that EBO that's the same for every Quad.
[QUOTE=Duskling;39994151]Can you explain this a little bit more in depth? What I am getting from this is that when I create new quads I should do quads[i].texture = null?[/QUOTE] You have to call glDelete* with the handles you get from glGen* when you don't need the resource any more. glGenTextures() creates an object in unmanaged memory, but all Java sees is the int texID you use to reference to that resource. Java [I]does not know[/I] about any unmanaged resources or objects and as such can not free them when you don't need them any more (because you never had a reference to the object in the first place). The OpenGL driver keeps a dictionary that connects the int in texID to the memory pointer of the resource. It can't use GC because that doesn't exist in C++ (and wouldn't work across the OpenGL-API's int handles), and it can't count references outside of the unmanaged code because managed environments usually don't support this. (It's incompatible with the API too, and the driver most likely doesn't count references at all as that's faster.) When you call glDelete* with a handle (e.g. your texID), the driver can find the memory pointer from its dictionary and free the resource. (In reality it's more complicated because the driver also has to keep track of VRAM and move resources back and forth, but that makes no difference on the Java side.)
Wow, thanks for all the help. This is awesome. So I guess I will get to work fixing this stuff.
So I made a function in the quad that deletes all the buffers and textures. Now it seems that it eats up alot less ram. However I have run into a big problem, The game sometimes doesn't close when I close ( the process stays ) and it overheats my cpu and uses all 4 cores 100%. Thank god I caught it before my computer melted :wink: Not sure what to do about that.
do you spin up additional threads?
[QUOTE=Bang Train;40006909]do you spin up additional threads?[/QUOTE] Nope, I don't even use threads.
Can you post your code for the window you are using?
[QUOTE=Duskling;40004846]So I made a function in the quad that deletes all the buffers and textures. Now it seems that it eats up alot less ram. However I have run into a big problem, The game sometimes doesn't close when I close ( the process stays ) and it overheats my cpu and uses all 4 cores 100%. Thank god I caught it before my computer melted :wink: Not sure what to do about that.[/QUOTE] You should really try running a profiler on the thing. Also, try limiting the frame-rate.
Sorry, you need to Log In to post a reply to this thread.