0

Oh dear, Garbage Collector. I ran upon a java outOfMemory error.

Stacktrace:

[09:44:10] 2014-01-22 09:44:10 [INFO] [STDERR] java.lang.OutOfMemoryError: Java heap space
[09:44:11] 2014-01-22 09:44:10 [INFO] [STDERR]  at net.minecraft.util.AABBPool.getAABB(AABBPool.java:50)
[09:44:11] 2014-01-22 09:44:11 [INFO] [STDERR]  at net.minecraft.block.Block.getCollisionBoundingBoxFromPool(Block.java:602)
[09:44:12] 2014-01-22 09:44:12 [INFO] [STDERR]  at net.minecraft.block.Block.addCollisionBoxesToList(Block.java:568)
[09:44:14] 2014-01-22 09:44:14 [INFO] [STDERR]  at net.minecraft.world.World.getCollidingBoundingBoxes(World.java:1684)
[09:44:15] 2014-01-22 09:44:14 [INFO] [STDERR]  at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:187)
[09:44:17] 2014-01-22 09:44:15 [INFO] [STDERR]  at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:389)
[09:44:18] 2014-01-22 09:44:17 [INFO] [STDERR]  at net.minecraft.server.integrated.IntegratedServerListenThread.networkTick(IntegratedServerListenThread.java:91)
[09:44:19] 2014-01-22 09:44:18 [INFO] [STDERR]  at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:691)
[09:44:20] 2014-01-22 09:44:19 [INFO] [STDERR]  at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:587)
[09:44:22] 2014-01-22 09:44:20 [INFO] [STDERR]  at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:129)
[09:44:23] 2014-01-22 09:44:22 [INFO] [STDERR]  at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:484)
[09:44:24] 2014-01-22 09:44:23 [INFO] [STDERR]  at net.minecraft.server.ThreadMinecraftServer.run(ThreadMinecraftServer.java:16)
[09:44:33] 2014-01-22 09:44:24 [SEVERE] [Minecraft-Server] Encountered an unexpected exception OutOfMemoryError
[09:44:33] java.lang.OutOfMemoryError: Java heap space
[09:44:33]  at net.minecraft.util.AABBPool.getAABB(AABBPool.java:50)
[09:44:33]  at net.minecraft.block.Block.getCollisionBoundingBoxFromPool(Block.java:602)
[09:44:33]  at net.minecraft.block.Block.addCollisionBoxesToList(Block.java:568)
[09:44:33]  at net.minecraft.world.World.getCollidingBoundingBoxes(World.java:1684)
[09:44:33]  at net.minecraft.entity.player.EntityPlayerMP.<init>(EntityPlayerMP.java:187)
[09:44:33]  at net.minecraft.server.management.ServerConfigurationManager.createPlayerForUser(ServerConfigurationManager.java:389)
[09:44:33]  at net.minecraft.server.integrated.IntegratedServerListenThread.networkTick(IntegratedServerListenThread.java:91)
[09:44:33]  at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:691)
[09:44:33]  at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:587)
[09:44:33]  at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:129)
[09:44:33]  at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:484)
[09:44:33]  at net.minecraft.server.ThreadMinecraftServer.run(ThreadMinecraftServer.java:16)

Information gathered using Eclipse Memory Analyzer:

The thread net.minecraft.server.ThreadMinecraftServer @ 0xbdf43f30 Server thread keeps local variables with total size 935,606,320 (87.86%) bytes.

The memory is accumulated in one instance of "java.lang.Object[]" loaded by "<system class loader>".

Keywords
java.lang.Object[]

