IMPORTANT: @Core Devs (and anyone using large Worlds in Jme) MemoryManagement issues/problems

Hi, I have found a few deep Problems with the CurrentAssetCache, I would like to suggest a idea for a solution and I’m also willing to write that and commit it to the jme project. I number every point to make discussion easier.

  1. AssetKey(currently per default) sets everything to cache, however when haveing a large world (like a mmo) you quickly fill all memory up. Per default Modelkey and MaterialKey do NOT get released. In large worlds this leads directly to a outofdirectmemory exception.
  2. Smartcache, the current “cache” is based on WeakReferences, however these have two problems:

    2.1. Objects only held alive by a Weakreference are usualy garbage collected as soon as there is no hardreference anymore → So basically every object not used for a few seconds is nearly guranteed to be garbagecollected wich makes the cache not really caching stuff, however the Directmemory is released as soon as it is not longer used.

    2.2 Using SoftReferences would partly solve the problem 2.1, since Softrefenced objects are only collected when the memory runs low

    2.3 However Softreferences have the Problem that they don’t know about DirectMemory this leads to the Problem that We still can run ouf of directmemory (we only did not with weakreferences since its kinda like not caching at all)
  3. The needed solution Idea is one of the following two

    3.1 A own Memorymanager, that offers a method to allocate DirectMemory, if the DirectMemory allocation fails with a outofmemory it kills CachedAssets, untill the DirectMemoryBuffer can be allocated.

    3.1.1 A good idea how to determine wich is the best choice to clear of the Cache is needed, a simple idea would be to store the last time a asset was accesed, and unload the oldest one

    3.2 A way to determine how much directMemory the jvm is allowed to have, and if new should be allocated test if there is still enough free, if not clear something similar to 3.1.1 this would have the benfit of not trying to allocate untill it fits, instead it could be made sure that enough is cleared before the first attempt.
  4. I think the only way to have a Java like memory model is by having a automatic System that does not force the user to keep a look at what he have to release when.

    Please feel free to discuss, (in fact not only feel free but do if you know anything helpful, as this needs to be solved if JME3 wants to be used in serious largescale projects (or in Enviroments where Ram is limited(Android))

I was not aware the weak references get released that fast. As for the other bugs, we already know about them and they are high on the to do list.



By the way, you see your Shaders (M) rising right? Is that what you meant by materials?

I think we may need to add a separate statistic for tracking meshes cause right now its only “Objects” which is number of objects rendered.

No I meant matdefs, but they are problaby only a very limited amount total even if they get never unloaded.



As for the caching problems, as I said, I would like to fix that stuff and commit it, after a general idea for managing the Directmemoy is decided. Since I currently have that problem and already digged deep into the source I can probably write that in 2-3 days without problems, I just don#t want to start it before I know that it is going to be used, since I dont want to create a own branch of jme for myself.

The code is already there. Essentially you want to give models and shaders the same treatment that textures are getting right now.

Not exactly essentialy I also want to make sure that the cache cares about DirectMemory, since Weak/Softereferences only care about Heap. I came to this conclusion after modifying my local copy with smartcache ModelKey and MaterialDef’s and changing the Cache to Softreferences. I tried to explain this in my first post, but I’m not sure if I made it clear enough.

Yes I see what you mean. But there’s not much we can do.

Java won’t try to clean up stuff that doesn’t take much space on the heap, when you allocate a direct buffer.



The only solution is to either not use Soft/WeakReferences or to do what you said, try to allocate and if failed, invoke the GC in a loop. Hopefully at some point it will decide to just wipe out all soft references which will allow you to get the result you want

I do a similar GC loop in mythruna when I run out of direct buffer memory. Sometimes it recovers, sometimes it crashes hard.



I’m convinced that there is some oddness in the way the JVM manages direct memory. It is certainly not as quick to GC and not as reliable as regular heap memory. I wish there was a way to force it to free it when we knew we were done with it instead of waiting for GC to clean it up.

pspeed said:
I wish there was a way to force it to free it when we knew we were done with it instead of waiting for GC to clean it up.

It is possible to do this, but you need to create a native library that will do allocation/deallocation for you.

This has some solutions:
http://stackoverflow.com/questions/1744533/jna-bytebuffer-not-getting-freed-and-causing-c-heap-to-run-out-of-memory

Yeah, I’ve seen those.



The other option is to create huge buffers yourself and then partition them out as sub-buffers. I’m not sure that will play nice with the native lwjgl stuff but it’s theoretically possible. Slicing a buffer up this way is not straight forward in the API and one wrong move opens you up to the same kinds of memory corruption I got into Java to avoid.

For what it’s worth, my own current plan is to allocate fewer more consistently sized buffers and reuse them whenever possible.



Also, as hinted in the link, I can confirm that setting a really large max heap will make direct memory performance worse because GC runs less often. I first encountered this when running Mythruna with default heap settings and never experiencing an OutOfMemoryError… though the GC pauses were quite frequent.

When i use the WeakReference cache there is currently it always cleans up the memory nearly instantly. Also I strongly dislike the idea of force remving Assets from the Cache, as then we have the good old C++ unsafe nullpointers back. I will make an attempt then to write a system that keeps mostly compatible with the current but takes care about the DirectMemory and see if it works better than the current one. Basic solution is to force GC when out of DirectMemory wich should help at least in most cases where there is enough space.

Note that a forced GC at that point will pause all threads for 1-2 seconds. It is VERY noticeable.



In Mythruna I have the ‘x’ key that you can hit to force GC. You have to pound it a few times to get it aggressively clean. If there is a lot in the heap, the first few times there will be a 1+ second pause with each hit but eventually it returns instantly and you know you are as clean as you are going to get. System.gc() is pretty brutal.

Well I know, but a lag is still better than a crash I assume. Also this will probably be better in the future (at least on windows) when the new garbagecollecter with the oracle jvm7 comes, as it seem like they improved much there. Secondly I after that I might try to implement a background unloading by clearing the cache from uneeded objects, to reduce the amount of full gc’s needed, but first things first.

Patch ,

→ using SoftReference instead of Weak

→ Remove of regularCache as it needs to be cleaned up manually and is not needed when the Smartcache works efficiently

→ Assets now cache per default

→ Made various Assets Cacheable (implement Asset)

→ adding a MemoryManager, that check every allocation and cleans up memory if needed, or if not possible fails with a OutOfMemoryException cleanly.

→ Changing all ByteBuffer.allocateDircetly to the new MemoryManager.allocate

→ Added a way to the Memorymanager to find out how much DirectMemory is aviable

Still TODO: add to the Memorymanager a background thread, that tries to keep the usage of the Directmemory at around 85% by cleaning up unneeded assets without invoking a full GC, this is to reduce the lags caused by this.

(Note this is just thought as a first prototype and probably needs some cleaning up)

Please say what you think about this, and also mention errors if you see some.



Index: src/desktop/com/jme3/asset/AssetCache.java

===================================================================

— src/desktop/com/jme3/asset/AssetCache.java (revision 7835)

+++ src/desktop/com/jme3/asset/AssetCache.java (working copy)

@@ -32,6 +32,7 @@

package com.jme3.asset;

+import java.lang.ref.SoftReference;

import java.lang.ref.WeakReference;

import java.util.HashMap;

import java.util.WeakHashMap;

@@ -46,13 +47,12 @@

public class AssetCache {

public static final class SmartAssetInfo {

  •    public WeakReference<AssetKey> smartKey;<br />
    
  •    public SoftReference<AssetKey> smartKey;<br />
    

public Asset asset;

}

private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache

= new WeakHashMap<AssetKey, SmartAssetInfo>();

  • private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();

    /**
  • Adds a resource to the cache.

    @@ -61,21 +61,18 @@
  • @see #getFromCache(java.lang.String)

    */

    public void addToCache(AssetKey key, Object obj){
  •    synchronized (regularCache){<br />
    
  •        if (obj instanceof Asset && key.useSmartCache()){<br />
    
  •            // put in smart cache<br />
    
  •            Asset asset = (Asset) obj;<br />
    
  •            asset.setKey(null); // no circular references<br />
    
  •            SmartAssetInfo smartInfo = new SmartAssetInfo();<br />
    
  •            smartInfo.asset = asset;<br />
    
  •            // use the original key as smart key<br />
    
  •            smartInfo.smartKey = new WeakReference<AssetKey>(key);<br />
    
  •            smartCache.put(key, smartInfo);<br />
    
  •        }else{<br />
    
  •            // put in regular cache<br />
    
  •            regularCache.put(key, obj);<br />
    
  •   if (obj instanceof Asset){<br />
    
  •   	synchronized(smartCache){<br />
    
  •    		// put in smart cache<br />
    
  •    		Asset asset = (Asset) obj;<br />
    
  •    		asset.setKey(null); // no circular references<br />
    
  •    		SmartAssetInfo smartInfo = new SmartAssetInfo();<br />
    
  •    		smartInfo.asset = asset;<br />
    
  •    		// use the original key as smart key<br />
    
  •    		smartInfo.smartKey = new SoftReference<AssetKey>(key);<br />
    
  •    		smartCache.put(key, smartInfo);<br />
    

}

  •    }<br />
    
  •   }<br />
    

}

/**

@@ -84,13 +81,10 @@

  • Thread-safe.

    */

    public boolean deleteFromCache(AssetKey key){
  •    if (key.useSmartCache()){<br />
    
  •        throw new UnsupportedOperationException("You cannot delete from the smart cache");<br />
    
  •    }<br />
    

-

  •    synchronized (regularCache){<br />
    
  •        return regularCache.remove(key) != null;<br />
    
  •    }<br />
    
  •   synchronized (smartCache){<br />
    
  •   	 smartCache.remove(key);<br />
    
  •   }<br />
    
  •   	return true;<br />
    

}

/**

@@ -100,31 +94,18 @@

  • @param key
  • @return

    */
  • public Object getFromCache(AssetKey key){
  •    synchronized (regularCache){<br />
    
  •        if (key.useSmartCache()) {<br />
    
  •            return smartCache.get(key).asset;<br />
    
  •        } else {<br />
    
  •            return regularCache.get(key);<br />
    
  •        }<br />
    
  • public SmartAssetInfo getFromCache(AssetKey key){
  •    synchronized (smartCache){<br />
    
  •    	return smartCache.get(key);<br />
    

}

}

/**

  • * Retrieves smart asset info from the cache.<br />
    
  • * @param key<br />
    
  • * @return<br />
    
  • */<br />
    
  • public SmartAssetInfo getFromSmartCache(AssetKey key){
  •    return smartCache.get(key);<br />
    
  • }

    -
  • /**
  • Deletes all the assets in the regular cache.

    */

    public void deleteAllAssets(){
  •    synchronized (regularCache){<br />
    
  •        regularCache.clear();<br />
    
  •    synchronized (smartCache){<br />
    
  •    	smartCache.clear();<br />
    

}

}

}

Index: src/desktop/com/jme3/asset/DesktopAssetManager.java

===================================================================

— src/desktop/com/jme3/asset/DesktopAssetManager.java (revision 7835)

+++ src/desktop/com/jme3/asset/DesktopAssetManager.java (working copy)

@@ -205,17 +205,13 @@

AssetKey smartKey = null;

Object o = null;

if (key.shouldCache()){

  •        if (key.useSmartCache()){<br />
    
  •            SmartAssetInfo smartInfo = cache.getFromSmartCache(key);<br />
    
  •            if (smartInfo != null){<br />
    
  •                smartKey = smartInfo.smartKey.get();<br />
    
  •                if (smartKey != null){<br />
    
  •                    o = smartInfo.asset;<br />
    
  •                }<br />
    
  •            }<br />
    
  •        }else{<br />
    
  •            o = cache.getFromCache(key);<br />
    
  •        }<br />
    
  •    	SmartAssetInfo smartInfo = cache.getFromCache(key);<br />
    
  •    	if (smartInfo != null){<br />
    
  •    		smartKey = smartInfo.smartKey.get();<br />
    
  •    		if (smartKey != null){<br />
    
  •    			o = smartInfo.asset;<br />
    
  •    		}<br />
    
  •    	}<br />
    

}

if (o == null){

AssetLoader loader = handler.aquireLoader(key);

@@ -264,7 +260,7 @@

// create an instance for user

T clone = (T) key.createClonedInstance(o);

  •    if (key.useSmartCache()){<br />
    
  •    if (clone instanceof Asset){<br />
    

if (smartKey != null){

// smart asset was already cached, use original key

((Asset)clone).setKey(smartKey);

@@ -352,8 +348,9 @@

public Shader loadShader(ShaderKey key){

// cache abuse in method

// that doesn’t use loaders/locators

  •    Shader s = (Shader) cache.getFromCache(key);<br />
    
  •    if (s == null){<br />
    
  •    SmartAssetInfo c = cache.getFromCache(key);<br />
    
  •    Shader s;<br />
    
  •    if (c == null){<br />
    

String vertName = key.getVertName();

String fragName = key.getFragName();

@@ -365,6 +362,8 @@

s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());

cache.addToCache(key, s);

  •    }else{<br />
    
  •    	s = (Shader)(Object)c.asset;<br />
    

}

return s;

}

Index: src/networking/com/jme3/network/connection/TCPConnection.java

===================================================================

— src/networking/com/jme3/network/connection/TCPConnection.java (revision 7835)

+++ src/networking/com/jme3/network/connection/TCPConnection.java (working copy)

@@ -35,6 +35,8 @@

import com.jme3.network.message.Message;

import com.jme3.network.queue.MessageQueue;

import com.jme3.network.serializing.Serializer;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.SocketAddress;

@@ -72,9 +74,9 @@

{

label = name;

  •    readBuffer =        ByteBuffer.allocateDirect(16228);<br />
    
  •    writeBuffer =       ByteBuffer.allocateDirect(16228);<br />
    
  •    tempWriteBuffer =   ByteBuffer.allocateDirect(16228);<br />
    
  •    readBuffer =        MemoryManager.allocate(16228);<br />
    
  •    writeBuffer =       MemoryManager.allocate(16228);<br />
    
  •    tempWriteBuffer =   MemoryManager.allocate(16228);<br />
    

}

public TCPConnection() { }

Index: src/networking/com/jme3/network/connection/SSLTCPConnection.java

===================================================================

— src/networking/com/jme3/network/connection/SSLTCPConnection.java (revision 7835)

+++ src/networking/com/jme3/network/connection/SSLTCPConnection.java (working copy)

@@ -33,6 +33,9 @@

package com.jme3.network.connection;

import javax.net.ssl.*;

+

+import com.jme3.util.MemoryManager;

+

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

@@ -83,9 +86,9 @@

SSLSession session = sslEngine.getSession();

  •    incDataDecrypted    = ByteBuffer.allocateDirect(session.getApplicationBufferSize());<br />
    
  •    incDataEncrypted    = ByteBuffer.allocateDirect(session.getPacketBufferSize());<br />
    
  •    outDataEncrypted    = ByteBuffer.allocateDirect(session.getPacketBufferSize());<br />
    
  •    incDataDecrypted    = MemoryManager.allocate(session.getApplicationBufferSize());<br />
    
  •    incDataEncrypted    = MemoryManager.allocate(session.getPacketBufferSize());<br />
    
  •    outDataEncrypted    = MemoryManager.allocate(session.getPacketBufferSize());<br />
    

incDataEncrypted.position(incDataEncrypted.limit());

outDataEncrypted.position(outDataEncrypted.limit());

Index: src/networking/com/jme3/network/connection/UDPConnection.java

===================================================================

— src/networking/com/jme3/network/connection/UDPConnection.java (revision 7835)

+++ src/networking/com/jme3/network/connection/UDPConnection.java (working copy)

@@ -35,6 +35,8 @@

import com.jme3.network.message.DiscoverHostMessage;

import com.jme3.network.message.Message;

import com.jme3.network.serializing.Serializer;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.SocketAddress;

@@ -61,8 +63,8 @@

public UDPConnection(String label) {

this.label = label;

  •    readBuffer =    ByteBuffer.allocateDirect(8192);<br />
    
  •    writeBuffer =   ByteBuffer.allocateDirect(8192);<br />
    
  •    readBuffer =    MemoryManager.allocate(8192);<br />
    
  •    writeBuffer =   MemoryManager.allocate(8192);<br />
    

}

public void connect(SocketAddress address) throws IOException {

Index: src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java

===================================================================

— src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (revision 7835)

+++ src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (working copy)

@@ -46,6 +46,8 @@

import com.jme3.audio.LowPassFilter;

import com.jme3.math.Vector3f;

import com.jme3.util.BufferUtils;

+import com.jme3.util.MemoryManager;

+

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;

@@ -77,7 +79,7 @@

private final static int MAX_NUM_CHANNELS = 64;

private IntBuffer ib = BufferUtils.createIntBuffer(1);

private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);

  • private final ByteBuffer nativeBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
  • private final ByteBuffer nativeBuf = MemoryManager.allocate(BUFFER_SIZE);

    private final byte[] arrayBuf = new byte[BUFFER_SIZE];

    private int[] channels;

    Index: src/bullet/com/jme3/bullet/util/NativeMeshUtil.java

    ===================================================================

    — src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (revision 7835)

    +++ src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (working copy)

    @@ -34,6 +34,8 @@

    import com.jme3.scene.Mesh;

    import com.jme3.scene.VertexBuffer.Type;

    import com.jme3.scene.mesh.IndexBuffer;

    +import com.jme3.util.MemoryManager;

    +

    import java.nio.ByteBuffer;

    import java.nio.ByteOrder;

    import java.nio.FloatBuffer;

    @@ -45,8 +47,8 @@

    public class NativeMeshUtil {

    public static long getTriangleIndexVertexArray(Mesh mesh){
  •    ByteBuffer triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    ByteBuffer vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    ByteBuffer triangleIndexBase = MemoryManager.allocate(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    ByteBuffer vertexBase = MemoryManager.allocate(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    

int numVertices = mesh.getVertexCount();

int vertexStride = 12; //3 verts * 4 bytes per.

int numTriangles = mesh.getTriangleCount();

Index: src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java

===================================================================

— src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java (revision 7835)

+++ src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java (working copy)

@@ -39,6 +39,8 @@

import com.jme3.export.OutputCapsule;

import com.jme3.scene.VertexBuffer.Type;

import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

@@ -68,8 +70,8 @@

}

private void createCollisionMesh(Mesh mesh) {

  •    triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    triangleIndexBase = MemoryManager.allocate(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    vertexBase = MemoryManager.allocate(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    

numVertices = mesh.getVertexCount();

vertexStride = 12; //3 verts * 4 bytes per.

numTriangles = mesh.getTriangleCount();

Index: src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java

===================================================================

— src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (revision 7835)

+++ src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (working copy)

@@ -11,6 +11,8 @@

import com.jme3.math.FastMath;

import com.jme3.math.Vector3f;

import com.jme3.scene.Mesh;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

@@ -94,7 +96,7 @@

}

protected void createShape() {

  •    bbuf = ByteBuffer.allocateDirect(heightfieldData.length * 4).order(ByteOrder.nativeOrder());<br />
    
  •    bbuf = MemoryManager.allocate(heightfieldData.length * 4).order(ByteOrder.nativeOrder());<br />
    

// fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData);

// fbuf.rewind();

// fbuf.put(heightfieldData);

Index: src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java

===================================================================

— src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (revision 7835)

+++ src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (working copy)

@@ -39,6 +39,8 @@

import com.jme3.export.OutputCapsule;

import com.jme3.scene.VertexBuffer.Type;

import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

@@ -70,8 +72,8 @@

}

private void createCollisionMesh(Mesh mesh) {

  •    triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    triangleIndexBase = MemoryManager.allocate(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    
  •    vertexBase = MemoryManager.allocate(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());<br />
    

// triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4);

// vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4);

numVertices = mesh.getVertexCount();

Index: src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java

===================================================================

— src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (revision 7835)

+++ src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (working copy)

@@ -7,6 +7,8 @@

import com.jme3.export.OutputCapsule;

import com.jme3.scene.Mesh;

import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.util.MemoryManager;

+

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

@@ -66,7 +68,7 @@

// objectId = new ConvexHullShape(pointList);

// objectId.setLocalScaling(Converter.convert(getScale()));

// objectId.setMargin(margin);

  •    ByteBuffer bbuf=ByteBuffer.allocateDirect(points.length * 4).order(ByteOrder.nativeOrder());<br />
    
  •    ByteBuffer bbuf=MemoryManager.allocate(points.length * 4).order(ByteOrder.nativeOrder());<br />
    

// fbuf = bbuf.asFloatBuffer();

// fbuf.rewind();

// fbuf.put(points);

Index: src/core/com/jme3/util/MemoryManager.java

===================================================================

— src/core/com/jme3/util/MemoryManager.java (revision 0)

+++ src/core/com/jme3/util/MemoryManager.java (revision 0)

@@ -0,0 +1,64 @@

+package com.jme3.util;

+

+import java.nio.ByteBuffer;

+import java.nio.ShortBuffer;

+import java.util.ArrayList;

+

+public class MemoryManager {

  • private static MemoryManager instance;

    +
  • private int maxDirectByte;

    +
  • public static MemoryManager getInstance(){
  •   if(instance == null){<br />
    
  •   	instance = new MemoryManager();<br />
    
  •   }<br />
    
  •   return instance;<br />
    
  • }

    +

    +
  • public MemoryManager(){
  •   //determine max directMemory in a jvm independent Way<br />
    
  •   ArrayList<ByteBuffer> amount = new ArrayList<ByteBuffer>();<br />
    
  •   try{<br />
    
  •   	while(true){<br />
    
  •   		ByteBuffer buffer = ByteBuffer.allocateDirect(1*1024*1024);<br />
    
  •   		amount.add(buffer);<br />
    
  •   	}<br />
    
  •   }catch(Throwable e){<br />
    
  •   	//leave 5% of all directMemory free for AudioRenderer,Threadhandeling,and other low level stuff<br />
    
  •   	maxDirectByte = (int) (amount.size()*1024*1024 * 0.95);<br />
    
  •   	System.out.println("Syste has " + amount.size() + " MB direct Memory");<br />
    
  •   	System.out.println("Reserved for low level " + (int) (amount.size() * 0.05) + " Mb direct Memory");<br />
    
  •   }<br />
    
  •   amount = null;<br />
    
  •   //clear those buffers<br />
    
  •   System.gc();<br />
    

+

  • }

    +
  • public static ByteBuffer allocate(int i) {
  •   return getInstance().allocate(i,false);<br />
    
  • }

    +
  • private ByteBuffer allocate(int i,boolean retry) {
  •   try{<br />
    
  •   	ByteBuffer buffer = ByteBuffer.allocateDirect(i);<br />
    
  •   	return buffer;<br />
    
  •   }catch(Exception e){<br />
    
  •   	if(retry){<br />
    
  •   		BufferUtils.printCurrentDirectMemory(null);<br />
    
  •   		throw new OutOfMemoryError("Unable to free enough Direct Memory");<br />
    
  •   	}<br />
    
  •   	retry(i);<br />
    
  •   }<br />
    
  •   return null;<br />
    
  • }

    +

    +
  • private void retry(int i) {
  •   System.out.println("Emergency GC");<br />
    
  •   System.gc();<br />
    
  •   allocate(i,true);<br />
    
  • }

    +}

    Index: src/core/com/jme3/util/BufferUtils.java

    ===================================================================

    — src/core/com/jme3/util/BufferUtils.java (revision 7835)

    +++ src/core/com/jme3/util/BufferUtils.java (working copy)

    @@ -700,7 +700,8 @@
  • @return the new DoubleBuffer

    */

    public static DoubleBuffer createDoubleBuffer(int size) {
  •    DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();<br />
    
  •   ByteBuffer buffer = MemoryManager.allocate(8 * size);<br />
    
  •    DoubleBuffer buf = buffer.order(ByteOrder.nativeOrder()).asDoubleBuffer();<br />
    

buf.clear();

onBufferAllocated(buf);

return buf;

@@ -763,7 +764,7 @@

  • @return the new FloatBuffer

    */

    public static FloatBuffer createFloatBuffer(int size) {
  •    FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    
  •    FloatBuffer buf = MemoryManager.allocate(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    

buf.clear();

onBufferAllocated(buf);

return buf;

@@ -825,7 +826,7 @@

  • @return the new IntBuffer

    */

    public static IntBuffer createIntBuffer(int size) {
  •    IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();<br />
    
  •    IntBuffer buf = MemoryManager.allocate(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();<br />
    

buf.clear();

onBufferAllocated(buf);

return buf;

@@ -888,7 +889,7 @@

  • @return the new IntBuffer

    */

    public static ByteBuffer createByteBuffer(int size) {
  •    ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());<br />
    
  •    ByteBuffer buf = MemoryManager.allocate(size).order(ByteOrder.nativeOrder());<br />
    

buf.clear();

onBufferAllocated(buf);

return buf;

@@ -966,7 +967,7 @@

  • @return the new ShortBuffer

    */

    public static ShortBuffer createShortBuffer(int size) {
  •    ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();<br />
    
  •    ShortBuffer buf = MemoryManager.allocate(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();<br />
    

buf.clear();

onBufferAllocated(buf);

return buf;

Index: src/core/com/jme3/font/BitmapFont.java

===================================================================

— src/core/com/jme3/font/BitmapFont.java (revision 7835)

+++ src/core/com/jme3/font/BitmapFont.java (working copy)

@@ -35,6 +35,8 @@

import java.io.IOException;

import java.util.Arrays;

+import com.jme3.asset.Asset;

+import com.jme3.asset.AssetKey;

import com.jme3.export.InputCapsule;

import com.jme3.export.JmeExporter;

import com.jme3.export.JmeImporter;

@@ -46,7 +48,7 @@

  • Represents a font within jME that is generated with the AngelCode Bitmap Font Generator
  • @author dhdd

    */

    -public class BitmapFont implements Savable {

    +public class BitmapFont implements Savable ,Asset{

    public enum Align {

    Left, Center, Right

    @@ -57,6 +59,7 @@

    private BitmapCharacterSet charSet;

    private Material[] pages;
  • private AssetKey key;

    public BitmapFont() {

    }

    @@ -219,4 +222,13 @@

    charSet.setStyle(style);

    }
  • @Override
  • public void setKey(AssetKey key) {
  •   this.key = key;<br />
    
  • }

    +
  • @Override
  • public AssetKey getKey() {
  •   return this.key;<br />
    
  • }

    }

    No newline at end of file

    Index: src/core/com/jme3/material/Material.java

    ===================================================================

    — src/core/com/jme3/material/Material.java (revision 7835)

    +++ src/core/com/jme3/material/Material.java (working copy)

    @@ -31,6 +31,7 @@

    */

    package com.jme3.material;

    +import com.jme3.asset.Asset;

    import com.jme3.asset.AssetKey;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Matrix4f;

    @@ -80,7 +81,7 @@

    *
  • @author Kirill Vainer

    */

    -public class Material implements Cloneable, Savable, Comparable {

    +public class Material implements Cloneable, Savable, Comparable,Asset {

    private static final Logger logger = Logger.getLogger(Material.class.getName());

    private static final RenderState additiveLight = new RenderState();

    @@ -109,6 +110,7 @@

    private boolean receivesShadows = false;

    private int sortingId = -1;

    private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
  • private AssetKey key;

    public Material(MaterialDef def) {

    if (def == null) {

    @@ -1073,4 +1075,15 @@

    setBoolean(“SeparateTexCoord”, true);

    }

    }

    +

    +
  • @Override
  • public void setKey(AssetKey key) {
  •   this.key = key;<br />
    
  • }

    +
  • @Override
  • public AssetKey getKey() {
  •   return this.key;<br />
    
  • }

    }

    Index: src/core/com/jme3/material/MaterialDef.java

    ===================================================================

    — src/core/com/jme3/material/MaterialDef.java (revision 7835)

    +++ src/core/com/jme3/material/MaterialDef.java (working copy)

    @@ -32,6 +32,8 @@

    package com.jme3.material;

    +import com.jme3.asset.Asset;

    +import com.jme3.asset.AssetKey;

    import com.jme3.asset.AssetManager;

    import com.jme3.shader.VarType;

    import java.util.ArrayList;

    @@ -46,7 +48,7 @@

    *
  • @author Kirill Vainer

    */

    -public class MaterialDef {

    +public class MaterialDef implements Asset{

    private static final Logger logger = Logger.getLogger(MaterialDef.class.getName());

    @@ -58,6 +60,8 @@

    private Map<String, TechniqueDef> techniques;

    private Map<String, MatParam> matParams;
  • private AssetKey key;

    +

    /**
  • Serialization only. Do not use.

    */

    @@ -177,4 +181,14 @@

    return techniques.get(name);

    }
  • @Override
  • public void setKey(AssetKey key) {
  •   this.key = key;<br />
    
  • }

    +
  • @Override
  • public AssetKey getKey() {
  •   return this.key;<br />
    
  • }

    +

    }

    Index: src/core/com/jme3/terrain/BufferGeomap.java

    ===================================================================

    — src/core/com/jme3/terrain/BufferGeomap.java (revision 7787)

    +++ src/core/com/jme3/terrain/BufferGeomap.java (working copy)

    @@ -42,6 +42,8 @@

    import com.jme3.scene.VertexBuffer;

    import com.jme3.scene.VertexBuffer.Type;

    import com.jme3.util.BufferUtils;

    +import com.jme3.util.MemoryManager;

    +

    import java.io.IOException;

    import java.nio.BufferUnderflowException;

    import java.nio.ByteBuffer;

    @@ -67,7 +69,7 @@

    }

    public BufferGeomap(int width, int height, int maxval) {
  •    this(ByteBuffer.allocateDirect(width*height*4).asFloatBuffer(),null,width,height,maxval);<br />
    
  •    this(MemoryManager.allocate(width*height*4).asFloatBuffer(),null,width,height,maxval);<br />
    

}

public FloatBuffer getHeightData(){

@@ -127,7 +129,7 @@

}

public Geomap copySubGeomap(int x, int y, int w, int h){

  •    FloatBuffer nhdata = ByteBuffer.allocateDirect(w*h*4).asFloatBuffer();<br />
    
  •    FloatBuffer nhdata = MemoryManager.allocate(w*h*4).asFloatBuffer();<br />
    

hdata.position(y*width+x);

for (int cy = 0; cy < height; cy++){

hdata.limit(hdata.position()+w);

@@ -139,7 +141,7 @@

ByteBuffer nndata = null;

if (ndata!=null){

  •        nndata = ByteBuffer.allocateDirect(w*h*3);<br />
    
  •        nndata = MemoryManager.allocate(w*h*3);<br />
    

ndata.position( (y*width+x)3 );

for (int cy = 0; cy < height; cy++){

ndata.limit(ndata.position()+w
3);

Index: src/core/com/jme3/asset/TextureKey.java

===================================================================

— src/core/com/jme3/asset/TextureKey.java (revision 7835)

+++ src/core/com/jme3/asset/TextureKey.java (working copy)

@@ -68,15 +68,6 @@

return name + (flipY ? " (Flipped)" : “”) + (asCube ? " (Cube)" : “”) + (generateMips ? " (Mipmaped)" : “”);

}

  • /**
  • * Enable smart caching for textures<br />
    
  • * @return true to enable smart cache<br />
    
  • */<br />
    
  • @Override
  • public boolean useSmartCache(){
  •    return true;<br />
    
  • }

    -

    @Override

    public Object createClonedInstance(Object asset){

    Texture tex = (Texture) asset;

    Index: src/core/com/jme3/asset/AssetKey.java

    ===================================================================

    — src/core/com/jme3/asset/AssetKey.java (revision 7835)

    +++ src/core/com/jme3/asset/AssetKey.java (working copy)

    @@ -128,15 +128,6 @@

    public boolean shouldCache(){

    return true;

    }

    -
  • /**
  • * @return Should return true, if the asset objects implement the "Asset"<br />
    
  • * interface and want to be removed from the cache when no longer<br />
    
  • * referenced in user-code.<br />
    
  • */<br />
    
  • public boolean useSmartCache(){
  •    return false;<br />
    
  • }

    @Override

    public boolean equals(Object other){

    Index: src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java

    ===================================================================

    — src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (revision 7835)

    +++ src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (working copy)

    @@ -45,6 +45,7 @@

    import com.jme3.scene.Node;

    import com.jme3.scene.Spatial;

    import com.jme3.scene.VertexBuffer.Type;

    +import com.jme3.util.MemoryManager;

    import com.jme3.util.TempVars;

    import java.nio.ByteBuffer;

    import java.nio.ByteOrder;

    @@ -177,7 +178,7 @@

    // The number of bytes needed is: (floats in a vertex) * (vertices in a triangle) * (# of triangles) * (size of float in bytes)

    final int numberOfFloats = 3 * 3 * numberOfTriangles;

    final int byteBufferSize = numberOfFloats * Float.SIZE;
  •    FloatBuffer vertices = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    
  •    FloatBuffer vertices = MemoryManager.allocate(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    

// Force the limit, set the cap - most number of floats we will use the buffer for

vertices.limit(numberOfFloats);

@@ -236,7 +237,7 @@

// There are 3 floats needed for each vertex (x,y,z)

final int numberOfFloats = vertices.size() * 3;

final int byteBufferSize = numberOfFloats * Float.SIZE;

  •    FloatBuffer verticesBuffer = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    
  •    FloatBuffer verticesBuffer = MemoryManager.allocate(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();<br />
    

// Force the limit, set the cap - most number of floats we will use the buffer for

verticesBuffer.limit(numberOfFloats);

Just some comments…



First, everything in src/networking/com/jme3/network/connection is deprecated as it has been replaced with newer code.



Second, if I’m reading it right the MemoryManager constructor is pretty brutal. In my game, just using JME would cause it to grab 1 gig from the OS right away and then release references. (Mythruna sets the direct memory size large.) In my experience, direct memory is not always freed right away even when invoking System.gc(). Maybe it is more likely when the heap/mem is still relatively clean. At any rate, the JVM is sometimes reluctant to give it back to the OS. We’ll need to do some testing to see how quick it goes back or every tiny test will look like it takes 256 meg or whatever.



Third, more of a style issue or maybe my misunderstanding but it seems like the retry flag on allocate is actually a dontRetry flag.



Also not sure how I feel about including the AssetKey in assets but that’s Kirill’s call. Most of my ‘assets’ are created and not loaded so that would be an unused field in my case.

pspeed said:
Also not sure how I feel about including the AssetKey in assets but that's Kirill's call. Most of my 'assets' are created and not loaded so that would be an unused field in my case.

Its kind of unavoidable. If you want to use smart caching then you need to have a reference to the AssetKey like that. The other option is to use userdata for this however obviously it won't work for shaders and textures.

So far I am okay with the patch, except that all ByteBuffer.allocateDirect() should be replaced with BufferUtils.create***Buffer() and then only BufferUtils has to use MemoryManager. Also I don't like the idea of force using smart caching for all assets. Not all assets need it.
For example, the data shaders take is insignificant compared to other assets and due to the way we are gonna manage memory now, shaders could get cleaned out often and then going to retrieve them from disk is expensive.

Ok, after thinking about this some more I’m going to put forth a longer argument on why I think that the AssetManager’s weak referenced behavior is correct… and why I think it is impossible to track allocated/used direct buffers accurately enough to use to clean a soft cache… but I’m running late for a meeting. A smart cache is something that should be built on top of asset manager not into it.



I will post more this afternoon.

Well at least under windows with Sun jvm the direct memory gets released instantly. However you might be true that it will not depending on jvm and os. However on the other side, if you know you need up to 1gb direct memory and thus set the JVM parameter like this, you get the hard gurantee that you will get it then, and not that the jvm gets told to use 1 gb but can only allocate 500mb in reality since the rest is already used. But I agree that this depends on the use case, basically you either make some kind of game and set the diretmemory to what it needs and then it makes sense to allocate it all on startup,

other version is, that you do a small tool in wich case smallest possible would be good. The constructor could be extended to set a behaviour for both cases.

Momoko_Fan said:
Its kind of unavoidable. If you want to use smart caching then you need to have a reference to the AssetKey like that. The other option is to use userdata for this however obviously it won't work for shaders and textures.

So far I am okay with the patch, except that all ByteBuffer.allocateDirect() should be replaced with BufferUtils.create***Buffer() and then only BufferUtils has to use MemoryManager. Also I don't like the idea of force using smart caching for all assets. Not all assets need it.
For example, the data shaders take is insignificant compared to other assets and due to the way we are gonna manage memory now, shaders could get cleaned out often and then going to retrieve them from disk is expensive.



Actually this is the the second thing I want to make, I want to give each Assettype a rating how much memory it usualy takes (aka Shader < Models < Textures) or similar, also I want to get how often a specific asset is used (could be counted over the get method, and how long the last use is away) out of this I then want to determine wich ones should be freed first. (Other approach would be to let the programmer determine with a additional load flag if the asset should stay in memory as long as possible or if it could be freed anytime)

Shouldn’t it be



if ( !retry ) {



throw new OutOfMemoryException();

}



?

Currently it seems that meaning of the flag is reversed.