Shortest Paths To the Accumulation Point
Class Name  Shallow Heap    Retained Heap
|-> java.lang.Object[17176655] @ 0xee3868b8    68,706,640   935,599,248
\-> elementData java.util.ArrayList @ 0xc22cbc80    24  935,599,272
-\-> listAABB net.minecraft.util.AABBPool @ 0xbff74570    40    935,599,312
--\-> value java.lang.ThreadLocal$ThreadLocalMap$Entry @ 0xc1844bc0    32   935,599,344
---\-> [58] java.lang.ThreadLocal$ThreadLocalMap$Entry[64] @ 0xbe0330b0    272  935,605,248
----\-> table java.lang.ThreadLocal$ThreadLocalMap @ 0xbe036680    24   935,605,272
-----\-> threadLocals net.minecraft.server.ThreadMinecraftServer @ 0xbdf43f30 Server thread Thread    120   935,606,320

Accumulated Object by class:
net.minecraft.util.AxisAlignedBB | Number of Objects: 13,545,197 | Used Heap Size: 866,892,608 | Retained Heap Size: 866,892,608

The function that is the source of this outOfMemory error is this:

private static int getNextEmptyId()
{
    int temp = 0;
    for(; temp < blocksList.length; temp++)
    {
        if(blocksList[temp] == null)
        {
           break;
        }
    }
    return temp;
}

blocksList is a partly filled array of the size of 4096. The function is supposed to check the first spot in the list being empty (null) so it can pass this id towards the super function which gives the id to the base class which uses this id to create the object and adds it to the blocksList.

The function getNextEmptyId() is called here:

public class ModBlock extends Block
{

...

    public ModBlock(String name, Material material)
    {
        super(getNextEmptyId(), material);
        GameRegistry.registerBlock(this, name);
        this.setTextureName("terracraft/" + name);
        this.setUnlocalizedName(name);
        this.stepSound = soundStoneFootstep;
    }

}

which then again calls this function via super:

public Block(int par1, Material par2Material)
{
    this.stepSound = soundPowderFootstep;
    this.blockParticleGravity = 1.0F;
    this.slipperiness = 0.6F;

    if (blocksList[par1] != null)
    {
        throw new IllegalArgumentException("Slot " + par1 + " is already occupied by " + blocksList[par1] + " when adding " + this);
    }
    else
    {
        this.blockMaterial = par2Material;
        blocksList[par1] = this;
        this.blockID = par1;
        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
        opaqueCubeLookup[par1] = this.isOpaqueCube();
        lightOpacity[par1] = this.isOpaqueCube() ? 255 : 0;
        canBlockGrass[par1] = !par2Material.getCanBlockGrass();
    }
}


In AABBPool line 50:

44    public AxisAlignedBB getAABB(double par1, double par3, double par5, double par7, double par9, double par11)
45    {
46        AxisAlignedBB axisalignedbb;
47
48        if (this.nextPoolIndex >= this.listAABB.size())
49        {
50            axisalignedbb = new AxisAlignedBB(par1, par3, par5, par7, par9, par11);
51           this.listAABB.add(axisalignedbb);
52        }
53        else
54        {
55            axisalignedbb = (AxisAlignedBB)this.listAABB.get(this.nextPoolIndex);
56            axisalignedbb.setBounds(par1, par3, par5, par7, par9, par11);
57        }
58
59        ++this.nextPoolIndex;
60        return axisalignedbb;
61    }

In Block line 602:

600    public AxisAlignedBB getCollisionBoundingBoxFromPool(World par1World, int par2, int par3, int par4)
601    {
602        return AxisAlignedBB.getAABBPool().getAABB((double)par2 + this.minX, (double)par3 + this.minY, (double)par4 + this.minZ, (double)par2 + this.maxX, (double)par3 + this.maxY, (double)par4 + this.maxZ);
603    }

If you need more information just say so. Thanks for any help.

  • That function allocates no memory*, therefore it can not be the source of an OOM exception. *Technically it allocates exactly one 4 byte int on the stack. – Brian Roach Jan 22 '14 at 08:27
  • 1
    Well it plays a huge role as commenting it out will result in no memory leak anymore.. I'm gonna add more code – Pascal Neubert Jan 22 '14 at 08:37
  • Post the actual stack trace. – Brian Roach Jan 22 '14 at 08:42
  • And it's calling the superclass' `ModBlock` method - did you mistype the name in your edit? – Brian Roach Jan 22 '14 at 08:44
  • Please show us `net.minecraft.util.AABBPool.getAABB(AABBPool.java:50)`. – Fabian Barney Jan 22 '14 at 08:49
  • @BrianRoach - ModBlock extends Block, my apologies for not mentioning. Added `net.minecraft.util.AABBPool.getAABB(AABBPool.java:50)` – Pascal Neubert Jan 22 '14 at 08:54
  • Right, that makes sense now. I don't know enough about Minecraft to offer any additional insight, but are you supposed to / need to keep your own references to those blocks? Wouldn't a counter work just as well? The thing is - that stack trace doesn't go through those constructors; it's just calling methods on a `Block` instance that already exists. You'd see `Block.` for the constructor. – Brian Roach Jan 22 '14 at 09:07
  • There is a general rule when analysing out of memory in any java program. Analyze the heap contents and see which objects are causing the leak (look for too many/too huge ones). I'd suggest to use the open source one - Eclipse MAT (http://www.eclipse.org/mat/) Afterwards, it should be easier to find the leaking place. Could you do so and share your findings with us? – Peter Butkovic Jan 22 '14 at 09:24
  • @BrianRoach Using a counter would prevent the memory leak however it'd require myself to update the counter with each minecraft update where Mojang allocates more blocks. "Developers are lazy" they say. So I tried to find an automatic code to check at which id no block has been defined yet. – Pascal Neubert Jan 22 '14 at 09:27
  • I used Eclipse MAT to check for leaking. It's saying that the class net.minecraft.util.AxisAlignedBB got 13,545,197 objects and uses a heap size equal to the retained heap size. The accumulation point is at net.minecraft.server.ThreadMinecraftServer @ 0xbdf43f30... Going to add information to main post as well. On a side note: for compatibility with other mods I am not allowed to edit the base classes Mojang or the Forge API creators created. – Pascal Neubert Jan 22 '14 at 09:54

1 Answers1

0

It looks at first sight that your problem is not the function looking for the gap in your blocks list, but that every block you build gets referenced inside that list and in your GameRegistry.

Maybe one of those references persist in some way when you try to free a block, that prevents GC to erase its memory and that causes your leak... it's just an idea, please check it.

Please have a look at Why does this code sample produce a memory leak? for further explanations on memory leaks

Community
  • 1
  • 1
Jorge_B
  • 9,712
  • 2
  • 17
  • 22
  • I had no luck finding anything in the GameRegistry so I guess it must be somewhere else in the code. Though if the problem lies in the code I didn't write changing it might result in incompatibility between mods. So I'd have to go back to using a counter instead.. – Pascal Neubert Jan 22 '14 at 11:51
  • Maybe you just need to, when you free a block: "unregister" it off your GameRegistry if possible, and assign a null value in its place in your blocks list, creating again a gap. If that "unregister" method exists and it is clean, it should do the trick for your GC to understand it must clean the block – Jorge_B Jan 22 '14 at 12:02
  • Unfortunately there's no such function anywhere in the `cpw.mods.fml.common.registry.*`. Besides if I'd set the blocks list to null, wouldn't it try to create a block with a duplicated id? Finding the gaps is not a problem as the program starts up just fine. – Pascal Neubert Jan 22 '14 at 12:07
  • 1
    You should just make sure you have set to null every reference you store in your application to every object once you consider it is no more necessary. If those references still exist, that is the definition of memory leak in java as in the example in the answer. Once you have made sure you have no more references in your code, consider investigating what `GameRegistry` does with the reference to your `Blocks` and, if you can't fix it, submit a bug or something to the code owner – Jorge_B Jan 22 '14 at 13:27
  • That's a really good idea. I should definitely notify the Forge API developers. They should know best about the whole thing. Weird that I didn't have this idea earlier. Well for now I go with having a counter as I had before. – Pascal Neubert Jan 22 '14 at 13:32